@odoo/o-spreadsheet 18.4.0-alpha.3 → 18.4.0-alpha.4
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 +1385 -819
- package/dist/o-spreadsheet.d.ts +359 -307
- package/dist/o-spreadsheet.esm.js +1385 -820
- package/dist/o-spreadsheet.iife.js +1385 -819
- package/dist/o-spreadsheet.iife.min.js +454 -443
- package/dist/o_spreadsheet.xml +82 -55
- package/package.json +1 -1
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* This file is generated by o-spreadsheet build tools. Do not edit it.
|
|
4
4
|
* @see https://github.com/odoo/o-spreadsheet
|
|
5
|
-
* @version 18.4.0-alpha.
|
|
6
|
-
* @date 2025-05-
|
|
7
|
-
* @hash
|
|
5
|
+
* @version 18.4.0-alpha.4
|
|
6
|
+
* @date 2025-05-20T05:57:45.452Z
|
|
7
|
+
* @hash 5c28bca
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
'use strict';
|
|
@@ -186,7 +186,7 @@ const ALERT_INFO_BG = "#CDEDF1";
|
|
|
186
186
|
const ALERT_INFO_BORDER = "#98DBE2";
|
|
187
187
|
const ALERT_INFO_TEXT_COLOR = "#09414A";
|
|
188
188
|
const BADGE_SELECTED_COLOR = "#E6F2F3";
|
|
189
|
-
const CHART_PADDING
|
|
189
|
+
const CHART_PADDING = 20;
|
|
190
190
|
const CHART_PADDING_BOTTOM = 10;
|
|
191
191
|
const CHART_PADDING_TOP = 15;
|
|
192
192
|
const CHART_TITLE_FONT_SIZE = 16;
|
|
@@ -348,6 +348,8 @@ const DEFAULT_GAUGE_UPPER_COLOR = "#43C5B1";
|
|
|
348
348
|
const DEFAULT_SCORECARD_BASELINE_MODE = "difference";
|
|
349
349
|
const DEFAULT_SCORECARD_BASELINE_COLOR_UP = "#43C5B1";
|
|
350
350
|
const DEFAULT_SCORECARD_BASELINE_COLOR_DOWN = "#EA6175";
|
|
351
|
+
const DEFAULT_SCORECARD_KEY_VALUE_FONT_SIZE = 32;
|
|
352
|
+
const DEFAULT_SCORECARD_BASELINE_FONT_SIZE = 16;
|
|
351
353
|
const LINE_FILL_TRANSPARENCY = 0.4;
|
|
352
354
|
const DEFAULT_WINDOW_SIZE = 2;
|
|
353
355
|
// session
|
|
@@ -397,6 +399,7 @@ const PIVOT_TABLE_CONFIG = {
|
|
|
397
399
|
styleId: "TableStyleMedium5",
|
|
398
400
|
automaticAutofill: false,
|
|
399
401
|
};
|
|
402
|
+
const PIVOT_INDENT = 15;
|
|
400
403
|
const DEFAULT_CURRENCY = {
|
|
401
404
|
symbol: "$",
|
|
402
405
|
position: "before",
|
|
@@ -1638,18 +1641,53 @@ function lettersToNumber(letters) {
|
|
|
1638
1641
|
let result = 0;
|
|
1639
1642
|
const l = letters.length;
|
|
1640
1643
|
for (let i = 0; i < l; i++) {
|
|
1641
|
-
const
|
|
1642
|
-
const colIndex = charCode >= 65 && charCode <= 90 ? charCode - 64 : charCode - 96;
|
|
1644
|
+
const colIndex = charToNumber(letters[i]);
|
|
1643
1645
|
result = result * 26 + colIndex;
|
|
1644
1646
|
}
|
|
1645
1647
|
return result - 1;
|
|
1646
1648
|
}
|
|
1649
|
+
function charToNumber(char) {
|
|
1650
|
+
const charCode = char.charCodeAt(0);
|
|
1651
|
+
return charCode >= 65 && charCode <= 90 ? charCode - 64 : charCode - 96;
|
|
1652
|
+
}
|
|
1647
1653
|
function isCharALetter(char) {
|
|
1648
1654
|
return (char >= "A" && char <= "Z") || (char >= "a" && char <= "z");
|
|
1649
1655
|
}
|
|
1650
1656
|
function isCharADigit(char) {
|
|
1651
1657
|
return char >= "0" && char <= "9";
|
|
1652
1658
|
}
|
|
1659
|
+
// we limit the max column to 3 letters and max row to 7 digits for performance reasons
|
|
1660
|
+
const MAX_COL = lettersToNumber("ZZZ");
|
|
1661
|
+
const MAX_ROW = 9999998;
|
|
1662
|
+
function consumeSpaces(chars) {
|
|
1663
|
+
while (chars.current === " ") {
|
|
1664
|
+
chars.advanceBy(1);
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
function consumeLetters(chars) {
|
|
1668
|
+
if (chars.current === "$")
|
|
1669
|
+
chars.advanceBy(1);
|
|
1670
|
+
if (!chars.current || !isCharALetter(chars.current)) {
|
|
1671
|
+
return -1;
|
|
1672
|
+
}
|
|
1673
|
+
let colCoordinate = 0;
|
|
1674
|
+
while (chars.current && isCharALetter(chars.current)) {
|
|
1675
|
+
colCoordinate = colCoordinate * 26 + charToNumber(chars.shift());
|
|
1676
|
+
}
|
|
1677
|
+
return colCoordinate;
|
|
1678
|
+
}
|
|
1679
|
+
function consumeDigits(chars) {
|
|
1680
|
+
if (chars.current === "$")
|
|
1681
|
+
chars.advanceBy(1);
|
|
1682
|
+
if (!chars.current || !isCharADigit(chars.current)) {
|
|
1683
|
+
return -1;
|
|
1684
|
+
}
|
|
1685
|
+
let num = 0;
|
|
1686
|
+
while (chars.current && isCharADigit(chars.current)) {
|
|
1687
|
+
num = num * 10 + Number(chars.shift());
|
|
1688
|
+
}
|
|
1689
|
+
return num;
|
|
1690
|
+
}
|
|
1653
1691
|
/**
|
|
1654
1692
|
* Convert a "XC" coordinate to cartesian coordinates.
|
|
1655
1693
|
*
|
|
@@ -1660,33 +1698,17 @@ function isCharADigit(char) {
|
|
|
1660
1698
|
* Note: it also accepts lowercase coordinates, but not fixed references
|
|
1661
1699
|
*/
|
|
1662
1700
|
function toCartesian(xc) {
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
// Process letter part
|
|
1668
|
-
if (xc[i] === "$")
|
|
1669
|
-
i++;
|
|
1670
|
-
while (i < xc.length && isCharALetter(xc[i])) {
|
|
1671
|
-
letterPart += xc[i++];
|
|
1672
|
-
}
|
|
1673
|
-
if (letterPart.length === 0 || letterPart.length > 3) {
|
|
1674
|
-
// limit to max 3 letters for performance reasons
|
|
1701
|
+
const chars = new TokenizingChars(xc);
|
|
1702
|
+
consumeSpaces(chars);
|
|
1703
|
+
const letterPart = consumeLetters(chars);
|
|
1704
|
+
if (letterPart === -1 || !chars.current) {
|
|
1675
1705
|
throw new Error(`Invalid cell description: ${xc}`);
|
|
1676
1706
|
}
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
}
|
|
1683
|
-
if (i !== xc.length || numberPart.length === 0 || numberPart.length > 7) {
|
|
1684
|
-
// limit to max 7 numbers for performance reasons
|
|
1685
|
-
throw new Error(`Invalid cell description: ${xc}`);
|
|
1686
|
-
}
|
|
1687
|
-
const col = lettersToNumber(letterPart);
|
|
1688
|
-
const row = Number(numberPart) - 1;
|
|
1689
|
-
if (isNaN(row)) {
|
|
1707
|
+
const num = consumeDigits(chars);
|
|
1708
|
+
consumeSpaces(chars);
|
|
1709
|
+
const col = letterPart - 1;
|
|
1710
|
+
const row = num - 1;
|
|
1711
|
+
if (!chars.isOver() || col > MAX_COL || row > MAX_ROW) {
|
|
1690
1712
|
throw new Error(`Invalid cell description: ${xc}`);
|
|
1691
1713
|
}
|
|
1692
1714
|
return { col, row };
|
|
@@ -5375,67 +5397,6 @@ function binarySuccessorSearch(arr, val, start = 0, matchEqual = true) {
|
|
|
5375
5397
|
return result;
|
|
5376
5398
|
}
|
|
5377
5399
|
|
|
5378
|
-
/** Reference of a cell (eg. A1, $B$5) */
|
|
5379
|
-
const cellReference = new RegExp(/\$?([A-Z]{1,3})\$?([0-9]{1,7})/, "i");
|
|
5380
|
-
// Same as above, but matches the exact string (nothing before or after)
|
|
5381
|
-
const singleCellReference = new RegExp(/^\$?([A-Z]{1,3})\$?([0-9]{1,7})$/, "i");
|
|
5382
|
-
/** Reference of a column header (eg. A, AB, $A) */
|
|
5383
|
-
const colHeader = new RegExp(/^\$?([A-Z]{1,3})+$/, "i");
|
|
5384
|
-
/** Reference of a row header (eg. 1, $1) */
|
|
5385
|
-
const rowHeader = new RegExp(/^\$?([0-9]{1,7})+$/, "i");
|
|
5386
|
-
/** Reference of a column (eg. A, $CA, Sheet1!B) */
|
|
5387
|
-
const colReference = new RegExp(/^\s*('.+'!|[^']+!)?\$?([A-Z]{1,3})$/, "i");
|
|
5388
|
-
/** Reference of a row (eg. 1, 59, Sheet1!9) */
|
|
5389
|
-
const rowReference = new RegExp(/^\s*('.+'!|[^']+!)?\$?([0-9]{1,7})$/, "i");
|
|
5390
|
-
/** Reference of a normal range or a full row range (eg. A1:B1, 1:$5, $A2:5) */
|
|
5391
|
-
const fullRowXc = /(\$?[A-Z]{1,3})?\$?[0-9]{1,7}\s*:\s*(\$?[A-Z]{1,3})?\$?[0-9]{1,7}\s*/i;
|
|
5392
|
-
/** Reference of a normal range or a column row range (eg. A1:B1, A:$B, $A1:C) */
|
|
5393
|
-
const fullColXc = /\$?[A-Z]{1,3}(\$?[0-9]{1,7})?\s*:\s*\$?[A-Z]{1,3}(\$?[0-9]{1,7})?\s*/i;
|
|
5394
|
-
/** Reference of a cell or a range, it can be a bounded range, a full row or a full column */
|
|
5395
|
-
const rangeReference = new RegExp(/^\s*('.+'!|[^']+!)?/.source +
|
|
5396
|
-
"(" +
|
|
5397
|
-
[cellReference.source, fullRowXc.source, fullColXc.source].join("|") +
|
|
5398
|
-
")" +
|
|
5399
|
-
/$/.source, "i");
|
|
5400
|
-
/**
|
|
5401
|
-
* Return true if the given xc is the reference of a column (e.g. A or AC or Sheet1!A)
|
|
5402
|
-
*/
|
|
5403
|
-
function isColReference(xc) {
|
|
5404
|
-
return colReference.test(xc);
|
|
5405
|
-
}
|
|
5406
|
-
/**
|
|
5407
|
-
* Return true if the given xc is the reference of a column (e.g. 1 or Sheet1!1)
|
|
5408
|
-
*/
|
|
5409
|
-
function isRowReference(xc) {
|
|
5410
|
-
return rowReference.test(xc);
|
|
5411
|
-
}
|
|
5412
|
-
function isColHeader(str) {
|
|
5413
|
-
return colHeader.test(str);
|
|
5414
|
-
}
|
|
5415
|
-
function isRowHeader(str) {
|
|
5416
|
-
return rowHeader.test(str);
|
|
5417
|
-
}
|
|
5418
|
-
/**
|
|
5419
|
-
* Return true if the given xc is the reference of a single cell,
|
|
5420
|
-
* without any specified sheet (e.g. A1)
|
|
5421
|
-
*/
|
|
5422
|
-
function isSingleCellReference(xc) {
|
|
5423
|
-
return singleCellReference.test(xc);
|
|
5424
|
-
}
|
|
5425
|
-
function splitReference(ref) {
|
|
5426
|
-
if (!ref.includes("!")) {
|
|
5427
|
-
return { xc: ref };
|
|
5428
|
-
}
|
|
5429
|
-
const parts = ref.split("!");
|
|
5430
|
-
const xc = parts.pop();
|
|
5431
|
-
const sheetName = getUnquotedSheetName(parts.join("!")) || undefined;
|
|
5432
|
-
return { sheetName, xc };
|
|
5433
|
-
}
|
|
5434
|
-
/** Return a reference SheetName!xc from the given arguments */
|
|
5435
|
-
function getFullReference(sheetName, xc) {
|
|
5436
|
-
return sheetName !== undefined ? `${getCanonicalSymbolName(sheetName)}!${xc}` : xc;
|
|
5437
|
-
}
|
|
5438
|
-
|
|
5439
5400
|
/**
|
|
5440
5401
|
* Convert from a cartesian reference to a Zone
|
|
5441
5402
|
* The range boundaries will be kept in the same order as the
|
|
@@ -5453,63 +5414,55 @@ function getFullReference(sheetName, xc) {
|
|
|
5453
5414
|
*
|
|
5454
5415
|
*/
|
|
5455
5416
|
function toZoneWithoutBoundaryChanges(xc) {
|
|
5456
|
-
|
|
5457
|
-
|
|
5458
|
-
|
|
5459
|
-
if (
|
|
5460
|
-
|
|
5461
|
-
}
|
|
5462
|
-
|
|
5463
|
-
|
|
5464
|
-
if (xc.includes(":")) {
|
|
5465
|
-
[firstRangePart, secondRangePart] = xc.split(":");
|
|
5466
|
-
firstRangePart = firstRangePart.trim();
|
|
5467
|
-
secondRangePart = secondRangePart.trim();
|
|
5468
|
-
}
|
|
5469
|
-
else {
|
|
5470
|
-
firstRangePart = xc.trim();
|
|
5471
|
-
}
|
|
5417
|
+
const chars = new TokenizingChars(xc);
|
|
5418
|
+
consumeSpaces(chars);
|
|
5419
|
+
const sheetSeparatorIndex = xc.indexOf("!");
|
|
5420
|
+
if (sheetSeparatorIndex !== -1) {
|
|
5421
|
+
chars.advanceBy(sheetSeparatorIndex + 1);
|
|
5422
|
+
}
|
|
5423
|
+
const leftLetters = consumeLetters(chars);
|
|
5424
|
+
const leftNumbers = consumeDigits(chars);
|
|
5472
5425
|
let top, bottom, left, right;
|
|
5473
5426
|
let fullCol = false;
|
|
5474
5427
|
let fullRow = false;
|
|
5475
5428
|
let hasHeader = false;
|
|
5476
|
-
if (
|
|
5477
|
-
left = right =
|
|
5429
|
+
if (leftNumbers === -1) {
|
|
5430
|
+
left = right = leftLetters - 1;
|
|
5478
5431
|
top = bottom = 0;
|
|
5479
5432
|
fullCol = true;
|
|
5480
5433
|
}
|
|
5481
|
-
else if (
|
|
5482
|
-
top = bottom =
|
|
5434
|
+
else if (leftLetters === -1) {
|
|
5435
|
+
top = bottom = leftNumbers - 1;
|
|
5483
5436
|
left = right = 0;
|
|
5484
5437
|
fullRow = true;
|
|
5485
5438
|
}
|
|
5486
5439
|
else {
|
|
5487
|
-
|
|
5488
|
-
|
|
5489
|
-
top = bottom = c.row;
|
|
5440
|
+
left = right = leftLetters - 1;
|
|
5441
|
+
top = bottom = leftNumbers - 1;
|
|
5490
5442
|
hasHeader = true;
|
|
5491
5443
|
}
|
|
5492
|
-
|
|
5493
|
-
|
|
5494
|
-
|
|
5444
|
+
consumeSpaces(chars);
|
|
5445
|
+
if (chars.current === ":") {
|
|
5446
|
+
chars.advanceBy(1);
|
|
5447
|
+
consumeSpaces(chars);
|
|
5448
|
+
const rightLetters = consumeLetters(chars);
|
|
5449
|
+
const rightNumbers = consumeDigits(chars);
|
|
5450
|
+
if (rightNumbers === -1) {
|
|
5451
|
+
right = rightLetters - 1;
|
|
5495
5452
|
fullCol = true;
|
|
5496
5453
|
}
|
|
5497
|
-
else if (
|
|
5498
|
-
bottom =
|
|
5454
|
+
else if (rightLetters === -1) {
|
|
5455
|
+
bottom = rightNumbers - 1;
|
|
5499
5456
|
fullRow = true;
|
|
5500
5457
|
}
|
|
5501
5458
|
else {
|
|
5502
|
-
|
|
5503
|
-
|
|
5504
|
-
bottom = c.row;
|
|
5459
|
+
right = rightLetters - 1;
|
|
5460
|
+
bottom = rightNumbers - 1;
|
|
5505
5461
|
top = fullCol ? bottom : top;
|
|
5506
5462
|
left = fullRow ? right : left;
|
|
5507
5463
|
hasHeader = true;
|
|
5508
5464
|
}
|
|
5509
5465
|
}
|
|
5510
|
-
if (fullCol && fullRow) {
|
|
5511
|
-
throw new Error("Wrong zone xc. The zone cannot be at the same time a full column and a full row");
|
|
5512
|
-
}
|
|
5513
5466
|
const zone = {
|
|
5514
5467
|
top,
|
|
5515
5468
|
left,
|
|
@@ -5538,7 +5491,16 @@ function toZoneWithoutBoundaryChanges(xc) {
|
|
|
5538
5491
|
*/
|
|
5539
5492
|
function toUnboundedZone(xc) {
|
|
5540
5493
|
const zone = toZoneWithoutBoundaryChanges(xc);
|
|
5541
|
-
|
|
5494
|
+
const orderedZone = reorderZone(zone);
|
|
5495
|
+
const bottom = orderedZone.bottom;
|
|
5496
|
+
const right = orderedZone.right;
|
|
5497
|
+
if ((bottom !== undefined && bottom > MAX_ROW) || (right !== undefined && right > MAX_COL)) {
|
|
5498
|
+
throw new Error(`Range string out of bounds: ${xc}`); // limit the size of the zone for performance
|
|
5499
|
+
}
|
|
5500
|
+
if (bottom === undefined && right === undefined) {
|
|
5501
|
+
throw new Error("Wrong zone xc. The zone cannot be at the same time a full column and a full row");
|
|
5502
|
+
}
|
|
5503
|
+
return orderedZone;
|
|
5542
5504
|
}
|
|
5543
5505
|
/**
|
|
5544
5506
|
* Convert from a cartesian reference to a Zone.
|
|
@@ -6152,6 +6114,67 @@ function scrollDelay(value) {
|
|
|
6152
6114
|
return MIN_DELAY + (MAX_DELAY - MIN_DELAY) * Math.exp(-ACCELERATION * (value - 1));
|
|
6153
6115
|
}
|
|
6154
6116
|
|
|
6117
|
+
/** Reference of a cell (eg. A1, $B$5) */
|
|
6118
|
+
const cellReference = new RegExp(/\$?([A-Z]{1,3})\$?([0-9]{1,7})/, "i");
|
|
6119
|
+
// Same as above, but matches the exact string (nothing before or after)
|
|
6120
|
+
const singleCellReference = new RegExp(/^\$?([A-Z]{1,3})\$?([0-9]{1,7})$/, "i");
|
|
6121
|
+
/** Reference of a column header (eg. A, AB, $A) */
|
|
6122
|
+
const colHeader = new RegExp(/^\$?([A-Z]{1,3})+$/, "i");
|
|
6123
|
+
/** Reference of a row header (eg. 1, $1) */
|
|
6124
|
+
const rowHeader = new RegExp(/^\$?([0-9]{1,7})+$/, "i");
|
|
6125
|
+
/** Reference of a column (eg. A, $CA, Sheet1!B) */
|
|
6126
|
+
const colReference = new RegExp(/^\s*('.+'!|[^']+!)?\$?([A-Z]{1,3})$/, "i");
|
|
6127
|
+
/** Reference of a row (eg. 1, 59, Sheet1!9) */
|
|
6128
|
+
const rowReference = new RegExp(/^\s*('.+'!|[^']+!)?\$?([0-9]{1,7})$/, "i");
|
|
6129
|
+
/** Reference of a normal range or a full row range (eg. A1:B1, 1:$5, $A2:5) */
|
|
6130
|
+
const fullRowXc = /(\$?[A-Z]{1,3})?\$?[0-9]{1,7}\s*:\s*(\$?[A-Z]{1,3})?\$?[0-9]{1,7}\s*/i;
|
|
6131
|
+
/** Reference of a normal range or a column row range (eg. A1:B1, A:$B, $A1:C) */
|
|
6132
|
+
const fullColXc = /\$?[A-Z]{1,3}(\$?[0-9]{1,7})?\s*:\s*\$?[A-Z]{1,3}(\$?[0-9]{1,7})?\s*/i;
|
|
6133
|
+
/** Reference of a cell or a range, it can be a bounded range, a full row or a full column */
|
|
6134
|
+
const rangeReference = new RegExp(/^\s*('.+'!|[^']+!)?/.source +
|
|
6135
|
+
"(" +
|
|
6136
|
+
[cellReference.source, fullRowXc.source, fullColXc.source].join("|") +
|
|
6137
|
+
")" +
|
|
6138
|
+
/$/.source, "i");
|
|
6139
|
+
/**
|
|
6140
|
+
* Return true if the given xc is the reference of a column (e.g. A or AC or Sheet1!A)
|
|
6141
|
+
*/
|
|
6142
|
+
function isColReference(xc) {
|
|
6143
|
+
return colReference.test(xc);
|
|
6144
|
+
}
|
|
6145
|
+
/**
|
|
6146
|
+
* Return true if the given xc is the reference of a column (e.g. 1 or Sheet1!1)
|
|
6147
|
+
*/
|
|
6148
|
+
function isRowReference(xc) {
|
|
6149
|
+
return rowReference.test(xc);
|
|
6150
|
+
}
|
|
6151
|
+
function isColHeader(str) {
|
|
6152
|
+
return colHeader.test(str);
|
|
6153
|
+
}
|
|
6154
|
+
function isRowHeader(str) {
|
|
6155
|
+
return rowHeader.test(str);
|
|
6156
|
+
}
|
|
6157
|
+
/**
|
|
6158
|
+
* Return true if the given xc is the reference of a single cell,
|
|
6159
|
+
* without any specified sheet (e.g. A1)
|
|
6160
|
+
*/
|
|
6161
|
+
function isSingleCellReference(xc) {
|
|
6162
|
+
return singleCellReference.test(xc);
|
|
6163
|
+
}
|
|
6164
|
+
function splitReference(ref) {
|
|
6165
|
+
if (!ref.includes("!")) {
|
|
6166
|
+
return { xc: ref };
|
|
6167
|
+
}
|
|
6168
|
+
const parts = ref.split("!");
|
|
6169
|
+
const xc = parts.pop();
|
|
6170
|
+
const sheetName = getUnquotedSheetName(parts.join("!")) || undefined;
|
|
6171
|
+
return { sheetName, xc };
|
|
6172
|
+
}
|
|
6173
|
+
/** Return a reference SheetName!xc from the given arguments */
|
|
6174
|
+
function getFullReference(sheetName, xc) {
|
|
6175
|
+
return sheetName !== undefined ? `${getCanonicalSymbolName(sheetName)}!${xc}` : xc;
|
|
6176
|
+
}
|
|
6177
|
+
|
|
6155
6178
|
function createDefaultRows(rowNumber) {
|
|
6156
6179
|
const rows = [];
|
|
6157
6180
|
for (let i = 0; i < rowNumber; i++) {
|
|
@@ -6848,9 +6871,6 @@ function getFontSizeMatchingWidth(lineWidth, maxFontSize, getTextWidth, precisio
|
|
|
6848
6871
|
}
|
|
6849
6872
|
return fontSize;
|
|
6850
6873
|
}
|
|
6851
|
-
function computeIconWidth(style) {
|
|
6852
|
-
return computeTextFontSizeInPixels(style) + 2 * MIN_CF_ICON_MARGIN;
|
|
6853
|
-
}
|
|
6854
6874
|
/** Transform a string to lower case. If the string is undefined, return an empty string */
|
|
6855
6875
|
function toLowerCase(str) {
|
|
6856
6876
|
return str ? str.toLowerCase() : "";
|
|
@@ -8084,208 +8104,6 @@ function getMovingAverageValues(dataset, labels, windowSize = DEFAULT_WINDOW_SIZ
|
|
|
8084
8104
|
return values;
|
|
8085
8105
|
}
|
|
8086
8106
|
|
|
8087
|
-
const PREVIOUS_VALUE = "(previous)";
|
|
8088
|
-
const NEXT_VALUE = "(next)";
|
|
8089
|
-
function getDomainOfParentRow(pivot, domain) {
|
|
8090
|
-
const { colDomain, rowDomain } = domainToColRowDomain(pivot, domain);
|
|
8091
|
-
return [...colDomain, ...rowDomain.slice(0, rowDomain.length - 1)];
|
|
8092
|
-
}
|
|
8093
|
-
function getDomainOfParentCol(pivot, domain) {
|
|
8094
|
-
const { colDomain, rowDomain } = domainToColRowDomain(pivot, domain);
|
|
8095
|
-
return [...colDomain.slice(0, colDomain.length - 1), ...rowDomain];
|
|
8096
|
-
}
|
|
8097
|
-
/**
|
|
8098
|
-
* Split a pivot domain into the part related to the rows of the pivot, and the part related to the columns.
|
|
8099
|
-
*/
|
|
8100
|
-
function domainToColRowDomain(pivot, domain) {
|
|
8101
|
-
const rowFields = pivot.definition.rows.map((c) => c.nameWithGranularity);
|
|
8102
|
-
const rowDomain = domain.filter((node) => rowFields.includes(node.field));
|
|
8103
|
-
const columnFields = pivot.definition.columns.map((c) => c.nameWithGranularity);
|
|
8104
|
-
const colDomain = domain.filter((node) => columnFields.includes(node.field));
|
|
8105
|
-
return { colDomain, rowDomain };
|
|
8106
|
-
}
|
|
8107
|
-
function getDimensionDomain(pivot, dimension, domain) {
|
|
8108
|
-
return dimension === "column"
|
|
8109
|
-
? domainToColRowDomain(pivot, domain).colDomain
|
|
8110
|
-
: domainToColRowDomain(pivot, domain).rowDomain;
|
|
8111
|
-
}
|
|
8112
|
-
function getFieldValueInDomain(fieldNameWithGranularity, domain) {
|
|
8113
|
-
const node = domain.find((n) => n.field === fieldNameWithGranularity);
|
|
8114
|
-
return node?.value;
|
|
8115
|
-
}
|
|
8116
|
-
function isDomainIsInPivot(pivot, domain) {
|
|
8117
|
-
const { rowDomain, colDomain } = domainToColRowDomain(pivot, domain);
|
|
8118
|
-
return (checkIfDomainInInTree(rowDomain, pivot.getTableStructure().getRowTree()) &&
|
|
8119
|
-
checkIfDomainInInTree(colDomain, pivot.getTableStructure().getColTree()));
|
|
8120
|
-
}
|
|
8121
|
-
function checkIfDomainInInTree(domain, tree) {
|
|
8122
|
-
return walkDomainTree(domain, tree) !== undefined;
|
|
8123
|
-
}
|
|
8124
|
-
/**
|
|
8125
|
-
* Given a tree of the col/rows of a pivot, and a domain related to those col/rows, return the node of the tree
|
|
8126
|
-
* corresponding to the domain.
|
|
8127
|
-
*
|
|
8128
|
-
* @param domain The domain to find in the tree
|
|
8129
|
-
* @param tree The tree to search in7
|
|
8130
|
-
* @param stopAtField If provided, the search will stop at the field with this name
|
|
8131
|
-
*/
|
|
8132
|
-
function walkDomainTree(domain, tree, stopAtField) {
|
|
8133
|
-
let currentTreeNode = tree;
|
|
8134
|
-
for (const node of domain) {
|
|
8135
|
-
const child = currentTreeNode.find((n) => n.value === node.value);
|
|
8136
|
-
if (!child) {
|
|
8137
|
-
return undefined;
|
|
8138
|
-
}
|
|
8139
|
-
if (child.field === stopAtField) {
|
|
8140
|
-
return currentTreeNode;
|
|
8141
|
-
}
|
|
8142
|
-
currentTreeNode = child.children;
|
|
8143
|
-
}
|
|
8144
|
-
return currentTreeNode;
|
|
8145
|
-
}
|
|
8146
|
-
/**
|
|
8147
|
-
* Get the domain parent of the given domain with the field `parentFieldName` as leaf of the domain.
|
|
8148
|
-
*
|
|
8149
|
-
* In practice, if the `parentFieldName` is a row in the pivot, the helper will return a domain with the same column
|
|
8150
|
-
* domain, and with a row domain all groupBys children to `parentFieldName` removed.
|
|
8151
|
-
*/
|
|
8152
|
-
function getFieldParentDomain(pivot, parentFieldName, domain) {
|
|
8153
|
-
let { rowDomain, colDomain } = domainToColRowDomain(pivot, domain);
|
|
8154
|
-
const dimension = getFieldDimensionType(pivot, parentFieldName);
|
|
8155
|
-
if (dimension === "row") {
|
|
8156
|
-
const index = rowDomain.findIndex((node) => node.field === parentFieldName);
|
|
8157
|
-
if (index === -1) {
|
|
8158
|
-
return domain;
|
|
8159
|
-
}
|
|
8160
|
-
rowDomain = rowDomain.slice(0, index + 1);
|
|
8161
|
-
}
|
|
8162
|
-
else {
|
|
8163
|
-
const index = colDomain.findIndex((node) => node.field === parentFieldName);
|
|
8164
|
-
if (index === -1) {
|
|
8165
|
-
return domain;
|
|
8166
|
-
}
|
|
8167
|
-
colDomain = colDomain.slice(0, index + 1);
|
|
8168
|
-
}
|
|
8169
|
-
return [...rowDomain, ...colDomain];
|
|
8170
|
-
}
|
|
8171
|
-
/**
|
|
8172
|
-
* Replace in the domain the value of the field `fieldNameWithGranularity` with the given `value`
|
|
8173
|
-
*/
|
|
8174
|
-
function replaceFieldValueInDomain(domain, fieldNameWithGranularity, value) {
|
|
8175
|
-
domain = deepCopy(domain);
|
|
8176
|
-
const node = domain.find((n) => n.field === fieldNameWithGranularity);
|
|
8177
|
-
if (!node) {
|
|
8178
|
-
return domain;
|
|
8179
|
-
}
|
|
8180
|
-
node.value = value;
|
|
8181
|
-
return domain;
|
|
8182
|
-
}
|
|
8183
|
-
function isFieldInDomain(nameWithGranularity, domain) {
|
|
8184
|
-
return domain.some((node) => node.field === nameWithGranularity);
|
|
8185
|
-
}
|
|
8186
|
-
/**
|
|
8187
|
-
* Check if the field is in the rows or columns of the pivot
|
|
8188
|
-
*/
|
|
8189
|
-
function getFieldDimensionType(pivot, nameWithGranularity) {
|
|
8190
|
-
const rowFields = pivot.definition.rows.map((c) => c.nameWithGranularity);
|
|
8191
|
-
if (rowFields.includes(nameWithGranularity)) {
|
|
8192
|
-
return "row";
|
|
8193
|
-
}
|
|
8194
|
-
const columnFields = pivot.definition.columns.map((c) => c.nameWithGranularity);
|
|
8195
|
-
if (columnFields.includes(nameWithGranularity)) {
|
|
8196
|
-
return "column";
|
|
8197
|
-
}
|
|
8198
|
-
throw new Error(`Field ${nameWithGranularity} not found in pivot`);
|
|
8199
|
-
}
|
|
8200
|
-
/**
|
|
8201
|
-
* Replace in the given domain the value of the field `fieldNameWithGranularity` with the previous or next value.
|
|
8202
|
-
*/
|
|
8203
|
-
function getPreviousOrNextValueDomain(pivot, domain, fieldNameWithGranularity, direction) {
|
|
8204
|
-
const dimension = getFieldDimensionType(pivot, fieldNameWithGranularity);
|
|
8205
|
-
const tree = dimension === "row"
|
|
8206
|
-
? pivot.getTableStructure().getRowTree()
|
|
8207
|
-
: pivot.getTableStructure().getColTree();
|
|
8208
|
-
const dimDomain = getDimensionDomain(pivot, dimension, domain);
|
|
8209
|
-
const currentTreeNode = walkDomainTree(dimDomain, tree, fieldNameWithGranularity);
|
|
8210
|
-
const values = currentTreeNode?.map((n) => n.value) ?? [];
|
|
8211
|
-
const value = getFieldValueInDomain(fieldNameWithGranularity, domain);
|
|
8212
|
-
if (value === undefined) {
|
|
8213
|
-
return undefined;
|
|
8214
|
-
}
|
|
8215
|
-
const valueIndex = values.indexOf(value);
|
|
8216
|
-
if (value === undefined || valueIndex === -1) {
|
|
8217
|
-
return undefined;
|
|
8218
|
-
}
|
|
8219
|
-
const offset = direction === PREVIOUS_VALUE ? -1 : 1;
|
|
8220
|
-
const newIndex = clip(valueIndex + offset, 0, values.length - 1);
|
|
8221
|
-
return replaceFieldValueInDomain(domain, fieldNameWithGranularity, values[newIndex]);
|
|
8222
|
-
}
|
|
8223
|
-
function domainToString(domain) {
|
|
8224
|
-
return domain ? domain.map(domainNodeToString).join(", ") : "";
|
|
8225
|
-
}
|
|
8226
|
-
function domainNodeToString(domainNode) {
|
|
8227
|
-
return domainNode ? `${domainNode.field}=${domainNode.value}` : "";
|
|
8228
|
-
}
|
|
8229
|
-
/**
|
|
8230
|
-
*
|
|
8231
|
-
* For the ranking, the pivot cell values of a column (or row) at the same depth are grouped together before being sorted
|
|
8232
|
-
* and ranked.
|
|
8233
|
-
*
|
|
8234
|
-
* The grouping of a pivot cell is done with both the value of the domain nodes that are parent of the field
|
|
8235
|
-
* `fieldNameWithGranularity` and the value of the last node of the domain of the pivot cell, if it's not the field
|
|
8236
|
-
* `fieldNameWithGranularity`.
|
|
8237
|
-
*
|
|
8238
|
-
* For example, let's take a pivot grouped by (Date:year, Stage, User, Product), where we want to rank by "Stage" field.
|
|
8239
|
-
* The domain nodes parents of the "Stage" are [Date:year]. The pivot cell with domain:
|
|
8240
|
-
* - [Date:year=2021] is not ranked because it does not contain the "Stage" field
|
|
8241
|
-
* - [Date:year=2021, Stage=Lead] is grouped with the cells [Date:year=2021, Stage=*, User=None, Product=None],
|
|
8242
|
-
* and then ranked within the group
|
|
8243
|
-
* - [Date:year=2021, Stage=Lead, User=Bob] is grouped with the cells [Date:year=2021, Stage=*, User=Bob, Product=None],
|
|
8244
|
-
* and then ranked within the group
|
|
8245
|
-
* - [Date:year=2021, Stage=Lead, User=Bob, Product=Table] is grouped with the cells [Date:year=2021, Stage=*, User=*, Product=Table],
|
|
8246
|
-
* and then ranked within the group
|
|
8247
|
-
*
|
|
8248
|
-
* If we rank the pivot on "User" instead, the parent domain becomes [Date:year, Sage] .The cell with domain:
|
|
8249
|
-
* - [Date:year=2021] is not ranked because it does not contain the "Stage" field
|
|
8250
|
-
* - [Date:year=2021, Stage=Lead] is not ranked because it does not contain the "User" field
|
|
8251
|
-
* - [Date:year=2021, Stage=Lead, User=Bob] is grouped with the cells [Date:year=2021, Stage=Lead, User=Bob, Product=None],
|
|
8252
|
-
* and then ranked within the group
|
|
8253
|
-
* - [Date:year=2021, Stage=Lead, User=Bob, Product=Table] is grouped with the cells with [Date:year=2021, Stage=Lead, User=*, Product=Table],
|
|
8254
|
-
* and then ranked within the group
|
|
8255
|
-
*
|
|
8256
|
-
*/
|
|
8257
|
-
function getRankingDomainKey(domain, fieldNameWithGranularity) {
|
|
8258
|
-
const index = domain.findIndex((node) => node.field === fieldNameWithGranularity);
|
|
8259
|
-
if (index === -1) {
|
|
8260
|
-
return "";
|
|
8261
|
-
}
|
|
8262
|
-
const parent = domain.slice(0, index);
|
|
8263
|
-
const lastNode = domain.at(-1);
|
|
8264
|
-
return domainToString(lastNode.field === fieldNameWithGranularity ? parent : [...parent, lastNode]);
|
|
8265
|
-
}
|
|
8266
|
-
/**
|
|
8267
|
-
* The running total domain is the domain without the field `fieldNameWithGranularity`, ie. we do the running total of
|
|
8268
|
-
* all the pivot cells of the column that have any value for the field `fieldNameWithGranularity` and the same value for
|
|
8269
|
-
* the other fields.
|
|
8270
|
-
*/
|
|
8271
|
-
function getRunningTotalDomainKey(domain, fieldNameWithGranularity) {
|
|
8272
|
-
const index = domain.findIndex((node) => node.field === fieldNameWithGranularity);
|
|
8273
|
-
if (index === -1) {
|
|
8274
|
-
return "";
|
|
8275
|
-
}
|
|
8276
|
-
return domainToString([...domain.slice(0, index), ...domain.slice(index + 1)]);
|
|
8277
|
-
}
|
|
8278
|
-
function sortPivotTree(tree, baseDomain, sortFn) {
|
|
8279
|
-
const sortedTree = [...tree];
|
|
8280
|
-
const domain = [...baseDomain];
|
|
8281
|
-
sortedTree.sort((node1, node2) => sortFn([...domain, node1], [...domain, node2]));
|
|
8282
|
-
for (const node of tree) {
|
|
8283
|
-
const children = sortPivotTree(node.children, [...domain, node], sortFn);
|
|
8284
|
-
node.children = children;
|
|
8285
|
-
}
|
|
8286
|
-
return sortedTree;
|
|
8287
|
-
}
|
|
8288
|
-
|
|
8289
8107
|
const pivotTimeAdapterRegistry = new Registry();
|
|
8290
8108
|
function pivotTimeAdapter(granularity) {
|
|
8291
8109
|
return pivotTimeAdapterRegistry.get(granularity);
|
|
@@ -8796,23 +8614,11 @@ pivotToFunctionValueRegistry
|
|
|
8796
8614
|
function getFieldDisplayName(field) {
|
|
8797
8615
|
return field.displayName + (field.granularity ? ` (${ALL_PERIODS[field.granularity]})` : "");
|
|
8798
8616
|
}
|
|
8799
|
-
function
|
|
8800
|
-
|
|
8801
|
-
if (rowDomain.length === 0 && colDomain.length === 0) {
|
|
8617
|
+
function addAlignFormatToPivotHeader(domain, functionResult) {
|
|
8618
|
+
if (domain.length === 0) {
|
|
8802
8619
|
return functionResult;
|
|
8803
8620
|
}
|
|
8804
|
-
|
|
8805
|
-
return {
|
|
8806
|
-
...functionResult,
|
|
8807
|
-
format: (functionResult.format || "@") + "* ",
|
|
8808
|
-
};
|
|
8809
|
-
}
|
|
8810
|
-
const indent = rowDomain.length - 1;
|
|
8811
|
-
const format = functionResult.format || "@";
|
|
8812
|
-
return {
|
|
8813
|
-
...functionResult,
|
|
8814
|
-
format: `${" ".repeat(indent)}${format}* `,
|
|
8815
|
-
};
|
|
8621
|
+
return { ...functionResult, format: (functionResult.format || "@") + "* " };
|
|
8816
8622
|
}
|
|
8817
8623
|
function isSortedColumnValid(sortedColumn, pivot) {
|
|
8818
8624
|
try {
|
|
@@ -9843,6 +9649,13 @@ class DependencyContainer extends EventBus {
|
|
|
9843
9649
|
resetStores() {
|
|
9844
9650
|
this.dependencies.clear();
|
|
9845
9651
|
}
|
|
9652
|
+
dispose() {
|
|
9653
|
+
for (const instance of this.dependencies.values()) {
|
|
9654
|
+
if ("dispose" in instance && typeof instance.dispose === "function") {
|
|
9655
|
+
instance.dispose();
|
|
9656
|
+
}
|
|
9657
|
+
}
|
|
9658
|
+
}
|
|
9846
9659
|
}
|
|
9847
9660
|
class StoreFactory {
|
|
9848
9661
|
get;
|
|
@@ -9921,6 +9734,7 @@ function useStoreProvider() {
|
|
|
9921
9734
|
return proxifyStoreMutation(store, () => container.trigger("store-updated"));
|
|
9922
9735
|
},
|
|
9923
9736
|
});
|
|
9737
|
+
owl.onWillUnmount(() => container.dispose());
|
|
9924
9738
|
return container;
|
|
9925
9739
|
}
|
|
9926
9740
|
/**
|
|
@@ -10274,6 +10088,19 @@ function unregisterChartJsExtensions() {
|
|
|
10274
10088
|
}
|
|
10275
10089
|
}
|
|
10276
10090
|
|
|
10091
|
+
class ChartAnimationStore extends SpreadsheetStore {
|
|
10092
|
+
mutators = ["disableAnimationForChart", "enableAnimationForChart"];
|
|
10093
|
+
animationPlayed = {};
|
|
10094
|
+
disableAnimationForChart(chartId, chartType) {
|
|
10095
|
+
this.animationPlayed[chartId] = chartType;
|
|
10096
|
+
return "noStateChange";
|
|
10097
|
+
}
|
|
10098
|
+
enableAnimationForChart(chartId) {
|
|
10099
|
+
this.animationPlayed[chartId] = undefined;
|
|
10100
|
+
return "noStateChange";
|
|
10101
|
+
}
|
|
10102
|
+
}
|
|
10103
|
+
|
|
10277
10104
|
function getFunnelChartController() {
|
|
10278
10105
|
return class FunnelChartController extends window.Chart.BarController {
|
|
10279
10106
|
static id = "funnel";
|
|
@@ -11927,10 +11754,12 @@ class ChartJsComponent extends owl.Component {
|
|
|
11927
11754
|
static template = "o-spreadsheet-ChartJsComponent";
|
|
11928
11755
|
static props = {
|
|
11929
11756
|
figureUI: Object,
|
|
11757
|
+
isFullScreen: { type: Boolean, optional: true },
|
|
11930
11758
|
};
|
|
11931
11759
|
canvas = owl.useRef("graphContainer");
|
|
11932
11760
|
chart;
|
|
11933
11761
|
currentRuntime;
|
|
11762
|
+
animationStore;
|
|
11934
11763
|
currentDevicePixelRatio = window.devicePixelRatio;
|
|
11935
11764
|
get background() {
|
|
11936
11765
|
return this.chartRuntime.background;
|
|
@@ -11946,6 +11775,9 @@ class ChartJsComponent extends owl.Component {
|
|
|
11946
11775
|
return runtime;
|
|
11947
11776
|
}
|
|
11948
11777
|
setup() {
|
|
11778
|
+
if (this.env.model.getters.isDashboard()) {
|
|
11779
|
+
this.animationStore = useStore(ChartAnimationStore);
|
|
11780
|
+
}
|
|
11949
11781
|
owl.onMounted(() => {
|
|
11950
11782
|
const runtime = this.chartRuntime;
|
|
11951
11783
|
this.currentRuntime = runtime;
|
|
@@ -11972,11 +11804,25 @@ class ChartJsComponent extends owl.Component {
|
|
|
11972
11804
|
});
|
|
11973
11805
|
}
|
|
11974
11806
|
createChart(chartData) {
|
|
11807
|
+
if (this.env.model.getters.isDashboard() && this.animationStore) {
|
|
11808
|
+
const chartType = this.env.model.getters.getChart(this.props.figureUI.id)?.type;
|
|
11809
|
+
if (chartType && this.animationStore.animationPlayed[this.animationFigureId] !== chartType) {
|
|
11810
|
+
chartData = this.enableAnimationInChartData(chartData);
|
|
11811
|
+
this.animationStore.disableAnimationForChart(this.animationFigureId, chartType);
|
|
11812
|
+
}
|
|
11813
|
+
}
|
|
11975
11814
|
const canvas = this.canvas.el;
|
|
11976
11815
|
const ctx = canvas.getContext("2d");
|
|
11977
11816
|
this.chart = new window.Chart(ctx, chartData);
|
|
11978
11817
|
}
|
|
11979
11818
|
updateChartJs(chartData) {
|
|
11819
|
+
if (this.env.model.getters.isDashboard()) {
|
|
11820
|
+
const chartType = this.env.model.getters.getChart(this.props.figureUI.id)?.type;
|
|
11821
|
+
if (chartType && this.hasChartDataChanged() && this.animationStore) {
|
|
11822
|
+
chartData = this.enableAnimationInChartData(chartData);
|
|
11823
|
+
this.animationStore.disableAnimationForChart(this.animationFigureId, chartType);
|
|
11824
|
+
}
|
|
11825
|
+
}
|
|
11980
11826
|
if (chartData.data && chartData.data.datasets) {
|
|
11981
11827
|
this.chart.data = chartData.data;
|
|
11982
11828
|
if (chartData.options?.plugins?.title) {
|
|
@@ -11989,6 +11835,20 @@ class ChartJsComponent extends owl.Component {
|
|
|
11989
11835
|
this.chart.config.options = chartData.options;
|
|
11990
11836
|
this.chart.update();
|
|
11991
11837
|
}
|
|
11838
|
+
hasChartDataChanged() {
|
|
11839
|
+
return !deepEquals(this.currentRuntime.chartJsConfig.data, this.chartRuntime.chartJsConfig.data);
|
|
11840
|
+
}
|
|
11841
|
+
enableAnimationInChartData(chartData) {
|
|
11842
|
+
return {
|
|
11843
|
+
...chartData,
|
|
11844
|
+
options: { ...chartData.options, animation: { animateRotate: true } },
|
|
11845
|
+
};
|
|
11846
|
+
}
|
|
11847
|
+
get animationFigureId() {
|
|
11848
|
+
return this.props.isFullScreen
|
|
11849
|
+
? this.props.figureUI.id + "-fullscreen"
|
|
11850
|
+
: this.props.figureUI.id;
|
|
11851
|
+
}
|
|
11992
11852
|
}
|
|
11993
11853
|
|
|
11994
11854
|
/**
|
|
@@ -12114,6 +11974,7 @@ const arrowDownPath = new window.Path2D("M8.6 4.8a.5.5 0 0 1 0 .75l-3.9 3.9a.5 .
|
|
|
12114
11974
|
const arrowUpPath = new window.Path2D("M8.7 5.5a.5.5 0 0 0 0-.75l-3.8-4a.5.5 0 0 0-.75 0l-3.8 4a.5.5 0 0 0 0 .75l.4.4a.5.5 0 0 0 .75 0l2.3-2.4v5.8c0 .25.25.5.5.5h.6c.25 0 .5-.25.5-.5v-5.8l2.2 2.4a.5.5 0 0 0 .75 0z");
|
|
12115
11975
|
let ScorecardChart$1 = class ScorecardChart extends AbstractChart {
|
|
12116
11976
|
keyValue;
|
|
11977
|
+
keyDescr;
|
|
12117
11978
|
baseline;
|
|
12118
11979
|
baselineMode;
|
|
12119
11980
|
baselineDescr;
|
|
@@ -12127,6 +11988,7 @@ let ScorecardChart$1 = class ScorecardChart extends AbstractChart {
|
|
|
12127
11988
|
constructor(definition, sheetId, getters) {
|
|
12128
11989
|
super(definition, sheetId, getters);
|
|
12129
11990
|
this.keyValue = createValidRange(getters, sheetId, definition.keyValue);
|
|
11991
|
+
this.keyDescr = definition.keyDescr;
|
|
12130
11992
|
this.baseline = createValidRange(getters, sheetId, definition.baseline);
|
|
12131
11993
|
this.baselineMode = definition.baselineMode;
|
|
12132
11994
|
this.baselineDescr = definition.baselineDescr;
|
|
@@ -12204,6 +12066,7 @@ let ScorecardChart$1 = class ScorecardChart extends AbstractChart {
|
|
|
12204
12066
|
keyValue: keyValue
|
|
12205
12067
|
? this.getters.getRangeString(keyValue, targetSheetId || this.sheetId)
|
|
12206
12068
|
: undefined,
|
|
12069
|
+
keyDescr: this.keyDescr,
|
|
12207
12070
|
humanize: this.humanize,
|
|
12208
12071
|
};
|
|
12209
12072
|
}
|
|
@@ -12227,7 +12090,7 @@ function drawScoreChart(structure, canvas) {
|
|
|
12227
12090
|
canvas.width = dpr * structure.canvas.width;
|
|
12228
12091
|
canvas.height = dpr * structure.canvas.height;
|
|
12229
12092
|
ctx.scale(dpr, dpr);
|
|
12230
|
-
const availableWidth = structure.canvas.width - CHART_PADDING
|
|
12093
|
+
const availableWidth = structure.canvas.width - CHART_PADDING;
|
|
12231
12094
|
ctx.fillStyle = structure.canvas.backgroundColor;
|
|
12232
12095
|
ctx.fillRect(0, 0, structure.canvas.width, structure.canvas.height);
|
|
12233
12096
|
if (structure.title) {
|
|
@@ -12263,18 +12126,22 @@ function drawScoreChart(structure, canvas) {
|
|
|
12263
12126
|
ctx.restore();
|
|
12264
12127
|
}
|
|
12265
12128
|
if (structure.baselineDescr) {
|
|
12266
|
-
const descr = structure.baselineDescr
|
|
12129
|
+
const descr = structure.baselineDescr;
|
|
12267
12130
|
ctx.font = descr.style.font;
|
|
12268
12131
|
ctx.fillStyle = descr.style.color;
|
|
12269
|
-
|
|
12270
|
-
ctx.fillText(clipTextWithEllipsis(ctx, description.text, availableWidth - description.position.x), description.position.x, description.position.y);
|
|
12271
|
-
}
|
|
12132
|
+
ctx.fillText(clipTextWithEllipsis(ctx, descr.text, availableWidth - descr.position.x), descr.position.x, descr.position.y);
|
|
12272
12133
|
}
|
|
12273
12134
|
if (structure.key) {
|
|
12274
12135
|
ctx.font = structure.key.style.font;
|
|
12275
12136
|
ctx.fillStyle = structure.key.style.color;
|
|
12276
12137
|
drawDecoratedText(ctx, clipTextWithEllipsis(ctx, structure.key.text, availableWidth - structure.key.position.x), structure.key.position, structure.key.style.underline, structure.key.style.strikethrough);
|
|
12277
12138
|
}
|
|
12139
|
+
if (structure.keyDescr) {
|
|
12140
|
+
const descr = structure.keyDescr;
|
|
12141
|
+
ctx.font = structure.keyDescr?.style.font ?? descr.style.font;
|
|
12142
|
+
ctx.fillStyle = descr.style.color;
|
|
12143
|
+
ctx.fillText(clipTextWithEllipsis(ctx, descr.text, availableWidth - descr.position.x), descr.position.x, descr.position.y);
|
|
12144
|
+
}
|
|
12278
12145
|
if (structure.progressBar) {
|
|
12279
12146
|
ctx.fillStyle = structure.progressBar.style.backgroundColor;
|
|
12280
12147
|
ctx.beginPath();
|
|
@@ -12329,28 +12196,41 @@ function createScorecardChartRuntime(chart, getters) {
|
|
|
12329
12196
|
text: chart.title.text ? _t(chart.title.text) : "",
|
|
12330
12197
|
},
|
|
12331
12198
|
keyValue: formattedKeyValue,
|
|
12199
|
+
keyDescr: chart.keyDescr?.text
|
|
12200
|
+
? _t(chart.keyDescr.text) // descriptions are extracted from .json files and they are translated at runtime here
|
|
12201
|
+
: "",
|
|
12332
12202
|
baselineDisplay,
|
|
12333
12203
|
baselineArrow: getBaselineArrowDirection(baselineCell, keyValueCell, chart.baselineMode),
|
|
12334
12204
|
baselineColor: getBaselineColor(baselineCell, chart.baselineMode, keyValueCell, chart.baselineColorUp, chart.baselineColorDown),
|
|
12335
|
-
baselineDescr: chart.baselineMode !== "progress" && chart.baselineDescr
|
|
12336
|
-
? _t(chart.baselineDescr) // descriptions are extracted from .json files and they are translated at runtime here
|
|
12205
|
+
baselineDescr: chart.baselineMode !== "progress" && chart.baselineDescr?.text
|
|
12206
|
+
? _t(chart.baselineDescr.text) // descriptions are extracted from .json files and they are translated at runtime here
|
|
12337
12207
|
: "",
|
|
12338
12208
|
fontColor,
|
|
12339
12209
|
background,
|
|
12340
|
-
baselineStyle:
|
|
12341
|
-
|
|
12342
|
-
|
|
12343
|
-
|
|
12344
|
-
|
|
12345
|
-
|
|
12346
|
-
|
|
12347
|
-
|
|
12348
|
-
|
|
12349
|
-
|
|
12350
|
-
|
|
12351
|
-
|
|
12352
|
-
|
|
12353
|
-
|
|
12210
|
+
baselineStyle: {
|
|
12211
|
+
...(chart.baselineMode !== "percentage" && chart.baselineMode !== "progress" && baseline
|
|
12212
|
+
? getters.getCellComputedStyle({
|
|
12213
|
+
sheetId: baseline.sheetId,
|
|
12214
|
+
col: baseline.zone.left,
|
|
12215
|
+
row: baseline.zone.top,
|
|
12216
|
+
})
|
|
12217
|
+
: undefined),
|
|
12218
|
+
fontSize: chart.baselineDescr?.fontSize,
|
|
12219
|
+
align: chart.baselineDescr?.align,
|
|
12220
|
+
},
|
|
12221
|
+
baselineDescrStyle: { textColor: chart.baselineDescr?.color, ...chart.baselineDescr },
|
|
12222
|
+
keyValueStyle: {
|
|
12223
|
+
...(chart.keyValue
|
|
12224
|
+
? getters.getCellComputedStyle({
|
|
12225
|
+
sheetId: chart.keyValue.sheetId,
|
|
12226
|
+
col: chart.keyValue.zone.left,
|
|
12227
|
+
row: chart.keyValue.zone.top,
|
|
12228
|
+
})
|
|
12229
|
+
: undefined),
|
|
12230
|
+
fontSize: chart.keyDescr?.fontSize,
|
|
12231
|
+
align: chart.keyDescr?.align,
|
|
12232
|
+
},
|
|
12233
|
+
keyValueDescrStyle: { textColor: chart.keyDescr?.color, ...chart.keyDescr },
|
|
12354
12234
|
progressBar: chart.baselineMode === "progress"
|
|
12355
12235
|
? {
|
|
12356
12236
|
value: baselineValue,
|
|
@@ -12361,11 +12241,7 @@ function createScorecardChartRuntime(chart, getters) {
|
|
|
12361
12241
|
}
|
|
12362
12242
|
|
|
12363
12243
|
/* Padding at the border of the chart */
|
|
12364
|
-
const CHART_PADDING = 10;
|
|
12365
12244
|
const BOTTOM_PADDING_RATIO = 0.05;
|
|
12366
|
-
/* Maximum font sizes of each element */
|
|
12367
|
-
const KEY_VALUE_FONT_SIZE = 32;
|
|
12368
|
-
const BASELINE_MAX_FONT_SIZE = 16;
|
|
12369
12245
|
function formatBaselineDescr(baselineDescr, baseline) {
|
|
12370
12246
|
const _baselineDescr = baselineDescr || "";
|
|
12371
12247
|
return baseline && _baselineDescr ? " " + _baselineDescr : _baselineDescr;
|
|
@@ -12415,7 +12291,7 @@ class ScorecardChartConfigBuilder {
|
|
|
12415
12291
|
style: style.title,
|
|
12416
12292
|
position: {
|
|
12417
12293
|
x,
|
|
12418
|
-
y:
|
|
12294
|
+
y: CHART_PADDING_BOTTOM + titleHeight / 2,
|
|
12419
12295
|
},
|
|
12420
12296
|
};
|
|
12421
12297
|
}
|
|
@@ -12425,42 +12301,49 @@ class ScorecardChartConfigBuilder {
|
|
|
12425
12301
|
baselineHeight = this.getTextDimensions(this.baselineDescr, style.baselineDescr.font).height;
|
|
12426
12302
|
}
|
|
12427
12303
|
const baselineDescrWidth = this.getTextDimensions(this.baselineDescr, style.baselineDescr.font).width;
|
|
12428
|
-
|
|
12429
|
-
|
|
12430
|
-
|
|
12431
|
-
|
|
12432
|
-
|
|
12433
|
-
|
|
12434
|
-
|
|
12435
|
-
|
|
12436
|
-
|
|
12437
|
-
|
|
12438
|
-
|
|
12439
|
-
if (
|
|
12440
|
-
structure.baseline
|
|
12441
|
-
|
|
12442
|
-
|
|
12443
|
-
structure.baselineArrow = {
|
|
12444
|
-
direction: this.baselineArrow,
|
|
12445
|
-
style: style.baselineArrow,
|
|
12304
|
+
let baselineX;
|
|
12305
|
+
switch (this.runtime.baselineStyle?.align) {
|
|
12306
|
+
case "right":
|
|
12307
|
+
baselineX = this.width - CHART_PADDING - baselineDescrWidth - baselineWidth;
|
|
12308
|
+
break;
|
|
12309
|
+
case "left":
|
|
12310
|
+
baselineX = CHART_PADDING + baselineArrowSize;
|
|
12311
|
+
break;
|
|
12312
|
+
default:
|
|
12313
|
+
baselineX = (this.width - baselineWidth - baselineDescrWidth + baselineArrowSize) / 2;
|
|
12314
|
+
}
|
|
12315
|
+
if (this.baseline) {
|
|
12316
|
+
structure.baseline = {
|
|
12317
|
+
text: this.baseline,
|
|
12318
|
+
style: style.baselineValue,
|
|
12446
12319
|
position: {
|
|
12447
|
-
x:
|
|
12448
|
-
y:
|
|
12320
|
+
x: baselineX,
|
|
12321
|
+
y: this.keyValue
|
|
12322
|
+
? this.height * (1 - BOTTOM_PADDING_RATIO * (this.runtime.progressBar ? 1 : 2))
|
|
12323
|
+
: this.height - (this.height - titleHeight - baselineHeight) / 2 - CHART_PADDING_BOTTOM,
|
|
12449
12324
|
},
|
|
12450
12325
|
};
|
|
12326
|
+
if (style.baselineArrow && !this.runtime.progressBar) {
|
|
12327
|
+
structure.baselineArrow = {
|
|
12328
|
+
direction: this.baselineArrow,
|
|
12329
|
+
style: style.baselineArrow,
|
|
12330
|
+
position: {
|
|
12331
|
+
x: structure.baseline.position.x - baselineArrowSize,
|
|
12332
|
+
y: structure.baseline.position.y - (baselineHeight + baselineArrowSize) / 2,
|
|
12333
|
+
},
|
|
12334
|
+
};
|
|
12335
|
+
}
|
|
12451
12336
|
}
|
|
12452
|
-
if (this.baselineDescr) {
|
|
12337
|
+
if (structure.baseline && this.baselineDescr) {
|
|
12453
12338
|
const position = {
|
|
12454
12339
|
x: structure.baseline.position.x + baselineWidth,
|
|
12455
12340
|
y: structure.baseline.position.y,
|
|
12456
12341
|
};
|
|
12457
|
-
structure.baselineDescr =
|
|
12458
|
-
|
|
12459
|
-
|
|
12460
|
-
|
|
12461
|
-
|
|
12462
|
-
},
|
|
12463
|
-
];
|
|
12342
|
+
structure.baselineDescr = {
|
|
12343
|
+
text: this.baselineDescr,
|
|
12344
|
+
style: style.baselineDescr,
|
|
12345
|
+
position,
|
|
12346
|
+
};
|
|
12464
12347
|
}
|
|
12465
12348
|
let progressBarHeight = 0;
|
|
12466
12349
|
if (this.runtime.progressBar) {
|
|
@@ -12482,18 +12365,41 @@ class ScorecardChartConfigBuilder {
|
|
|
12482
12365
|
};
|
|
12483
12366
|
}
|
|
12484
12367
|
const { width: keyWidth, height: keyHeight } = this.getFullTextDimensions(this.keyValue, style.keyValue.font);
|
|
12368
|
+
const keyDescrWidth = this.getTextDimensions(this.keyDescr, style.keyDescr.font).width;
|
|
12369
|
+
let keyX;
|
|
12370
|
+
switch (this.runtime.keyValueStyle?.align) {
|
|
12371
|
+
case "right":
|
|
12372
|
+
keyX = this.width - CHART_PADDING - keyDescrWidth - keyWidth;
|
|
12373
|
+
break;
|
|
12374
|
+
case "left":
|
|
12375
|
+
keyX = CHART_PADDING;
|
|
12376
|
+
break;
|
|
12377
|
+
default:
|
|
12378
|
+
keyX = (this.width - keyWidth - keyDescrWidth) / 2;
|
|
12379
|
+
}
|
|
12485
12380
|
if (this.keyValue) {
|
|
12486
12381
|
structure.key = {
|
|
12487
12382
|
text: this.keyValue,
|
|
12488
12383
|
style: style.keyValue,
|
|
12489
12384
|
position: {
|
|
12490
|
-
x: Math.max(CHART_PADDING,
|
|
12385
|
+
x: Math.max(CHART_PADDING, keyX),
|
|
12491
12386
|
y: this.height * (0.5 - BOTTOM_PADDING_RATIO * 2) +
|
|
12492
|
-
|
|
12387
|
+
CHART_PADDING_BOTTOM / 2 +
|
|
12493
12388
|
(titleHeight + keyHeight / 2) / 2,
|
|
12494
12389
|
},
|
|
12495
12390
|
};
|
|
12496
12391
|
}
|
|
12392
|
+
if (structure.key && this.keyDescr) {
|
|
12393
|
+
const position = {
|
|
12394
|
+
x: structure.key.position.x + keyWidth,
|
|
12395
|
+
y: structure.key.position.y,
|
|
12396
|
+
};
|
|
12397
|
+
structure.keyDescr = {
|
|
12398
|
+
text: this.keyDescr,
|
|
12399
|
+
style: style.keyDescr,
|
|
12400
|
+
position,
|
|
12401
|
+
};
|
|
12402
|
+
}
|
|
12497
12403
|
return structure;
|
|
12498
12404
|
}
|
|
12499
12405
|
get title() {
|
|
@@ -12502,6 +12408,9 @@ class ScorecardChartConfigBuilder {
|
|
|
12502
12408
|
get keyValue() {
|
|
12503
12409
|
return this.runtime.keyValue;
|
|
12504
12410
|
}
|
|
12411
|
+
get keyDescr() {
|
|
12412
|
+
return formatBaselineDescr(this.runtime.keyDescr, this.keyValue);
|
|
12413
|
+
}
|
|
12505
12414
|
get baseline() {
|
|
12506
12415
|
return this.runtime.baselineDisplay;
|
|
12507
12416
|
}
|
|
@@ -12534,7 +12443,9 @@ class ScorecardChartConfigBuilder {
|
|
|
12534
12443
|
};
|
|
12535
12444
|
}
|
|
12536
12445
|
getTextStyles() {
|
|
12537
|
-
|
|
12446
|
+
const keyValueFontSize = this.runtime.keyValueStyle?.fontSize ?? DEFAULT_SCORECARD_KEY_VALUE_FONT_SIZE;
|
|
12447
|
+
const keyValueDescrFontSize = Math.floor(0.9 * keyValueFontSize);
|
|
12448
|
+
let baselineValueFontSize = this.runtime.baselineStyle?.fontSize ?? DEFAULT_SCORECARD_BASELINE_FONT_SIZE;
|
|
12538
12449
|
const baselineDescrFontSize = Math.floor(0.9 * baselineValueFontSize);
|
|
12539
12450
|
if (this.runtime.progressBar) {
|
|
12540
12451
|
baselineValueFontSize /= 1.5;
|
|
@@ -12546,27 +12457,37 @@ class ScorecardChartConfigBuilder {
|
|
|
12546
12457
|
},
|
|
12547
12458
|
keyValue: {
|
|
12548
12459
|
color: this.runtime.keyValueStyle?.textColor || this.runtime.fontColor,
|
|
12549
|
-
font: getDefaultContextFont(
|
|
12460
|
+
font: getDefaultContextFont(keyValueFontSize, this.runtime.keyValueStyle?.bold, this.runtime.keyValueStyle?.italic),
|
|
12550
12461
|
strikethrough: this.runtime.keyValueStyle?.strikethrough,
|
|
12551
12462
|
underline: this.runtime.keyValueStyle?.underline,
|
|
12552
12463
|
},
|
|
12464
|
+
keyDescr: {
|
|
12465
|
+
color: this.runtime.keyValueDescrStyle?.textColor || this.runtime.fontColor,
|
|
12466
|
+
font: getDefaultContextFont(keyValueDescrFontSize, this.runtime.keyValueDescrStyle?.bold, this.runtime.keyValueDescrStyle?.italic),
|
|
12467
|
+
strikethrough: this.runtime.keyValueDescrStyle?.strikethrough,
|
|
12468
|
+
underline: this.runtime.keyValueDescrStyle?.underline,
|
|
12469
|
+
},
|
|
12553
12470
|
baselineValue: {
|
|
12554
12471
|
font: getDefaultContextFont(baselineValueFontSize, this.runtime.baselineStyle?.bold, this.runtime.baselineStyle?.italic),
|
|
12555
12472
|
strikethrough: this.runtime.baselineStyle?.strikethrough,
|
|
12556
12473
|
underline: this.runtime.baselineStyle?.underline,
|
|
12557
|
-
color: this.runtime.
|
|
12558
|
-
this.runtime.
|
|
12474
|
+
color: this.runtime.baselineColor ||
|
|
12475
|
+
this.runtime.baselineStyle?.textColor ||
|
|
12559
12476
|
this.secondaryFontColor,
|
|
12560
12477
|
},
|
|
12561
12478
|
baselineDescr: {
|
|
12562
|
-
font: getDefaultContextFont(baselineDescrFontSize),
|
|
12563
|
-
|
|
12479
|
+
font: getDefaultContextFont(baselineDescrFontSize, this.runtime.baselineDescrStyle?.bold, this.runtime.baselineDescrStyle?.italic),
|
|
12480
|
+
strikethrough: this.runtime.baselineDescrStyle?.strikethrough,
|
|
12481
|
+
underline: this.runtime.baselineDescrStyle?.underline,
|
|
12482
|
+
color: this.runtime.baselineDescrStyle?.textColor ?? this.secondaryFontColor,
|
|
12564
12483
|
},
|
|
12565
12484
|
baselineArrow: this.baselineArrow === "neutral" || this.runtime.progressBar
|
|
12566
12485
|
? undefined
|
|
12567
12486
|
: {
|
|
12568
12487
|
size: this.keyValue ? 0.8 * baselineValueFontSize : 0,
|
|
12569
|
-
color: this.runtime.baselineColor ||
|
|
12488
|
+
color: this.runtime.baselineColor ||
|
|
12489
|
+
this.runtime.baselineStyle?.textColor ||
|
|
12490
|
+
this.secondaryFontColor,
|
|
12570
12491
|
},
|
|
12571
12492
|
};
|
|
12572
12493
|
}
|
|
@@ -12960,14 +12881,14 @@ function getGaugeRenderingConfig(boundingRect, runtime, ctx) {
|
|
|
12960
12881
|
}
|
|
12961
12882
|
switch (runtime.title.align) {
|
|
12962
12883
|
case "right":
|
|
12963
|
-
x = boundingRect.width - titleWidth - CHART_PADDING
|
|
12884
|
+
x = boundingRect.width - titleWidth - CHART_PADDING;
|
|
12964
12885
|
break;
|
|
12965
12886
|
case "center":
|
|
12966
12887
|
x = (boundingRect.width - titleWidth) / 2;
|
|
12967
12888
|
break;
|
|
12968
12889
|
case "left":
|
|
12969
12890
|
default:
|
|
12970
|
-
x = CHART_PADDING
|
|
12891
|
+
x = CHART_PADDING;
|
|
12971
12892
|
break;
|
|
12972
12893
|
}
|
|
12973
12894
|
return {
|
|
@@ -14289,8 +14210,8 @@ function getTopPaddingForDashboard(definition, getters) {
|
|
|
14289
14210
|
function getChartLayout(definition, args) {
|
|
14290
14211
|
return {
|
|
14291
14212
|
padding: {
|
|
14292
|
-
left: CHART_PADDING
|
|
14293
|
-
right: CHART_PADDING
|
|
14213
|
+
left: CHART_PADDING,
|
|
14214
|
+
right: CHART_PADDING,
|
|
14294
14215
|
top: Math.max(CHART_PADDING_TOP, args.topPadding || 0),
|
|
14295
14216
|
bottom: CHART_PADDING_BOTTOM,
|
|
14296
14217
|
},
|
|
@@ -14841,11 +14762,11 @@ function getLegendMargin(definition) {
|
|
|
14841
14762
|
case "right":
|
|
14842
14763
|
const hasTitle = !!definition.title.text;
|
|
14843
14764
|
const topMargin = hasTitle ? CHART_PADDING_TOP + 30 : CHART_PADDING_TOP;
|
|
14844
|
-
return { top: topMargin, left: CHART_PADDING
|
|
14765
|
+
return { top: topMargin, left: CHART_PADDING, right: CHART_PADDING };
|
|
14845
14766
|
case "bottom":
|
|
14846
14767
|
case "left":
|
|
14847
14768
|
case "none":
|
|
14848
|
-
return { left: CHART_PADDING
|
|
14769
|
+
return { left: CHART_PADDING, right: CHART_PADDING, bottom: CHART_PADDING_BOTTOM };
|
|
14849
14770
|
}
|
|
14850
14771
|
}
|
|
14851
14772
|
function legendPositionToGeoLegendPosition(position) {
|
|
@@ -14913,7 +14834,7 @@ function getChartTitle(definition) {
|
|
|
14913
14834
|
padding: {
|
|
14914
14835
|
// Disable title top/left/right padding to use the chart padding instead.
|
|
14915
14836
|
// The legend already has a top padding, so bottom padding is useless for the title there.
|
|
14916
|
-
bottom: definition.legendPosition === "top" ? 0 : CHART_PADDING
|
|
14837
|
+
bottom: definition.legendPosition === "top" ? 0 : CHART_PADDING,
|
|
14917
14838
|
},
|
|
14918
14839
|
};
|
|
14919
14840
|
}
|
|
@@ -15476,6 +15397,10 @@ function createBarChartRuntime(chart, getters) {
|
|
|
15476
15397
|
|
|
15477
15398
|
class GaugeChartComponent extends owl.Component {
|
|
15478
15399
|
static template = "o-spreadsheet-GaugeChartComponent";
|
|
15400
|
+
static props = {
|
|
15401
|
+
figureUI: Object,
|
|
15402
|
+
isFullScreen: { type: Boolean, optional: true },
|
|
15403
|
+
};
|
|
15479
15404
|
canvas = owl.useRef("chartContainer");
|
|
15480
15405
|
get runtime() {
|
|
15481
15406
|
return this.env.model.getters.getChartRuntime(this.props.figureUI.id);
|
|
@@ -15488,9 +15413,6 @@ class GaugeChartComponent extends owl.Component {
|
|
|
15488
15413
|
});
|
|
15489
15414
|
}
|
|
15490
15415
|
}
|
|
15491
|
-
GaugeChartComponent.props = {
|
|
15492
|
-
figureUI: Object,
|
|
15493
|
-
};
|
|
15494
15416
|
|
|
15495
15417
|
class ComboChart extends AbstractChart {
|
|
15496
15418
|
dataSets;
|
|
@@ -18294,6 +18216,26 @@ function getDeleteMenuItem(figureId, onFigureDeleted, env) {
|
|
|
18294
18216
|
};
|
|
18295
18217
|
}
|
|
18296
18218
|
|
|
18219
|
+
class FullScreenChartStore extends SpreadsheetStore {
|
|
18220
|
+
mutators = ["toggleFullScreenChart"];
|
|
18221
|
+
fullScreenFigure = undefined;
|
|
18222
|
+
toggleFullScreenChart(figureId) {
|
|
18223
|
+
if (this.fullScreenFigure?.id === figureId) {
|
|
18224
|
+
this.fullScreenFigure = undefined;
|
|
18225
|
+
}
|
|
18226
|
+
else {
|
|
18227
|
+
this.makeFullScreen(figureId);
|
|
18228
|
+
}
|
|
18229
|
+
}
|
|
18230
|
+
makeFullScreen(figureId) {
|
|
18231
|
+
const sheetId = this.getters.getActiveSheetId();
|
|
18232
|
+
const figure = this.getters.getFigure(sheetId, figureId);
|
|
18233
|
+
if (figure) {
|
|
18234
|
+
this.fullScreenFigure = { ...figure, x: 0, y: 0, width: 0, height: 0 };
|
|
18235
|
+
}
|
|
18236
|
+
}
|
|
18237
|
+
}
|
|
18238
|
+
|
|
18297
18239
|
/**
|
|
18298
18240
|
* Repeatedly calls a callback function with a time delay between calls.
|
|
18299
18241
|
*/
|
|
@@ -18413,7 +18355,9 @@ function usePopoverContainer() {
|
|
|
18413
18355
|
const spreadsheetRect = useSpreadsheetRect();
|
|
18414
18356
|
function updateRect() {
|
|
18415
18357
|
const env = component.env;
|
|
18416
|
-
const newRect = "getPopoverContainerRect" in env
|
|
18358
|
+
const newRect = "getPopoverContainerRect" in env && env.getPopoverContainerRect
|
|
18359
|
+
? env.getPopoverContainerRect()
|
|
18360
|
+
: spreadsheetRect;
|
|
18417
18361
|
container.x = newRect.x;
|
|
18418
18362
|
container.y = newRect.y;
|
|
18419
18363
|
container.width = newRect.width;
|
|
@@ -18938,9 +18882,11 @@ class ChartDashboardMenu extends owl.Component {
|
|
|
18938
18882
|
static components = { Menu };
|
|
18939
18883
|
static props = { figureUI: Object };
|
|
18940
18884
|
originalChartDefinition;
|
|
18885
|
+
fullScreenFigureStore;
|
|
18941
18886
|
menuState = owl.useState({ isOpen: false, anchorRect: null, menuItems: [] });
|
|
18942
18887
|
setup() {
|
|
18943
18888
|
super.setup();
|
|
18889
|
+
this.fullScreenFigureStore = useStore(FullScreenChartStore);
|
|
18944
18890
|
this.originalChartDefinition = this.env.model.getters.getChartDefinition(this.props.figureUI.id);
|
|
18945
18891
|
owl.onWillUpdateProps(({ figureUI }) => {
|
|
18946
18892
|
if (figureUI.id !== this.props.figureUI.id) {
|
|
@@ -18948,7 +18894,10 @@ class ChartDashboardMenu extends owl.Component {
|
|
|
18948
18894
|
}
|
|
18949
18895
|
});
|
|
18950
18896
|
}
|
|
18951
|
-
|
|
18897
|
+
getMenuItems() {
|
|
18898
|
+
return [this.fullScreenMenuItem, ...this.changeChartTypeMenuItems].filter(isDefined);
|
|
18899
|
+
}
|
|
18900
|
+
get changeChartTypeMenuItems() {
|
|
18952
18901
|
const definition = this.env.model.getters.getChartDefinition(this.props.figureUI.id);
|
|
18953
18902
|
if (!["line", "bar", "pie"].includes(definition.type)) {
|
|
18954
18903
|
return [];
|
|
@@ -18956,8 +18905,11 @@ class ChartDashboardMenu extends owl.Component {
|
|
|
18956
18905
|
return ["column", "line", "pie"].map((type) => {
|
|
18957
18906
|
const item = chartSubtypeRegistry.get(type);
|
|
18958
18907
|
return {
|
|
18959
|
-
|
|
18960
|
-
|
|
18908
|
+
id: item.chartType,
|
|
18909
|
+
label: item.displayName,
|
|
18910
|
+
onClick: () => this.onTypeChange(item.chartType),
|
|
18911
|
+
isSelected: item.chartType === this.selectedChartType,
|
|
18912
|
+
iconClass: this.getIconClasses(item.chartType),
|
|
18961
18913
|
};
|
|
18962
18914
|
});
|
|
18963
18915
|
}
|
|
@@ -19012,6 +18964,30 @@ class ChartDashboardMenu extends owl.Component {
|
|
|
19012
18964
|
this.menuState.anchorRect = { x: ev.clientX, y: ev.clientY, width: 0, height: 0 };
|
|
19013
18965
|
this.menuState.menuItems = getChartMenuActions(this.props.figureUI.id, () => { }, this.env);
|
|
19014
18966
|
}
|
|
18967
|
+
get fullScreenMenuItem() {
|
|
18968
|
+
const definition = this.env.model.getters.getChartDefinition(this.props.figureUI.id);
|
|
18969
|
+
if (definition.type === "scorecard") {
|
|
18970
|
+
return undefined;
|
|
18971
|
+
}
|
|
18972
|
+
if (this.props.figureUI.id === this.fullScreenFigureStore.fullScreenFigure?.id) {
|
|
18973
|
+
return {
|
|
18974
|
+
id: "fullScreenChart",
|
|
18975
|
+
label: _t("Exit Full Screen"),
|
|
18976
|
+
iconClass: "fa fa-compress",
|
|
18977
|
+
onClick: () => {
|
|
18978
|
+
this.fullScreenFigureStore.toggleFullScreenChart(this.props.figureUI.id);
|
|
18979
|
+
},
|
|
18980
|
+
};
|
|
18981
|
+
}
|
|
18982
|
+
return {
|
|
18983
|
+
id: "fullScreenChart",
|
|
18984
|
+
label: _t("Full Screen"),
|
|
18985
|
+
iconClass: "fa fa-expand",
|
|
18986
|
+
onClick: () => {
|
|
18987
|
+
this.fullScreenFigureStore.toggleFullScreenChart(this.props.figureUI.id);
|
|
18988
|
+
},
|
|
18989
|
+
};
|
|
18990
|
+
}
|
|
19015
18991
|
}
|
|
19016
18992
|
|
|
19017
18993
|
// -----------------------------------------------------------------------------
|
|
@@ -29030,7 +29006,7 @@ const PIVOT = {
|
|
|
29030
29006
|
if (error) {
|
|
29031
29007
|
return error;
|
|
29032
29008
|
}
|
|
29033
|
-
const table = pivot.
|
|
29009
|
+
const table = pivot.getCollapsedTableStructure();
|
|
29034
29010
|
const cells = table.getPivotCells(_includedTotal, _includeColumnHeaders);
|
|
29035
29011
|
const headerRows = _includeColumnHeaders ? table.columns.length : 0;
|
|
29036
29012
|
const pivotTitle = this.getters.getPivotDisplayName(pivotId);
|
|
@@ -29050,7 +29026,7 @@ const PIVOT = {
|
|
|
29050
29026
|
break;
|
|
29051
29027
|
case "HEADER":
|
|
29052
29028
|
const valueAndFormat = pivot.getPivotHeaderValueAndFormat(pivotCell.domain);
|
|
29053
|
-
result[col].push(
|
|
29029
|
+
result[col].push(addAlignFormatToPivotHeader(pivotCell.domain, valueAndFormat));
|
|
29054
29030
|
break;
|
|
29055
29031
|
case "MEASURE_HEADER":
|
|
29056
29032
|
result[col].push(pivot.getPivotMeasureValue(pivotCell.measure, pivotCell.domain));
|
|
@@ -32977,12 +32953,13 @@ class StandaloneComposerStore extends AbstractComposerStore {
|
|
|
32977
32953
|
return res;
|
|
32978
32954
|
}
|
|
32979
32955
|
getComposerContent() {
|
|
32956
|
+
let content = this._currentContent;
|
|
32980
32957
|
if (this.editionMode === "inactive") {
|
|
32981
32958
|
// References in the content might not be linked to the current active sheet
|
|
32982
32959
|
// We here force the sheet name prefix for all references that are not in
|
|
32983
32960
|
// the current active sheet
|
|
32984
32961
|
const defaultRangeSheetId = this.args().defaultRangeSheetId;
|
|
32985
|
-
|
|
32962
|
+
content = rangeTokenize(this.args().content)
|
|
32986
32963
|
.map((token) => {
|
|
32987
32964
|
if (token.type === "REFERENCE") {
|
|
32988
32965
|
const range = this.getters.getRangeFromSheetXC(defaultRangeSheetId, token.value);
|
|
@@ -32992,7 +32969,7 @@ class StandaloneComposerStore extends AbstractComposerStore {
|
|
|
32992
32969
|
})
|
|
32993
32970
|
.join("");
|
|
32994
32971
|
}
|
|
32995
|
-
return this.
|
|
32972
|
+
return localizeContent(content, this.getters.getLocale());
|
|
32996
32973
|
}
|
|
32997
32974
|
stopEdition() {
|
|
32998
32975
|
this._stopEdition();
|
|
@@ -35671,11 +35648,6 @@ function buildTableStyle(name, templateName, primaryColor) {
|
|
|
35671
35648
|
};
|
|
35672
35649
|
}
|
|
35673
35650
|
|
|
35674
|
-
/**
|
|
35675
|
-
* Registry to draw icons on cells
|
|
35676
|
-
*/
|
|
35677
|
-
const iconsOnCellRegistry = new Registry();
|
|
35678
|
-
|
|
35679
35651
|
css /* scss */ `
|
|
35680
35652
|
.o-spreadsheet {
|
|
35681
35653
|
.o-icon {
|
|
@@ -35812,13 +35784,6 @@ const ICON_SETS = {
|
|
|
35812
35784
|
bad: "dotBad",
|
|
35813
35785
|
},
|
|
35814
35786
|
};
|
|
35815
|
-
iconsOnCellRegistry.add("conditional_formatting", (getters, position) => {
|
|
35816
|
-
const icon = getters.getConditionalIcon(position);
|
|
35817
|
-
if (icon) {
|
|
35818
|
-
return ICONS[icon].svg;
|
|
35819
|
-
}
|
|
35820
|
-
return undefined;
|
|
35821
|
-
});
|
|
35822
35787
|
|
|
35823
35788
|
/**
|
|
35824
35789
|
* Map of the different types of conversions warnings and their name in error messages
|
|
@@ -39926,6 +39891,22 @@ migrationStepRegistry
|
|
|
39926
39891
|
}
|
|
39927
39892
|
return data;
|
|
39928
39893
|
},
|
|
39894
|
+
})
|
|
39895
|
+
.add("18.4.2", {
|
|
39896
|
+
migrate(data) {
|
|
39897
|
+
for (const sheet of data.sheets || []) {
|
|
39898
|
+
for (const figure of sheet.figures || []) {
|
|
39899
|
+
if (figure.tag !== "chart" || figure.data.type !== "scorecard") {
|
|
39900
|
+
continue;
|
|
39901
|
+
}
|
|
39902
|
+
const scData = figure.data;
|
|
39903
|
+
if (scData.baselineDescr) {
|
|
39904
|
+
scData.baselineDescr = { text: scData.baselineDescr };
|
|
39905
|
+
}
|
|
39906
|
+
}
|
|
39907
|
+
}
|
|
39908
|
+
return data;
|
|
39909
|
+
},
|
|
39929
39910
|
});
|
|
39930
39911
|
function fixOverlappingFilters(data) {
|
|
39931
39912
|
for (const sheet of data.sheets || []) {
|
|
@@ -40800,7 +40781,7 @@ const REINSERT_DYNAMIC_PIVOT_CHILDREN = (env) => env.model.getters.getPivotIds()
|
|
|
40800
40781
|
sequence: index,
|
|
40801
40782
|
execute: (env) => {
|
|
40802
40783
|
const zone = env.model.getters.getSelectedZone();
|
|
40803
|
-
const table = env.model.getters.getPivot(pivotId).
|
|
40784
|
+
const table = env.model.getters.getPivot(pivotId).getCollapsedTableStructure().export();
|
|
40804
40785
|
env.model.dispatch("INSERT_PIVOT_WITH_TABLE", {
|
|
40805
40786
|
pivotId,
|
|
40806
40787
|
table,
|
|
@@ -40819,7 +40800,7 @@ const REINSERT_STATIC_PIVOT_CHILDREN = (env) => env.model.getters.getPivotIds().
|
|
|
40819
40800
|
sequence: index,
|
|
40820
40801
|
execute: (env) => {
|
|
40821
40802
|
const zone = env.model.getters.getSelectedZone();
|
|
40822
|
-
const table = env.model.getters.getPivot(pivotId).
|
|
40803
|
+
const table = env.model.getters.getPivot(pivotId).getExpandedTableStructure().export();
|
|
40823
40804
|
env.model.dispatch("INSERT_PIVOT_WITH_TABLE", {
|
|
40824
40805
|
pivotId,
|
|
40825
40806
|
table,
|
|
@@ -42200,6 +42181,218 @@ function getColumnsNumber(env) {
|
|
|
42200
42181
|
}
|
|
42201
42182
|
}
|
|
42202
42183
|
|
|
42184
|
+
const PREVIOUS_VALUE = "(previous)";
|
|
42185
|
+
const NEXT_VALUE = "(next)";
|
|
42186
|
+
function getDomainOfParentRow(pivot, domain) {
|
|
42187
|
+
const { colDomain, rowDomain } = domainToColRowDomain(pivot, domain);
|
|
42188
|
+
return [...colDomain, ...rowDomain.slice(0, rowDomain.length - 1)];
|
|
42189
|
+
}
|
|
42190
|
+
function getDomainOfParentCol(pivot, domain) {
|
|
42191
|
+
const { colDomain, rowDomain } = domainToColRowDomain(pivot, domain);
|
|
42192
|
+
return [...colDomain.slice(0, colDomain.length - 1), ...rowDomain];
|
|
42193
|
+
}
|
|
42194
|
+
/**
|
|
42195
|
+
* Split a pivot domain into the part related to the rows of the pivot, and the part related to the columns.
|
|
42196
|
+
*/
|
|
42197
|
+
function domainToColRowDomain(pivot, domain) {
|
|
42198
|
+
const rowFields = pivot.definition.rows.map((c) => c.nameWithGranularity);
|
|
42199
|
+
const rowDomain = domain.filter((node) => rowFields.includes(node.field));
|
|
42200
|
+
const columnFields = pivot.definition.columns.map((c) => c.nameWithGranularity);
|
|
42201
|
+
const colDomain = domain.filter((node) => columnFields.includes(node.field));
|
|
42202
|
+
return { colDomain, rowDomain };
|
|
42203
|
+
}
|
|
42204
|
+
function getDimensionDomain(pivot, dimension, domain) {
|
|
42205
|
+
return dimension === "column"
|
|
42206
|
+
? domainToColRowDomain(pivot, domain).colDomain
|
|
42207
|
+
: domainToColRowDomain(pivot, domain).rowDomain;
|
|
42208
|
+
}
|
|
42209
|
+
function getFieldValueInDomain(fieldNameWithGranularity, domain) {
|
|
42210
|
+
const node = domain.find((n) => n.field === fieldNameWithGranularity);
|
|
42211
|
+
return node?.value;
|
|
42212
|
+
}
|
|
42213
|
+
function isDomainIsInPivot(pivot, domain) {
|
|
42214
|
+
for (const node of domain) {
|
|
42215
|
+
if (pivot.definition.rows.find((row) => row.nameWithGranularity === node.field) === undefined &&
|
|
42216
|
+
pivot.definition.columns.find((col) => col.nameWithGranularity === node.field) === undefined) {
|
|
42217
|
+
return false;
|
|
42218
|
+
}
|
|
42219
|
+
}
|
|
42220
|
+
const { rowDomain, colDomain } = domainToColRowDomain(pivot, domain);
|
|
42221
|
+
return (checkIfDomainInInTree(rowDomain, pivot.getExpandedTableStructure().getRowTree()) &&
|
|
42222
|
+
checkIfDomainInInTree(colDomain, pivot.getExpandedTableStructure().getColTree()));
|
|
42223
|
+
}
|
|
42224
|
+
function checkIfDomainInInTree(domain, tree) {
|
|
42225
|
+
return walkDomainTree(domain, tree) !== undefined;
|
|
42226
|
+
}
|
|
42227
|
+
/**
|
|
42228
|
+
* Given a tree of the col/rows of a pivot, and a domain related to those col/rows, return the node of the tree
|
|
42229
|
+
* corresponding to the domain.
|
|
42230
|
+
*
|
|
42231
|
+
* @param domain The domain to find in the tree
|
|
42232
|
+
* @param tree The tree to search in7
|
|
42233
|
+
* @param stopAtField If provided, the search will stop at the field with this name
|
|
42234
|
+
*/
|
|
42235
|
+
function walkDomainTree(domain, tree, stopAtField) {
|
|
42236
|
+
let currentTreeNode = tree;
|
|
42237
|
+
for (const node of domain) {
|
|
42238
|
+
const child = currentTreeNode.find((n) => n.value === node.value);
|
|
42239
|
+
if (!child) {
|
|
42240
|
+
return undefined;
|
|
42241
|
+
}
|
|
42242
|
+
if (child.field === stopAtField) {
|
|
42243
|
+
return currentTreeNode;
|
|
42244
|
+
}
|
|
42245
|
+
currentTreeNode = child.children;
|
|
42246
|
+
}
|
|
42247
|
+
return currentTreeNode;
|
|
42248
|
+
}
|
|
42249
|
+
/**
|
|
42250
|
+
* Get the domain parent of the given domain with the field `parentFieldName` as leaf of the domain.
|
|
42251
|
+
*
|
|
42252
|
+
* In practice, if the `parentFieldName` is a row in the pivot, the helper will return a domain with the same column
|
|
42253
|
+
* domain, and with a row domain all groupBys children to `parentFieldName` removed.
|
|
42254
|
+
*/
|
|
42255
|
+
function getFieldParentDomain(pivot, parentFieldName, domain) {
|
|
42256
|
+
let { rowDomain, colDomain } = domainToColRowDomain(pivot, domain);
|
|
42257
|
+
const dimension = getFieldDimensionType(pivot, parentFieldName);
|
|
42258
|
+
if (dimension === "row") {
|
|
42259
|
+
const index = rowDomain.findIndex((node) => node.field === parentFieldName);
|
|
42260
|
+
if (index === -1) {
|
|
42261
|
+
return domain;
|
|
42262
|
+
}
|
|
42263
|
+
rowDomain = rowDomain.slice(0, index + 1);
|
|
42264
|
+
}
|
|
42265
|
+
else {
|
|
42266
|
+
const index = colDomain.findIndex((node) => node.field === parentFieldName);
|
|
42267
|
+
if (index === -1) {
|
|
42268
|
+
return domain;
|
|
42269
|
+
}
|
|
42270
|
+
colDomain = colDomain.slice(0, index + 1);
|
|
42271
|
+
}
|
|
42272
|
+
return [...rowDomain, ...colDomain];
|
|
42273
|
+
}
|
|
42274
|
+
/**
|
|
42275
|
+
* Replace in the domain the value of the field `fieldNameWithGranularity` with the given `value`
|
|
42276
|
+
*/
|
|
42277
|
+
function replaceFieldValueInDomain(domain, fieldNameWithGranularity, value) {
|
|
42278
|
+
domain = deepCopy(domain);
|
|
42279
|
+
const node = domain.find((n) => n.field === fieldNameWithGranularity);
|
|
42280
|
+
if (!node) {
|
|
42281
|
+
return domain;
|
|
42282
|
+
}
|
|
42283
|
+
node.value = value;
|
|
42284
|
+
return domain;
|
|
42285
|
+
}
|
|
42286
|
+
function isFieldInDomain(nameWithGranularity, domain) {
|
|
42287
|
+
return domain.some((node) => node.field === nameWithGranularity);
|
|
42288
|
+
}
|
|
42289
|
+
/**
|
|
42290
|
+
* Check if the field is in the rows or columns of the pivot
|
|
42291
|
+
*/
|
|
42292
|
+
function getFieldDimensionType(pivot, nameWithGranularity) {
|
|
42293
|
+
const rowFields = pivot.definition.rows.map((c) => c.nameWithGranularity);
|
|
42294
|
+
if (rowFields.includes(nameWithGranularity)) {
|
|
42295
|
+
return "row";
|
|
42296
|
+
}
|
|
42297
|
+
const columnFields = pivot.definition.columns.map((c) => c.nameWithGranularity);
|
|
42298
|
+
if (columnFields.includes(nameWithGranularity)) {
|
|
42299
|
+
return "column";
|
|
42300
|
+
}
|
|
42301
|
+
throw new Error(`Field ${nameWithGranularity} not found in pivot`);
|
|
42302
|
+
}
|
|
42303
|
+
/**
|
|
42304
|
+
* Replace in the given domain the value of the field `fieldNameWithGranularity` with the previous or next value.
|
|
42305
|
+
*/
|
|
42306
|
+
function getPreviousOrNextValueDomain(pivot, domain, fieldNameWithGranularity, direction) {
|
|
42307
|
+
const dimension = getFieldDimensionType(pivot, fieldNameWithGranularity);
|
|
42308
|
+
const tree = dimension === "row"
|
|
42309
|
+
? pivot.getExpandedTableStructure().getRowTree()
|
|
42310
|
+
: pivot.getExpandedTableStructure().getColTree();
|
|
42311
|
+
const dimDomain = getDimensionDomain(pivot, dimension, domain);
|
|
42312
|
+
const currentTreeNode = walkDomainTree(dimDomain, tree, fieldNameWithGranularity);
|
|
42313
|
+
const values = currentTreeNode?.map((n) => n.value) ?? [];
|
|
42314
|
+
const value = getFieldValueInDomain(fieldNameWithGranularity, domain);
|
|
42315
|
+
if (value === undefined) {
|
|
42316
|
+
return undefined;
|
|
42317
|
+
}
|
|
42318
|
+
const valueIndex = values.indexOf(value);
|
|
42319
|
+
if (value === undefined || valueIndex === -1) {
|
|
42320
|
+
return undefined;
|
|
42321
|
+
}
|
|
42322
|
+
const offset = direction === PREVIOUS_VALUE ? -1 : 1;
|
|
42323
|
+
const newIndex = clip(valueIndex + offset, 0, values.length - 1);
|
|
42324
|
+
return replaceFieldValueInDomain(domain, fieldNameWithGranularity, values[newIndex]);
|
|
42325
|
+
}
|
|
42326
|
+
function domainToString(domain) {
|
|
42327
|
+
return domain ? domain.map(domainNodeToString).join(", ") : "";
|
|
42328
|
+
}
|
|
42329
|
+
function domainNodeToString(domainNode) {
|
|
42330
|
+
return domainNode ? `${domainNode.field}=${domainNode.value}` : "";
|
|
42331
|
+
}
|
|
42332
|
+
/**
|
|
42333
|
+
*
|
|
42334
|
+
* For the ranking, the pivot cell values of a column (or row) at the same depth are grouped together before being sorted
|
|
42335
|
+
* and ranked.
|
|
42336
|
+
*
|
|
42337
|
+
* The grouping of a pivot cell is done with both the value of the domain nodes that are parent of the field
|
|
42338
|
+
* `fieldNameWithGranularity` and the value of the last node of the domain of the pivot cell, if it's not the field
|
|
42339
|
+
* `fieldNameWithGranularity`.
|
|
42340
|
+
*
|
|
42341
|
+
* For example, let's take a pivot grouped by (Date:year, Stage, User, Product), where we want to rank by "Stage" field.
|
|
42342
|
+
* The domain nodes parents of the "Stage" are [Date:year]. The pivot cell with domain:
|
|
42343
|
+
* - [Date:year=2021] is not ranked because it does not contain the "Stage" field
|
|
42344
|
+
* - [Date:year=2021, Stage=Lead] is grouped with the cells [Date:year=2021, Stage=*, User=None, Product=None],
|
|
42345
|
+
* and then ranked within the group
|
|
42346
|
+
* - [Date:year=2021, Stage=Lead, User=Bob] is grouped with the cells [Date:year=2021, Stage=*, User=Bob, Product=None],
|
|
42347
|
+
* and then ranked within the group
|
|
42348
|
+
* - [Date:year=2021, Stage=Lead, User=Bob, Product=Table] is grouped with the cells [Date:year=2021, Stage=*, User=*, Product=Table],
|
|
42349
|
+
* and then ranked within the group
|
|
42350
|
+
*
|
|
42351
|
+
* If we rank the pivot on "User" instead, the parent domain becomes [Date:year, Sage] .The cell with domain:
|
|
42352
|
+
* - [Date:year=2021] is not ranked because it does not contain the "Stage" field
|
|
42353
|
+
* - [Date:year=2021, Stage=Lead] is not ranked because it does not contain the "User" field
|
|
42354
|
+
* - [Date:year=2021, Stage=Lead, User=Bob] is grouped with the cells [Date:year=2021, Stage=Lead, User=Bob, Product=None],
|
|
42355
|
+
* and then ranked within the group
|
|
42356
|
+
* - [Date:year=2021, Stage=Lead, User=Bob, Product=Table] is grouped with the cells with [Date:year=2021, Stage=Lead, User=*, Product=Table],
|
|
42357
|
+
* and then ranked within the group
|
|
42358
|
+
*
|
|
42359
|
+
*/
|
|
42360
|
+
function getRankingDomainKey(domain, fieldNameWithGranularity) {
|
|
42361
|
+
const index = domain.findIndex((node) => node.field === fieldNameWithGranularity);
|
|
42362
|
+
if (index === -1) {
|
|
42363
|
+
return "";
|
|
42364
|
+
}
|
|
42365
|
+
const parent = domain.slice(0, index);
|
|
42366
|
+
const lastNode = domain.at(-1);
|
|
42367
|
+
return domainToString(lastNode.field === fieldNameWithGranularity ? parent : [...parent, lastNode]);
|
|
42368
|
+
}
|
|
42369
|
+
/**
|
|
42370
|
+
* The running total domain is the domain without the field `fieldNameWithGranularity`, ie. we do the running total of
|
|
42371
|
+
* all the pivot cells of the column that have any value for the field `fieldNameWithGranularity` and the same value for
|
|
42372
|
+
* the other fields.
|
|
42373
|
+
*/
|
|
42374
|
+
function getRunningTotalDomainKey(domain, fieldNameWithGranularity) {
|
|
42375
|
+
const index = domain.findIndex((node) => node.field === fieldNameWithGranularity);
|
|
42376
|
+
if (index === -1) {
|
|
42377
|
+
return "";
|
|
42378
|
+
}
|
|
42379
|
+
return domainToString([...domain.slice(0, index), ...domain.slice(index + 1)]);
|
|
42380
|
+
}
|
|
42381
|
+
function sortPivotTree(tree, baseDomain, sortFn) {
|
|
42382
|
+
const sortedTree = [...tree];
|
|
42383
|
+
const domain = [...baseDomain];
|
|
42384
|
+
sortedTree.sort((node1, node2) => sortFn([...domain, node1], [...domain, node2]));
|
|
42385
|
+
for (const node of tree) {
|
|
42386
|
+
const children = sortPivotTree(node.children, [...domain, node], sortFn);
|
|
42387
|
+
node.children = children;
|
|
42388
|
+
}
|
|
42389
|
+
return sortedTree;
|
|
42390
|
+
}
|
|
42391
|
+
function isParentDomain(domain, parentDomain) {
|
|
42392
|
+
return (domain.length > parentDomain.length &&
|
|
42393
|
+
parentDomain.every((node, i) => deepEquals(node, domain[i])));
|
|
42394
|
+
}
|
|
42395
|
+
|
|
42203
42396
|
const pivotProperties = {
|
|
42204
42397
|
name: _t("See pivot properties"),
|
|
42205
42398
|
execute(env) {
|
|
@@ -43200,6 +43393,66 @@ class ArrayFormulaHighlight extends SpreadsheetStore {
|
|
|
43200
43393
|
}
|
|
43201
43394
|
}
|
|
43202
43395
|
|
|
43396
|
+
class ClientFocusStore extends SpreadsheetStore {
|
|
43397
|
+
mutators = [
|
|
43398
|
+
"focusClient",
|
|
43399
|
+
"unfocusClient",
|
|
43400
|
+
"showClientTag",
|
|
43401
|
+
"hideClientTag",
|
|
43402
|
+
"jumpToClient",
|
|
43403
|
+
];
|
|
43404
|
+
_showClientTag = false;
|
|
43405
|
+
clientFocusTimeout = {};
|
|
43406
|
+
constructor(get) {
|
|
43407
|
+
super(get);
|
|
43408
|
+
this.onDispose(() => {
|
|
43409
|
+
for (const clientId in this.clientFocusTimeout) {
|
|
43410
|
+
this.unfocusClient(clientId);
|
|
43411
|
+
}
|
|
43412
|
+
});
|
|
43413
|
+
}
|
|
43414
|
+
get focusedClients() {
|
|
43415
|
+
const focused = new Set();
|
|
43416
|
+
this.model.getters.getConnectedClients().forEach((client) => {
|
|
43417
|
+
if (this._showClientTag || this.clientFocusTimeout[client.id] !== undefined) {
|
|
43418
|
+
focused.add(client.id);
|
|
43419
|
+
}
|
|
43420
|
+
});
|
|
43421
|
+
return focused;
|
|
43422
|
+
}
|
|
43423
|
+
jumpToClient(clientId) {
|
|
43424
|
+
const client = this.model.getters.getClient(clientId);
|
|
43425
|
+
this.focusClient(clientId);
|
|
43426
|
+
if (client.position) {
|
|
43427
|
+
this.model.dispatch("ACTIVATE_SHEET", {
|
|
43428
|
+
sheetIdTo: client.position.sheetId,
|
|
43429
|
+
sheetIdFrom: this.getters.getActiveSheetId(),
|
|
43430
|
+
});
|
|
43431
|
+
this.model.dispatch("SCROLL_TO_CELL", { col: client.position.col, row: client.position.row });
|
|
43432
|
+
}
|
|
43433
|
+
}
|
|
43434
|
+
showClientTag() {
|
|
43435
|
+
this._showClientTag = true;
|
|
43436
|
+
}
|
|
43437
|
+
hideClientTag() {
|
|
43438
|
+
this._showClientTag = false;
|
|
43439
|
+
}
|
|
43440
|
+
focusClient(clientId) {
|
|
43441
|
+
if (this.clientFocusTimeout[clientId]) {
|
|
43442
|
+
clearTimeout(this.clientFocusTimeout[clientId]);
|
|
43443
|
+
}
|
|
43444
|
+
// This call to unfocus client isn't proxyfied and doesn't trigger a render.
|
|
43445
|
+
// The focus will visually disappear when the next render is triggered
|
|
43446
|
+
this.clientFocusTimeout[clientId] = setTimeout(() => this.unfocusClient(clientId), 3000);
|
|
43447
|
+
}
|
|
43448
|
+
unfocusClient(clientId) {
|
|
43449
|
+
if (this.clientFocusTimeout[clientId]) {
|
|
43450
|
+
clearTimeout(this.clientFocusTimeout[clientId]);
|
|
43451
|
+
}
|
|
43452
|
+
this.clientFocusTimeout[clientId] = undefined;
|
|
43453
|
+
}
|
|
43454
|
+
}
|
|
43455
|
+
|
|
43203
43456
|
/**
|
|
43204
43457
|
* Function to be used during a pointerdown event, this function allows to
|
|
43205
43458
|
* perform actions related to the pointermove and pointerup events and adjusts the viewport
|
|
@@ -43473,6 +43726,11 @@ class ClientTag extends owl.Component {
|
|
|
43473
43726
|
get tagStyle() {
|
|
43474
43727
|
const { col, row, color } = this.props;
|
|
43475
43728
|
const { height } = this.env.model.getters.getSheetViewDimensionWithHeaders();
|
|
43729
|
+
const visible = this.env.model.getters.isVisibleInViewport({
|
|
43730
|
+
sheetId: this.env.model.getters.getActiveSheetId(),
|
|
43731
|
+
col,
|
|
43732
|
+
row,
|
|
43733
|
+
});
|
|
43476
43734
|
const { x, y } = this.env.model.getters.getVisibleRect({
|
|
43477
43735
|
left: col,
|
|
43478
43736
|
top: row,
|
|
@@ -43484,6 +43742,7 @@ class ClientTag extends owl.Component {
|
|
|
43484
43742
|
left: `${x - 1}px`,
|
|
43485
43743
|
border: `1px solid ${color}`,
|
|
43486
43744
|
"background-color": color,
|
|
43745
|
+
visibility: visible ? "visible" : "hidden",
|
|
43487
43746
|
});
|
|
43488
43747
|
}
|
|
43489
43748
|
}
|
|
@@ -43909,151 +44168,6 @@ class GridComposer extends owl.Component {
|
|
|
43909
44168
|
}
|
|
43910
44169
|
}
|
|
43911
44170
|
|
|
43912
|
-
css /* scss */ `
|
|
43913
|
-
.o-grid-cell-icon {
|
|
43914
|
-
width: ${GRID_ICON_EDGE_LENGTH}px;
|
|
43915
|
-
height: ${GRID_ICON_EDGE_LENGTH}px;
|
|
43916
|
-
}
|
|
43917
|
-
`;
|
|
43918
|
-
class GridCellIcon extends owl.Component {
|
|
43919
|
-
static template = "o-spreadsheet-GridCellIcon";
|
|
43920
|
-
static props = {
|
|
43921
|
-
cellPosition: Object,
|
|
43922
|
-
horizontalAlign: { type: String, optional: true },
|
|
43923
|
-
verticalAlign: { type: String, optional: true },
|
|
43924
|
-
slots: Object,
|
|
43925
|
-
};
|
|
43926
|
-
get iconStyle() {
|
|
43927
|
-
const cellPosition = this.props.cellPosition;
|
|
43928
|
-
const merge = this.env.model.getters.getMerge(cellPosition);
|
|
43929
|
-
const zone = merge || positionToZone(cellPosition);
|
|
43930
|
-
const rect = this.env.model.getters.getVisibleRectWithoutHeaders(zone);
|
|
43931
|
-
const x = this.getIconHorizontalPosition(rect, cellPosition);
|
|
43932
|
-
const y = this.getIconVerticalPosition(rect, cellPosition);
|
|
43933
|
-
return cssPropertiesToCss({
|
|
43934
|
-
top: `${y}px`,
|
|
43935
|
-
left: `${x}px`,
|
|
43936
|
-
});
|
|
43937
|
-
}
|
|
43938
|
-
getIconVerticalPosition(rect, cellPosition) {
|
|
43939
|
-
const start = rect.y;
|
|
43940
|
-
const end = rect.y + rect.height;
|
|
43941
|
-
const cell = this.env.model.getters.getCell(cellPosition);
|
|
43942
|
-
const align = this.props.verticalAlign || cell?.style?.verticalAlign || DEFAULT_VERTICAL_ALIGN;
|
|
43943
|
-
switch (align) {
|
|
43944
|
-
case "bottom":
|
|
43945
|
-
return end - GRID_ICON_MARGIN - GRID_ICON_EDGE_LENGTH;
|
|
43946
|
-
case "top":
|
|
43947
|
-
return start + GRID_ICON_MARGIN;
|
|
43948
|
-
default:
|
|
43949
|
-
const centeringOffset = Math.floor((end - start - GRID_ICON_EDGE_LENGTH) / 2);
|
|
43950
|
-
return end - GRID_ICON_EDGE_LENGTH - centeringOffset;
|
|
43951
|
-
}
|
|
43952
|
-
}
|
|
43953
|
-
getIconHorizontalPosition(rect, cellPosition) {
|
|
43954
|
-
const start = rect.x;
|
|
43955
|
-
const end = rect.x + rect.width;
|
|
43956
|
-
const cell = this.env.model.getters.getCell(cellPosition);
|
|
43957
|
-
const evaluatedCell = this.env.model.getters.getEvaluatedCell(cellPosition);
|
|
43958
|
-
const align = this.props.horizontalAlign || cell?.style?.align || evaluatedCell.defaultAlign;
|
|
43959
|
-
switch (align) {
|
|
43960
|
-
case "right":
|
|
43961
|
-
return end - GRID_ICON_MARGIN - GRID_ICON_EDGE_LENGTH;
|
|
43962
|
-
case "left":
|
|
43963
|
-
return start + GRID_ICON_MARGIN;
|
|
43964
|
-
default:
|
|
43965
|
-
const centeringOffset = Math.floor((end - start - GRID_ICON_EDGE_LENGTH) / 2);
|
|
43966
|
-
return end - GRID_ICON_EDGE_LENGTH - centeringOffset;
|
|
43967
|
-
}
|
|
43968
|
-
}
|
|
43969
|
-
isPositionVisible(position) {
|
|
43970
|
-
const rect = this.env.model.getters.getVisibleRect(positionToZone(position));
|
|
43971
|
-
return !(rect.width === 0 || rect.height === 0);
|
|
43972
|
-
}
|
|
43973
|
-
}
|
|
43974
|
-
|
|
43975
|
-
const MARGIN = (GRID_ICON_EDGE_LENGTH - CHECKBOX_WIDTH) / 2;
|
|
43976
|
-
css /* scss */ `
|
|
43977
|
-
.o-dv-checkbox {
|
|
43978
|
-
margin: ${MARGIN}px;
|
|
43979
|
-
/* required to prevent the checkbox position to be sensible to the font-size (affects Firefox) */
|
|
43980
|
-
position: absolute;
|
|
43981
|
-
}
|
|
43982
|
-
`;
|
|
43983
|
-
class DataValidationCheckbox extends owl.Component {
|
|
43984
|
-
static template = "o-spreadsheet-DataValidationCheckbox";
|
|
43985
|
-
static components = {
|
|
43986
|
-
Checkbox,
|
|
43987
|
-
};
|
|
43988
|
-
static props = {
|
|
43989
|
-
cellPosition: Object,
|
|
43990
|
-
};
|
|
43991
|
-
onCheckboxChange(value) {
|
|
43992
|
-
const { sheetId, col, row } = this.props.cellPosition;
|
|
43993
|
-
const cellContent = value ? "TRUE" : "FALSE";
|
|
43994
|
-
this.env.model.dispatch("UPDATE_CELL", { sheetId, col, row, content: cellContent });
|
|
43995
|
-
}
|
|
43996
|
-
get checkBoxValue() {
|
|
43997
|
-
return !!this.env.model.getters.getEvaluatedCell(this.props.cellPosition).value;
|
|
43998
|
-
}
|
|
43999
|
-
get isDisabled() {
|
|
44000
|
-
const cell = this.env.model.getters.getCell(this.props.cellPosition);
|
|
44001
|
-
return this.env.model.getters.isReadonly() || !!cell?.isFormula;
|
|
44002
|
-
}
|
|
44003
|
-
}
|
|
44004
|
-
|
|
44005
|
-
const ICON_WIDTH = 13;
|
|
44006
|
-
css /* scss */ `
|
|
44007
|
-
.o-dv-list-icon {
|
|
44008
|
-
color: ${TEXT_BODY_MUTED};
|
|
44009
|
-
border-radius: 1px;
|
|
44010
|
-
height: ${GRID_ICON_EDGE_LENGTH}px;
|
|
44011
|
-
width: ${GRID_ICON_EDGE_LENGTH}px;
|
|
44012
|
-
|
|
44013
|
-
&:hover {
|
|
44014
|
-
color: #ffffff;
|
|
44015
|
-
background-color: ${TEXT_BODY_MUTED};
|
|
44016
|
-
}
|
|
44017
|
-
|
|
44018
|
-
svg {
|
|
44019
|
-
width: ${ICON_WIDTH}px;
|
|
44020
|
-
height: ${ICON_WIDTH}px;
|
|
44021
|
-
}
|
|
44022
|
-
}
|
|
44023
|
-
`;
|
|
44024
|
-
class DataValidationListIcon extends owl.Component {
|
|
44025
|
-
static template = "o-spreadsheet-DataValidationListIcon";
|
|
44026
|
-
static props = {
|
|
44027
|
-
cellPosition: Object,
|
|
44028
|
-
};
|
|
44029
|
-
onClick() {
|
|
44030
|
-
const { col, row } = this.props.cellPosition;
|
|
44031
|
-
this.env.model.selection.selectCell(col, row);
|
|
44032
|
-
this.env.startCellEdition();
|
|
44033
|
-
}
|
|
44034
|
-
}
|
|
44035
|
-
|
|
44036
|
-
class DataValidationOverlay extends owl.Component {
|
|
44037
|
-
static template = "o-spreadsheet-DataValidationOverlay";
|
|
44038
|
-
static props = {};
|
|
44039
|
-
static components = { GridCellIcon, DataValidationCheckbox, DataValidationListIcon };
|
|
44040
|
-
get checkBoxCellPositions() {
|
|
44041
|
-
return this.env.model.getters
|
|
44042
|
-
.getVisibleCellPositions()
|
|
44043
|
-
.filter((position) => this.env.model.getters.isCellValidCheckbox(position) &&
|
|
44044
|
-
!this.env.model.getters.isFilterHeader(position));
|
|
44045
|
-
}
|
|
44046
|
-
get listIconsCellPositions() {
|
|
44047
|
-
if (this.env.model.getters.isReadonly()) {
|
|
44048
|
-
return [];
|
|
44049
|
-
}
|
|
44050
|
-
return this.env.model.getters
|
|
44051
|
-
.getVisibleCellPositions()
|
|
44052
|
-
.filter((position) => this.env.model.getters.cellHasListDataValidationIcon(position) &&
|
|
44053
|
-
!this.env.model.getters.isFilterHeader(position));
|
|
44054
|
-
}
|
|
44055
|
-
}
|
|
44056
|
-
|
|
44057
44171
|
function dragFigureForMove({ x: mouseX, y: mouseY }, { x: mouseInitialX, y: mouseInitialY }, initialFigure, { maxX, maxY }, { scrollX: initialScrollX, scrollY: initialScrollY }, { scrollX, scrollY }) {
|
|
44058
44172
|
const deltaX = mouseX - mouseInitialX + scrollX - initialScrollX;
|
|
44059
44173
|
const newX = clip(initialFigure.x + deltaX, 0, maxX - initialFigure.width);
|
|
@@ -44067,14 +44181,14 @@ function dragFigureForResize(initialFigure, dirX, dirY, { x: mouseX, y: mouseY }
|
|
|
44067
44181
|
const deltaX = Math.min(dirX * (mouseInitialX - mouseX + scrollX - initialScrollX), width - minFigSize);
|
|
44068
44182
|
const deltaY = Math.min(dirY * (mouseInitialY - mouseY + scrollY - initialScrollY), height - minFigSize);
|
|
44069
44183
|
const fraction = Math.min(deltaX / width, deltaY / height);
|
|
44070
|
-
width = width * (1 - fraction);
|
|
44071
|
-
height = height * (1 - fraction);
|
|
44072
44184
|
if (dirX < 0) {
|
|
44073
44185
|
x = x + width * fraction;
|
|
44074
44186
|
}
|
|
44075
44187
|
if (dirY < 0) {
|
|
44076
44188
|
y = y + height * fraction;
|
|
44077
44189
|
}
|
|
44190
|
+
width = width * (1 - fraction);
|
|
44191
|
+
height = height * (1 - fraction);
|
|
44078
44192
|
}
|
|
44079
44193
|
else {
|
|
44080
44194
|
const deltaX = Math.max(dirX * (mouseX - mouseInitialX + scrollX - initialScrollX), minFigSize - width);
|
|
@@ -44655,78 +44769,6 @@ class FiguresContainer extends owl.Component {
|
|
|
44655
44769
|
}
|
|
44656
44770
|
}
|
|
44657
44771
|
|
|
44658
|
-
css /* scss */ `
|
|
44659
|
-
.o-filter-icon {
|
|
44660
|
-
color: ${FILTERS_COLOR};
|
|
44661
|
-
display: flex;
|
|
44662
|
-
align-items: center;
|
|
44663
|
-
justify-content: center;
|
|
44664
|
-
width: ${GRID_ICON_EDGE_LENGTH}px;
|
|
44665
|
-
height: ${GRID_ICON_EDGE_LENGTH}px;
|
|
44666
|
-
|
|
44667
|
-
&:hover {
|
|
44668
|
-
background: ${FILTERS_COLOR};
|
|
44669
|
-
color: #fff;
|
|
44670
|
-
}
|
|
44671
|
-
|
|
44672
|
-
&.o-high-contrast {
|
|
44673
|
-
color: #defade;
|
|
44674
|
-
}
|
|
44675
|
-
&.o-high-contrast:hover {
|
|
44676
|
-
color: ${FILTERS_COLOR};
|
|
44677
|
-
background: #fff;
|
|
44678
|
-
}
|
|
44679
|
-
}
|
|
44680
|
-
.o-filter-icon:hover {
|
|
44681
|
-
background: ${FILTERS_COLOR};
|
|
44682
|
-
color: #fff;
|
|
44683
|
-
}
|
|
44684
|
-
`;
|
|
44685
|
-
class FilterIcon extends owl.Component {
|
|
44686
|
-
static template = "o-spreadsheet-FilterIcon";
|
|
44687
|
-
static props = {
|
|
44688
|
-
cellPosition: Object,
|
|
44689
|
-
};
|
|
44690
|
-
cellPopovers;
|
|
44691
|
-
setup() {
|
|
44692
|
-
this.cellPopovers = useStore(CellPopoverStore);
|
|
44693
|
-
}
|
|
44694
|
-
onClick() {
|
|
44695
|
-
const position = this.props.cellPosition;
|
|
44696
|
-
const activePopover = this.cellPopovers.persistentCellPopover;
|
|
44697
|
-
const { col, row } = position;
|
|
44698
|
-
if (activePopover.isOpen &&
|
|
44699
|
-
activePopover.col === col &&
|
|
44700
|
-
activePopover.row === row &&
|
|
44701
|
-
activePopover.type === "FilterMenu") {
|
|
44702
|
-
this.cellPopovers.close();
|
|
44703
|
-
return;
|
|
44704
|
-
}
|
|
44705
|
-
this.cellPopovers.open({ col, row }, "FilterMenu");
|
|
44706
|
-
}
|
|
44707
|
-
get isFilterActive() {
|
|
44708
|
-
return this.env.model.getters.isFilterActive(this.props.cellPosition);
|
|
44709
|
-
}
|
|
44710
|
-
get iconClass() {
|
|
44711
|
-
const cellStyle = this.env.model.getters.getCellComputedStyle(this.props.cellPosition);
|
|
44712
|
-
const luminance = relativeLuminance(cellStyle.fillColor || "#fff");
|
|
44713
|
-
return luminance < 0.45 ? "o-high-contrast" : "";
|
|
44714
|
-
}
|
|
44715
|
-
}
|
|
44716
|
-
|
|
44717
|
-
class FilterIconsOverlay extends owl.Component {
|
|
44718
|
-
static template = "o-spreadsheet-FilterIconsOverlay";
|
|
44719
|
-
static props = {};
|
|
44720
|
-
static components = {
|
|
44721
|
-
GridCellIcon,
|
|
44722
|
-
FilterIcon,
|
|
44723
|
-
};
|
|
44724
|
-
getFilterHeadersPositions() {
|
|
44725
|
-
const sheetId = this.env.model.getters.getActiveSheetId();
|
|
44726
|
-
return this.env.model.getters.getFilterHeaders(sheetId);
|
|
44727
|
-
}
|
|
44728
|
-
}
|
|
44729
|
-
|
|
44730
44772
|
css /* scss */ `
|
|
44731
44773
|
.o-validation {
|
|
44732
44774
|
border-radius: 4px;
|
|
@@ -44867,6 +44909,78 @@ class GridAddRowsFooter extends owl.Component {
|
|
|
44867
44909
|
}
|
|
44868
44910
|
}
|
|
44869
44911
|
|
|
44912
|
+
class GridCellIcon extends owl.Component {
|
|
44913
|
+
static template = "o-spreadsheet-GridCellIcon";
|
|
44914
|
+
static props = {
|
|
44915
|
+
icon: Object,
|
|
44916
|
+
verticalAlign: { type: String, optional: true },
|
|
44917
|
+
slots: Object,
|
|
44918
|
+
};
|
|
44919
|
+
get iconStyle() {
|
|
44920
|
+
const cellPosition = this.props.icon.position;
|
|
44921
|
+
const merge = this.env.model.getters.getMerge(cellPosition);
|
|
44922
|
+
const zone = merge || positionToZone(cellPosition);
|
|
44923
|
+
const rect = this.env.model.getters.getVisibleRectWithoutHeaders(zone);
|
|
44924
|
+
const x = this.getIconHorizontalPosition(rect, cellPosition);
|
|
44925
|
+
const y = this.getIconVerticalPosition(rect, cellPosition);
|
|
44926
|
+
return cssPropertiesToCss({
|
|
44927
|
+
top: `${y}px`,
|
|
44928
|
+
left: `${x}px`,
|
|
44929
|
+
width: `${this.props.icon.size}px`,
|
|
44930
|
+
height: `${this.props.icon.size}px`,
|
|
44931
|
+
});
|
|
44932
|
+
}
|
|
44933
|
+
getIconVerticalPosition(rect, cellPosition) {
|
|
44934
|
+
const start = rect.y;
|
|
44935
|
+
const end = rect.y + rect.height;
|
|
44936
|
+
const cell = this.env.model.getters.getCell(cellPosition);
|
|
44937
|
+
const align = this.props.verticalAlign || cell?.style?.verticalAlign || DEFAULT_VERTICAL_ALIGN;
|
|
44938
|
+
switch (align) {
|
|
44939
|
+
case "bottom":
|
|
44940
|
+
return end - GRID_ICON_MARGIN - GRID_ICON_EDGE_LENGTH;
|
|
44941
|
+
case "top":
|
|
44942
|
+
return start + GRID_ICON_MARGIN;
|
|
44943
|
+
default:
|
|
44944
|
+
const centeringOffset = Math.floor((end - start - GRID_ICON_EDGE_LENGTH) / 2);
|
|
44945
|
+
return end - GRID_ICON_EDGE_LENGTH - centeringOffset;
|
|
44946
|
+
}
|
|
44947
|
+
}
|
|
44948
|
+
getIconHorizontalPosition(rect, cellPosition) {
|
|
44949
|
+
const start = rect.x;
|
|
44950
|
+
const end = rect.x + rect.width;
|
|
44951
|
+
const cell = this.env.model.getters.getCell(cellPosition);
|
|
44952
|
+
const evaluatedCell = this.env.model.getters.getEvaluatedCell(cellPosition);
|
|
44953
|
+
const align = this.props.icon.horizontalAlign || cell?.style?.align || evaluatedCell.defaultAlign;
|
|
44954
|
+
switch (align) {
|
|
44955
|
+
case "right":
|
|
44956
|
+
return end - this.props.icon.size - this.props.icon.margin;
|
|
44957
|
+
case "left":
|
|
44958
|
+
return start + this.props.icon.margin;
|
|
44959
|
+
default:
|
|
44960
|
+
const centeringOffset = Math.floor((end - start - this.props.icon.size) / 2);
|
|
44961
|
+
return end - this.props.icon.size - centeringOffset;
|
|
44962
|
+
}
|
|
44963
|
+
}
|
|
44964
|
+
isPositionVisible(position) {
|
|
44965
|
+
const rect = this.env.model.getters.getVisibleRect(positionToZone(position));
|
|
44966
|
+
return !(rect.width === 0 || rect.height === 0);
|
|
44967
|
+
}
|
|
44968
|
+
}
|
|
44969
|
+
|
|
44970
|
+
class GridCellIconOverlay extends owl.Component {
|
|
44971
|
+
static template = "o-spreadsheet-GridCellIconOverlay";
|
|
44972
|
+
static props = {};
|
|
44973
|
+
static components = { GridCellIcon };
|
|
44974
|
+
get icons() {
|
|
44975
|
+
const icons = [];
|
|
44976
|
+
for (const position of this.env.model.getters.getVisibleCellPositions()) {
|
|
44977
|
+
const cellIcons = this.env.model.getters.getCellIcons(position);
|
|
44978
|
+
icons.push(...cellIcons.filter((icon) => icon.component));
|
|
44979
|
+
}
|
|
44980
|
+
return icons;
|
|
44981
|
+
}
|
|
44982
|
+
}
|
|
44983
|
+
|
|
44870
44984
|
/**
|
|
44871
44985
|
* Manages an event listener on a ref. Useful for hooks that want to manage
|
|
44872
44986
|
* event listeners, especially more than one. Prefer using t-on directly in
|
|
@@ -45118,9 +45232,8 @@ class GridOverlay extends owl.Component {
|
|
|
45118
45232
|
};
|
|
45119
45233
|
static components = {
|
|
45120
45234
|
FiguresContainer,
|
|
45121
|
-
DataValidationOverlay,
|
|
45122
45235
|
GridAddRowsFooter,
|
|
45123
|
-
|
|
45236
|
+
GridCellIconOverlay,
|
|
45124
45237
|
};
|
|
45125
45238
|
static defaultProps = {
|
|
45126
45239
|
onCellDoubleClicked: () => { },
|
|
@@ -46130,13 +46243,12 @@ class GridRenderer {
|
|
|
46130
46243
|
// compute horizontal align start point parameter
|
|
46131
46244
|
let x = box.x;
|
|
46132
46245
|
if (align === "left") {
|
|
46133
|
-
|
|
46246
|
+
const leftIconSize = box.icons.left ? box.icons.left.size + box.icons.left.margin : 0;
|
|
46247
|
+
x += MIN_CELL_TEXT_MARGIN + leftIconSize;
|
|
46134
46248
|
}
|
|
46135
46249
|
else if (align === "right") {
|
|
46136
|
-
|
|
46137
|
-
|
|
46138
|
-
MIN_CELL_TEXT_MARGIN -
|
|
46139
|
-
(box.hasIcon ? GRID_ICON_EDGE_LENGTH + GRID_ICON_MARGIN : 0);
|
|
46250
|
+
const rightIconSize = box.icons.right ? box.icons.right.size + box.icons.right.margin : 0;
|
|
46251
|
+
x += box.width - MIN_CELL_TEXT_MARGIN - rightIconSize;
|
|
46140
46252
|
}
|
|
46141
46253
|
else {
|
|
46142
46254
|
x += box.width / 2;
|
|
@@ -46170,18 +46282,28 @@ class GridRenderer {
|
|
|
46170
46282
|
drawIcon(renderingContext, boxes) {
|
|
46171
46283
|
const { ctx } = renderingContext;
|
|
46172
46284
|
for (const box of boxes) {
|
|
46173
|
-
|
|
46285
|
+
for (const icon of Object.values(box.icons)) {
|
|
46286
|
+
if (!icon || !icon.svg) {
|
|
46287
|
+
continue;
|
|
46288
|
+
}
|
|
46174
46289
|
ctx.save();
|
|
46175
|
-
|
|
46176
|
-
|
|
46177
|
-
|
|
46178
|
-
|
|
46179
|
-
|
|
46290
|
+
ctx.beginPath();
|
|
46291
|
+
ctx.rect(box.x, box.y, box.width, box.height);
|
|
46292
|
+
ctx.clip();
|
|
46293
|
+
const iconSize = icon.size;
|
|
46294
|
+
const iconY = this.computeTextYCoordinate(box, iconSize);
|
|
46295
|
+
const svg = icon.svg;
|
|
46296
|
+
let x;
|
|
46297
|
+
if (icon.horizontalAlign === "left") {
|
|
46298
|
+
x = box.x + icon.margin;
|
|
46299
|
+
}
|
|
46300
|
+
else if (icon.horizontalAlign === "right") {
|
|
46301
|
+
x = box.x + box.width - iconSize - icon.margin;
|
|
46180
46302
|
}
|
|
46181
|
-
|
|
46182
|
-
|
|
46183
|
-
|
|
46184
|
-
ctx.translate(
|
|
46303
|
+
else {
|
|
46304
|
+
x = box.x + (box.width - iconSize) / 2;
|
|
46305
|
+
}
|
|
46306
|
+
ctx.translate(x, iconY);
|
|
46185
46307
|
ctx.scale(iconSize / svg.width, iconSize / svg.height);
|
|
46186
46308
|
ctx.fillStyle = svg.fillColor;
|
|
46187
46309
|
ctx.fill(new Path2D(svg.path));
|
|
@@ -46358,13 +46480,11 @@ class GridRenderer {
|
|
|
46358
46480
|
const position = { sheetId, col: col + 1, row };
|
|
46359
46481
|
const nextCell = this.getters.getEvaluatedCell(position);
|
|
46360
46482
|
const nextCellBorder = this.getters.getCellComputedBorder(position);
|
|
46361
|
-
const
|
|
46362
|
-
const cellHasCheckbox = this.getters.isCellValidCheckbox(position);
|
|
46483
|
+
const doesCellHaveGridIcon = this.getters.doesCellHaveGridIcon(position);
|
|
46363
46484
|
if (nextCell.type !== CellValueType.empty ||
|
|
46364
46485
|
this.getters.isInMerge(position) ||
|
|
46365
46486
|
nextCellBorder?.left ||
|
|
46366
|
-
|
|
46367
|
-
cellHasCheckbox) {
|
|
46487
|
+
doesCellHaveGridIcon) {
|
|
46368
46488
|
return col;
|
|
46369
46489
|
}
|
|
46370
46490
|
col++;
|
|
@@ -46378,13 +46498,11 @@ class GridRenderer {
|
|
|
46378
46498
|
const position = { sheetId, col: col - 1, row };
|
|
46379
46499
|
const previousCell = this.getters.getEvaluatedCell(position);
|
|
46380
46500
|
const previousCellBorder = this.getters.getCellComputedBorder(position);
|
|
46381
|
-
const
|
|
46382
|
-
const cellHasCheckbox = this.getters.isCellValidCheckbox(position);
|
|
46501
|
+
const doesCellHaveGridIcon = this.getters.doesCellHaveGridIcon(position);
|
|
46383
46502
|
if (previousCell.type !== CellValueType.empty ||
|
|
46384
46503
|
this.getters.isInMerge(position) ||
|
|
46385
46504
|
previousCellBorder?.right ||
|
|
46386
|
-
|
|
46387
|
-
cellHasCheckbox) {
|
|
46505
|
+
doesCellHaveGridIcon) {
|
|
46388
46506
|
return col;
|
|
46389
46507
|
}
|
|
46390
46508
|
col--;
|
|
@@ -46420,6 +46538,12 @@ class GridRenderer {
|
|
|
46420
46538
|
const dataBarFill = this.fingerprints.isEnabled
|
|
46421
46539
|
? undefined
|
|
46422
46540
|
: this.getters.getConditionalDataBar(position);
|
|
46541
|
+
const iconsList = this.getters.getCellIcons(position);
|
|
46542
|
+
const cellIcons = {
|
|
46543
|
+
left: iconsList.find((icon) => icon?.horizontalAlign === "left"),
|
|
46544
|
+
right: iconsList.find((icon) => icon?.horizontalAlign === "right"),
|
|
46545
|
+
center: iconsList.find((icon) => icon?.horizontalAlign === "center"),
|
|
46546
|
+
};
|
|
46423
46547
|
const box = {
|
|
46424
46548
|
x,
|
|
46425
46549
|
y,
|
|
@@ -46432,32 +46556,21 @@ class GridRenderer {
|
|
|
46432
46556
|
overlayColor: this.hoveredTables.overlayColors.get(position),
|
|
46433
46557
|
isError: (cell.type === CellValueType.error && !!cell.message) ||
|
|
46434
46558
|
this.getters.isDataValidationInvalid(position),
|
|
46559
|
+
icons: cellIcons,
|
|
46435
46560
|
};
|
|
46436
|
-
/** Icon */
|
|
46437
|
-
const iconSvg = this.getters.getCellIconSvg(position);
|
|
46438
46561
|
const fontSizePX = computeTextFontSizeInPixels(box.style);
|
|
46439
|
-
|
|
46440
|
-
if (iconSvg) {
|
|
46441
|
-
box.image = {
|
|
46442
|
-
type: "icon",
|
|
46443
|
-
size: fontSizePX,
|
|
46444
|
-
clipIcon: { x: box.x, y: box.y, width: Math.min(iconBoxWidth, width), height },
|
|
46445
|
-
svg: iconSvg,
|
|
46446
|
-
};
|
|
46447
|
-
}
|
|
46448
|
-
if (cell.type === CellValueType.empty || this.getters.isCellValidCheckbox(position)) {
|
|
46562
|
+
if (cell.type === CellValueType.empty || box.icons.center) {
|
|
46449
46563
|
return box;
|
|
46450
46564
|
}
|
|
46451
|
-
/** Filter Header or data validation icon */
|
|
46452
|
-
box.hasIcon = this.getters.doesCellHaveGridIcon(position);
|
|
46453
|
-
const headerIconWidth = box.hasIcon ? GRID_ICON_EDGE_LENGTH + GRID_ICON_MARGIN : 0;
|
|
46454
46565
|
/** Content */
|
|
46455
46566
|
const wrapping = style.wrapping || "overflow";
|
|
46456
46567
|
const wrapText = wrapping === "wrap" && !showFormula;
|
|
46457
46568
|
const maxWidth = width - 2 * MIN_CELL_TEXT_MARGIN;
|
|
46458
46569
|
const multiLineText = this.getters.getCellMultiLineText(position, { maxWidth, wrapText });
|
|
46459
46570
|
const textWidth = Math.max(...multiLineText.map((line) => this.getters.getTextWidth(line, style) + MIN_CELL_TEXT_MARGIN));
|
|
46460
|
-
const
|
|
46571
|
+
const leftIconWidth = box.icons.left ? box.icons.left.size + box.icons.left.margin : 0;
|
|
46572
|
+
const rightIconWidth = box.icons.right ? box.icons.right.size + box.icons.right.margin : 0;
|
|
46573
|
+
const contentWidth = leftIconWidth + textWidth + rightIconWidth;
|
|
46461
46574
|
const align = this.computeCellAlignment(position, contentWidth > width);
|
|
46462
46575
|
box.content = {
|
|
46463
46576
|
textLines: multiLineText,
|
|
@@ -46466,11 +46579,11 @@ class GridRenderer {
|
|
|
46466
46579
|
};
|
|
46467
46580
|
/** ClipRect */
|
|
46468
46581
|
const isOverflowing = contentWidth > width || fontSizePX > height;
|
|
46469
|
-
if (
|
|
46582
|
+
if (box.icons.left || box.icons.right) {
|
|
46470
46583
|
box.clipRect = {
|
|
46471
|
-
x: box.x +
|
|
46584
|
+
x: box.x + leftIconWidth,
|
|
46472
46585
|
y: box.y,
|
|
46473
|
-
width: Math.max(0, width -
|
|
46586
|
+
width: Math.max(0, width - leftIconWidth - rightIconWidth),
|
|
46474
46587
|
height,
|
|
46475
46588
|
};
|
|
46476
46589
|
}
|
|
@@ -48332,14 +48445,16 @@ class ChartTitle extends owl.Component {
|
|
|
48332
48445
|
static components = { Section, TextStyler };
|
|
48333
48446
|
static props = {
|
|
48334
48447
|
title: { type: String, optional: true },
|
|
48448
|
+
placeholder: { type: String, optional: true },
|
|
48335
48449
|
updateTitle: Function,
|
|
48336
|
-
name: { type: String
|
|
48450
|
+
name: { type: String },
|
|
48337
48451
|
style: Object,
|
|
48338
48452
|
defaultStyle: { type: Object, optional: true },
|
|
48339
48453
|
updateStyle: Function,
|
|
48340
48454
|
};
|
|
48341
48455
|
static defaultProps = {
|
|
48342
48456
|
title: "",
|
|
48457
|
+
placeholder: "",
|
|
48343
48458
|
};
|
|
48344
48459
|
updateTitle(ev) {
|
|
48345
48460
|
this.props.updateTitle(ev.target.value);
|
|
@@ -49369,6 +49484,7 @@ class ScorecardChartDesignPanel extends owl.Component {
|
|
|
49369
49484
|
SidePanelCollapsible,
|
|
49370
49485
|
Section,
|
|
49371
49486
|
Checkbox,
|
|
49487
|
+
ChartTitle,
|
|
49372
49488
|
};
|
|
49373
49489
|
static props = {
|
|
49374
49490
|
figureId: String,
|
|
@@ -49393,9 +49509,6 @@ class ScorecardChartDesignPanel extends owl.Component {
|
|
|
49393
49509
|
translate(term) {
|
|
49394
49510
|
return _t(term);
|
|
49395
49511
|
}
|
|
49396
|
-
updateBaselineDescr(ev) {
|
|
49397
|
-
this.props.updateChart(this.props.figureId, { baselineDescr: ev.target.value });
|
|
49398
|
-
}
|
|
49399
49512
|
setColor(color, colorPickerId) {
|
|
49400
49513
|
switch (colorPickerId) {
|
|
49401
49514
|
case "backgroundColor":
|
|
@@ -49409,6 +49522,38 @@ class ScorecardChartDesignPanel extends owl.Component {
|
|
|
49409
49522
|
break;
|
|
49410
49523
|
}
|
|
49411
49524
|
}
|
|
49525
|
+
get keyStyle() {
|
|
49526
|
+
return {
|
|
49527
|
+
align: "center",
|
|
49528
|
+
fontSize: DEFAULT_SCORECARD_KEY_VALUE_FONT_SIZE,
|
|
49529
|
+
...this.props.definition.keyDescr,
|
|
49530
|
+
};
|
|
49531
|
+
}
|
|
49532
|
+
get baselineStyle() {
|
|
49533
|
+
return {
|
|
49534
|
+
align: "center",
|
|
49535
|
+
fontSize: DEFAULT_SCORECARD_BASELINE_FONT_SIZE,
|
|
49536
|
+
...this.props.definition.baselineDescr,
|
|
49537
|
+
};
|
|
49538
|
+
}
|
|
49539
|
+
setKeyText(text) {
|
|
49540
|
+
this.props.updateChart(this.props.figureId, {
|
|
49541
|
+
keyDescr: { ...this.props.definition.keyDescr, text },
|
|
49542
|
+
});
|
|
49543
|
+
}
|
|
49544
|
+
updateKeyStyle(style) {
|
|
49545
|
+
const keyDescr = { ...this.keyStyle, ...style };
|
|
49546
|
+
this.props.updateChart(this.props.figureId, { keyDescr });
|
|
49547
|
+
}
|
|
49548
|
+
setBaselineText(text) {
|
|
49549
|
+
this.props.updateChart(this.props.figureId, {
|
|
49550
|
+
baselineDescr: { ...this.props.definition.baselineDescr, text },
|
|
49551
|
+
});
|
|
49552
|
+
}
|
|
49553
|
+
updateBaselineStyle(style) {
|
|
49554
|
+
const baselineDescr = { ...this.baselineStyle, ...style };
|
|
49555
|
+
this.props.updateChart(this.props.figureId, { baselineDescr });
|
|
49556
|
+
}
|
|
49412
49557
|
}
|
|
49413
49558
|
|
|
49414
49559
|
class SunburstChartDesignPanel extends owl.Component {
|
|
@@ -52319,6 +52464,9 @@ class PivotMeasureEditor extends owl.Component {
|
|
|
52319
52464
|
}
|
|
52320
52465
|
return undefined;
|
|
52321
52466
|
}
|
|
52467
|
+
get isCalculatedMeasureInvalid() {
|
|
52468
|
+
return this.env.model.getters.getMeasureCompiledFormula(this.props.measure).isBadExpression;
|
|
52469
|
+
}
|
|
52322
52470
|
}
|
|
52323
52471
|
|
|
52324
52472
|
css /* scss */ `
|
|
@@ -52809,11 +52957,13 @@ class PivotRuntimeDefinition {
|
|
|
52809
52957
|
columns;
|
|
52810
52958
|
rows;
|
|
52811
52959
|
sortedColumn;
|
|
52960
|
+
collapsedDomains;
|
|
52812
52961
|
constructor(definition, fields) {
|
|
52813
52962
|
this.measures = definition.measures.map((measure) => createMeasure(fields, measure));
|
|
52814
52963
|
this.columns = definition.columns.map((dimension) => createPivotDimension(fields, dimension));
|
|
52815
52964
|
this.rows = definition.rows.map((dimension) => createPivotDimension(fields, dimension));
|
|
52816
52965
|
this.sortedColumn = definition.sortedColumn;
|
|
52966
|
+
this.collapsedDomains = definition.collapsedDomains;
|
|
52817
52967
|
}
|
|
52818
52968
|
getDimension(nameWithGranularity) {
|
|
52819
52969
|
const dimension = this.columns.find((d) => d.nameWithGranularity === nameWithGranularity) ||
|
|
@@ -52968,24 +53118,62 @@ class SpreadsheetPivotTable {
|
|
|
52968
53118
|
rowTree;
|
|
52969
53119
|
colTree;
|
|
52970
53120
|
isSorted = false;
|
|
52971
|
-
constructor(columns, rows, measures, fieldsType) {
|
|
52972
|
-
this.
|
|
53121
|
+
constructor(columns, rows, measures, fieldsType, collapsedDomains = { COL: [], ROW: [] }) {
|
|
53122
|
+
this.measures = measures;
|
|
53123
|
+
this.fieldsType = fieldsType;
|
|
53124
|
+
if (collapsedDomains.COL.length) {
|
|
53125
|
+
columns = this.removeCollapsedColumns(columns, measures, collapsedDomains.COL);
|
|
53126
|
+
}
|
|
53127
|
+
this.columns = columns.map((cols) => {
|
|
52973
53128
|
// offset in the pivot table
|
|
52974
53129
|
// starts at 1 because the first column is the row title
|
|
52975
53130
|
let offset = 1;
|
|
52976
|
-
return
|
|
53131
|
+
return cols.map((col) => {
|
|
52977
53132
|
col = { ...col, offset };
|
|
52978
53133
|
offset += col.width;
|
|
52979
53134
|
return col;
|
|
52980
53135
|
});
|
|
52981
53136
|
});
|
|
52982
|
-
this.rows = rows;
|
|
52983
|
-
this.measures = measures;
|
|
52984
|
-
this.fieldsType = fieldsType;
|
|
53137
|
+
this.rows = rows.filter((row) => !this.isParentCollapsed(collapsedDomains.ROW, row));
|
|
52985
53138
|
this.maxIndent = Math.max(...this.rows.map((row) => row.indent));
|
|
52986
53139
|
this.rowTree = lazy(() => this.buildRowsTree());
|
|
52987
53140
|
this.colTree = lazy(() => this.buildColumnsTree());
|
|
52988
53141
|
}
|
|
53142
|
+
removeCollapsedColumns(columns, measures, collapsedDomains) {
|
|
53143
|
+
const replaceCollapsedChildrenWithSubTotalColumns = (parentCol, depth) => {
|
|
53144
|
+
const parentDomain = this.getDomain(parentCol);
|
|
53145
|
+
const cols = columns[depth];
|
|
53146
|
+
const startIndex = cols.findIndex((col) => isParentDomain(this.getDomain(col), parentDomain));
|
|
53147
|
+
const endIndex = cols.findLastIndex((col) => isParentDomain(this.getDomain(col), parentDomain));
|
|
53148
|
+
const isLeaf = depth === columns.length - 1;
|
|
53149
|
+
const newColumns = measures.map((measure) => {
|
|
53150
|
+
const fields = isLeaf ? [...parentCol.fields, "measure"] : [];
|
|
53151
|
+
const values = isLeaf ? [...parentCol.values, measure] : [];
|
|
53152
|
+
return { fields, values, width: 1, offset: 0, collapsedHeader: !isLeaf };
|
|
53153
|
+
});
|
|
53154
|
+
cols.splice(startIndex, endIndex - startIndex + 1, ...newColumns);
|
|
53155
|
+
};
|
|
53156
|
+
return columns.map((cols, i) => {
|
|
53157
|
+
for (const col of cols) {
|
|
53158
|
+
if (i >= columns.length - 2) {
|
|
53159
|
+
return cols;
|
|
53160
|
+
}
|
|
53161
|
+
const domain = this.getDomain(col);
|
|
53162
|
+
if (!collapsedDomains.some((collapsedDomain) => deepEquals(domain, collapsedDomain))) {
|
|
53163
|
+
continue;
|
|
53164
|
+
}
|
|
53165
|
+
col.width = measures.length;
|
|
53166
|
+
for (let depth = i + 1; depth < columns.length; depth++) {
|
|
53167
|
+
replaceCollapsedChildrenWithSubTotalColumns(col, depth);
|
|
53168
|
+
}
|
|
53169
|
+
}
|
|
53170
|
+
return cols;
|
|
53171
|
+
});
|
|
53172
|
+
}
|
|
53173
|
+
isParentCollapsed(collapsedDomains, dim) {
|
|
53174
|
+
const domain = this.getDomain(dim);
|
|
53175
|
+
return collapsedDomains.some((collapsedDomain) => isParentDomain(domain, collapsedDomain));
|
|
53176
|
+
}
|
|
52989
53177
|
/**
|
|
52990
53178
|
* Get the number of columns leafs (i.e. the number of the last row of columns)
|
|
52991
53179
|
*/
|
|
@@ -53041,19 +53229,19 @@ class SpreadsheetPivotTable {
|
|
|
53041
53229
|
}
|
|
53042
53230
|
else if (row <= colHeadersHeight - 1) {
|
|
53043
53231
|
const domain = this.getColHeaderDomain(col, row);
|
|
53044
|
-
return domain ? { type: "HEADER", domain } : EMPTY_PIVOT_CELL;
|
|
53232
|
+
return domain ? { type: "HEADER", domain, dimension: "COL" } : EMPTY_PIVOT_CELL;
|
|
53045
53233
|
}
|
|
53046
53234
|
else if (col === 0) {
|
|
53047
53235
|
const rowIndex = row - colHeadersHeight;
|
|
53048
|
-
const domain = this.
|
|
53049
|
-
return { type: "HEADER", domain };
|
|
53236
|
+
const domain = this.getDomain(this.rows[rowIndex]);
|
|
53237
|
+
return { type: "HEADER", domain, dimension: "ROW" };
|
|
53050
53238
|
}
|
|
53051
53239
|
else {
|
|
53052
53240
|
const rowIndex = row - colHeadersHeight;
|
|
53053
53241
|
if (!includeTotal && this.isTotalRow(rowIndex)) {
|
|
53054
53242
|
return EMPTY_PIVOT_CELL;
|
|
53055
53243
|
}
|
|
53056
|
-
const domain = [...this.
|
|
53244
|
+
const domain = [...this.getDomain(this.rows[rowIndex]), ...this.getColDomain(col)];
|
|
53057
53245
|
const measure = this.getColMeasure(col);
|
|
53058
53246
|
return { type: "VALUE", domain, measure };
|
|
53059
53247
|
}
|
|
@@ -53062,31 +53250,31 @@ class SpreadsheetPivotTable {
|
|
|
53062
53250
|
if (col === 0) {
|
|
53063
53251
|
return undefined;
|
|
53064
53252
|
}
|
|
53065
|
-
const domain = [];
|
|
53066
53253
|
const pivotCol = this.columns[row].find((pivotCol) => pivotCol.offset === col);
|
|
53067
|
-
if (!pivotCol) {
|
|
53254
|
+
if (!pivotCol || pivotCol.collapsedHeader) {
|
|
53068
53255
|
return undefined;
|
|
53069
53256
|
}
|
|
53070
|
-
|
|
53071
|
-
|
|
53257
|
+
return this.getDomain(pivotCol);
|
|
53258
|
+
}
|
|
53259
|
+
getDomain(dim) {
|
|
53260
|
+
return dim.fields.map((fieldWithGranularity, i) => {
|
|
53072
53261
|
if (fieldWithGranularity === "measure") {
|
|
53073
|
-
|
|
53262
|
+
return {
|
|
53074
53263
|
type: "char",
|
|
53075
53264
|
field: fieldWithGranularity,
|
|
53076
|
-
value: toNormalizedPivotValue({ displayName: "measure", type: "char" },
|
|
53077
|
-
}
|
|
53265
|
+
value: toNormalizedPivotValue({ displayName: "measure", type: "char" }, dim.values[i]),
|
|
53266
|
+
};
|
|
53078
53267
|
}
|
|
53079
53268
|
else {
|
|
53080
53269
|
const { fieldName, granularity } = parseDimension(fieldWithGranularity);
|
|
53081
53270
|
const type = this.fieldsType[fieldName] || "char";
|
|
53082
|
-
|
|
53271
|
+
return {
|
|
53083
53272
|
type,
|
|
53084
53273
|
field: fieldWithGranularity,
|
|
53085
|
-
value: toNormalizedPivotValue({ displayName: fieldName, type, granularity },
|
|
53086
|
-
}
|
|
53274
|
+
value: toNormalizedPivotValue({ displayName: fieldName, type, granularity }, dim.values[i]),
|
|
53275
|
+
};
|
|
53087
53276
|
}
|
|
53088
|
-
}
|
|
53089
|
-
return domain;
|
|
53277
|
+
});
|
|
53090
53278
|
}
|
|
53091
53279
|
getColDomain(col) {
|
|
53092
53280
|
const domain = this.getColHeaderDomain(col, this.columns.length - 1);
|
|
@@ -53100,20 +53288,6 @@ class SpreadsheetPivotTable {
|
|
|
53100
53288
|
}
|
|
53101
53289
|
return measure.toString();
|
|
53102
53290
|
}
|
|
53103
|
-
getRowDomain(row) {
|
|
53104
|
-
const domain = [];
|
|
53105
|
-
for (let i = 0; i < this.rows[row].fields.length; i++) {
|
|
53106
|
-
const fieldWithGranularity = this.rows[row].fields[i];
|
|
53107
|
-
const { fieldName, granularity } = parseDimension(fieldWithGranularity);
|
|
53108
|
-
const type = this.fieldsType[fieldName] || "char";
|
|
53109
|
-
domain.push({
|
|
53110
|
-
type,
|
|
53111
|
-
field: fieldWithGranularity,
|
|
53112
|
-
value: toNormalizedPivotValue({ displayName: fieldName, type, granularity }, this.rows[row].values[i]),
|
|
53113
|
-
});
|
|
53114
|
-
}
|
|
53115
|
-
return domain;
|
|
53116
|
-
}
|
|
53117
53291
|
buildRowsTree() {
|
|
53118
53292
|
const tree = [];
|
|
53119
53293
|
let depth = 0;
|
|
@@ -53218,7 +53392,7 @@ const EMPTY_PIVOT_CELL = { type: "EMPTY" };
|
|
|
53218
53392
|
/**
|
|
53219
53393
|
* This function converts a list of data entry into a spreadsheet pivot table.
|
|
53220
53394
|
*/
|
|
53221
|
-
function dataEntriesToSpreadsheetPivotTable(dataEntries, definition) {
|
|
53395
|
+
function dataEntriesToSpreadsheetPivotTable(dataEntries, definition, mode) {
|
|
53222
53396
|
const measureIds = definition.measures.filter((measure) => !measure.isHidden).map((m) => m.id);
|
|
53223
53397
|
const columnsTree = dataEntriesToColumnsTree(dataEntries, definition.columns, 0);
|
|
53224
53398
|
computeWidthOfColumnsNodes(columnsTree, measureIds.length);
|
|
@@ -53237,7 +53411,8 @@ function dataEntriesToSpreadsheetPivotTable(dataEntries, definition) {
|
|
|
53237
53411
|
for (const row of definition.rows) {
|
|
53238
53412
|
fieldsType[row.fieldName] = row.type;
|
|
53239
53413
|
}
|
|
53240
|
-
|
|
53414
|
+
const collapsedDomains = mode === "collapsed" ? definition.collapsedDomains : undefined;
|
|
53415
|
+
return new SpreadsheetPivotTable(cols, rows, measureIds, fieldsType, collapsedDomains);
|
|
53241
53416
|
}
|
|
53242
53417
|
// -----------------------------------------------------------------------------
|
|
53243
53418
|
// ROWS
|
|
@@ -53610,7 +53785,8 @@ class SpreadsheetPivot {
|
|
|
53610
53785
|
* This object contains the pivot table structure. It is created from the
|
|
53611
53786
|
* data entries and the pivot definition.
|
|
53612
53787
|
*/
|
|
53613
|
-
|
|
53788
|
+
collapsedTable;
|
|
53789
|
+
expandedTable;
|
|
53614
53790
|
/**
|
|
53615
53791
|
* This error is set when the range is invalid. It is used to show an error
|
|
53616
53792
|
* message to the user.
|
|
@@ -53643,7 +53819,8 @@ class SpreadsheetPivot {
|
|
|
53643
53819
|
this.dataEntries = this.loadData();
|
|
53644
53820
|
}
|
|
53645
53821
|
if (type >= ReloadType.TABLE) {
|
|
53646
|
-
this.
|
|
53822
|
+
this.collapsedTable = undefined;
|
|
53823
|
+
this.expandedTable = undefined;
|
|
53647
53824
|
}
|
|
53648
53825
|
}
|
|
53649
53826
|
onDefinitionChange(nextDefinition) {
|
|
@@ -53803,14 +53980,23 @@ class SpreadsheetPivot {
|
|
|
53803
53980
|
}
|
|
53804
53981
|
return values;
|
|
53805
53982
|
}
|
|
53806
|
-
|
|
53983
|
+
getCollapsedTableStructure() {
|
|
53807
53984
|
if (!this.isValid()) {
|
|
53808
53985
|
throw new Error("Pivot is not valid !");
|
|
53809
53986
|
}
|
|
53810
|
-
if (!this.
|
|
53811
|
-
this.
|
|
53987
|
+
if (!this.collapsedTable) {
|
|
53988
|
+
this.collapsedTable = dataEntriesToSpreadsheetPivotTable(this.dataEntries, this.definition, "collapsed");
|
|
53989
|
+
}
|
|
53990
|
+
return this.collapsedTable;
|
|
53991
|
+
}
|
|
53992
|
+
getExpandedTableStructure() {
|
|
53993
|
+
if (!this.isValid()) {
|
|
53994
|
+
throw new Error("Pivot is not valid !");
|
|
53995
|
+
}
|
|
53996
|
+
if (!this.expandedTable) {
|
|
53997
|
+
this.expandedTable = dataEntriesToSpreadsheetPivotTable(this.dataEntries, this.definition, "expanded");
|
|
53812
53998
|
}
|
|
53813
|
-
return this.
|
|
53999
|
+
return this.expandedTable;
|
|
53814
54000
|
}
|
|
53815
54001
|
getFields() {
|
|
53816
54002
|
return this.metaData.fields;
|
|
@@ -54168,6 +54354,13 @@ class PivotSidePanelStore extends SpreadsheetStore {
|
|
|
54168
54354
|
})),
|
|
54169
54355
|
sortedColumn: this.shouldKeepSortedColumn(definition) ? definition.sortedColumn : undefined,
|
|
54170
54356
|
};
|
|
54357
|
+
if (cleanedDefinition.collapsedDomains) {
|
|
54358
|
+
const { COL, ROW } = cleanedDefinition.collapsedDomains;
|
|
54359
|
+
cleanedDefinition.collapsedDomains = {
|
|
54360
|
+
COL: COL.filter((domain) => this.areDomainFieldsValid(domain, cleanedDefinition.columns)),
|
|
54361
|
+
ROW: ROW.filter((domain) => this.areDomainFieldsValid(domain, cleanedDefinition.rows)),
|
|
54362
|
+
};
|
|
54363
|
+
}
|
|
54171
54364
|
if (!this.draft && deepEquals(coreDefinition, cleanedDefinition)) {
|
|
54172
54365
|
return;
|
|
54173
54366
|
}
|
|
@@ -54257,6 +54450,15 @@ class PivotSidePanelStore extends SpreadsheetStore {
|
|
|
54257
54450
|
return (newDefinition.measures.find((measure) => measure.id === sortedColumn.measure) &&
|
|
54258
54451
|
deepEquals(oldDefinition.columns, newDefinition.columns));
|
|
54259
54452
|
}
|
|
54453
|
+
areDomainFieldsValid(domain, dims) {
|
|
54454
|
+
const fieldsNameWithGranularity = dims.map(({ fieldName, granularity }) => fieldName + (granularity ? `:${granularity}` : ""));
|
|
54455
|
+
for (let i = 0; i < domain.length; i++) {
|
|
54456
|
+
if (domain[i].field !== fieldsNameWithGranularity[i]) {
|
|
54457
|
+
return false;
|
|
54458
|
+
}
|
|
54459
|
+
}
|
|
54460
|
+
return true;
|
|
54461
|
+
}
|
|
54260
54462
|
}
|
|
54261
54463
|
|
|
54262
54464
|
class PivotSpreadsheetSidePanel extends owl.Component {
|
|
@@ -55524,6 +55726,7 @@ class Grid extends owl.Component {
|
|
|
55524
55726
|
composerFocusStore;
|
|
55525
55727
|
DOMFocusableElementStore;
|
|
55526
55728
|
paintFormatStore;
|
|
55729
|
+
clientFocusStore;
|
|
55527
55730
|
dragNDropGrid = useDragAndDropBeyondTheViewport(this.env);
|
|
55528
55731
|
onMouseWheel;
|
|
55529
55732
|
hoveredCell;
|
|
@@ -55541,6 +55744,7 @@ class Grid extends owl.Component {
|
|
|
55541
55744
|
this.DOMFocusableElementStore = useStore(DOMFocusableElementStore);
|
|
55542
55745
|
this.sidePanel = useStore(SidePanelStore);
|
|
55543
55746
|
this.paintFormatStore = useStore(PaintFormatStore);
|
|
55747
|
+
this.clientFocusStore = useStore(ClientFocusStore);
|
|
55544
55748
|
useStore(ArrayFormulaHighlight);
|
|
55545
55749
|
owl.useChildSubEnv({ getPopoverContainerRect: () => this.getGridRect() });
|
|
55546
55750
|
owl.useExternalListener(document.body, "cut", this.copy.bind(this, true));
|
|
@@ -55823,6 +56027,9 @@ class Grid extends owl.Component {
|
|
|
55823
56027
|
isCellHovered(col, row) {
|
|
55824
56028
|
return this.hoveredCell.col === col && this.hoveredCell.row === row;
|
|
55825
56029
|
}
|
|
56030
|
+
get focusedClients() {
|
|
56031
|
+
return this.clientFocusStore.focusedClients;
|
|
56032
|
+
}
|
|
55826
56033
|
getGridRect() {
|
|
55827
56034
|
return {
|
|
55828
56035
|
...getRefBoundingRect(this.gridRef),
|
|
@@ -56141,6 +56348,51 @@ class Grid extends owl.Component {
|
|
|
56141
56348
|
const supportedPivotPositionalFormulaRegistry = new Registry();
|
|
56142
56349
|
supportedPivotPositionalFormulaRegistry.add("SPREADSHEET", false);
|
|
56143
56350
|
|
|
56351
|
+
class FullScreenChart extends owl.Component {
|
|
56352
|
+
static template = "o-spreadsheet-FullScreenChart";
|
|
56353
|
+
static props = {};
|
|
56354
|
+
static components = { ChartDashboardMenu };
|
|
56355
|
+
fullScreenChartStore;
|
|
56356
|
+
ref = owl.useRef("fullScreenChart");
|
|
56357
|
+
spreadsheetRect = useSpreadsheetRect();
|
|
56358
|
+
figureRegistry = figureRegistry;
|
|
56359
|
+
setup() {
|
|
56360
|
+
this.fullScreenChartStore = useStore(FullScreenChartStore);
|
|
56361
|
+
const animationStore = useStore(ChartAnimationStore);
|
|
56362
|
+
let lastFigureId = undefined;
|
|
56363
|
+
owl.onWillUpdateProps(() => {
|
|
56364
|
+
if (lastFigureId !== this.figureUI?.id) {
|
|
56365
|
+
animationStore.enableAnimationForChart(this.figureUI?.id + "-fullscreen");
|
|
56366
|
+
}
|
|
56367
|
+
lastFigureId = this.figureUI?.id;
|
|
56368
|
+
});
|
|
56369
|
+
owl.useEffect((el) => el?.focus(), () => [this.ref.el]);
|
|
56370
|
+
}
|
|
56371
|
+
get figureUI() {
|
|
56372
|
+
return this.fullScreenChartStore.fullScreenFigure;
|
|
56373
|
+
}
|
|
56374
|
+
exitFullScreen() {
|
|
56375
|
+
if (this.figureUI) {
|
|
56376
|
+
this.fullScreenChartStore.toggleFullScreenChart(this.figureUI.id);
|
|
56377
|
+
}
|
|
56378
|
+
}
|
|
56379
|
+
onKeyDown(ev) {
|
|
56380
|
+
if (ev.key === "Escape") {
|
|
56381
|
+
this.exitFullScreen();
|
|
56382
|
+
}
|
|
56383
|
+
}
|
|
56384
|
+
get chartComponent() {
|
|
56385
|
+
if (!this.figureUI)
|
|
56386
|
+
return undefined;
|
|
56387
|
+
const type = this.env.model.getters.getChartType(this.figureUI.id);
|
|
56388
|
+
const component = chartComponentRegistry.get(type);
|
|
56389
|
+
if (!component) {
|
|
56390
|
+
throw new Error(`Component is not defined for type ${type}`);
|
|
56391
|
+
}
|
|
56392
|
+
return component;
|
|
56393
|
+
}
|
|
56394
|
+
}
|
|
56395
|
+
|
|
56144
56396
|
css /* scss */ `
|
|
56145
56397
|
.o_pivot_html_renderer {
|
|
56146
56398
|
width: 100%;
|
|
@@ -56195,7 +56447,7 @@ class PivotHTMLRenderer extends owl.Component {
|
|
|
56195
56447
|
showMissingValuesOnly: false,
|
|
56196
56448
|
});
|
|
56197
56449
|
setup() {
|
|
56198
|
-
const table = this.pivot.
|
|
56450
|
+
const table = this.pivot.getExpandedTableStructure();
|
|
56199
56451
|
const formulaId = this.env.model.getters.getPivotFormulaId(this.props.pivotId);
|
|
56200
56452
|
this.data = {
|
|
56201
56453
|
columns: this._buildColHeaders(formulaId, table),
|
|
@@ -56239,7 +56491,7 @@ class PivotHTMLRenderer extends owl.Component {
|
|
|
56239
56491
|
* The parent of "January" is "Australia"
|
|
56240
56492
|
*/
|
|
56241
56493
|
addRecursiveRow(index) {
|
|
56242
|
-
const rows = this.pivot.
|
|
56494
|
+
const rows = this.pivot.getExpandedTableStructure().rows;
|
|
56243
56495
|
const row = [...rows[index].values];
|
|
56244
56496
|
if (row.length <= 1) {
|
|
56245
56497
|
return [index];
|
|
@@ -63648,10 +63900,9 @@ class Evaluator {
|
|
|
63648
63900
|
return this.evaluatedCells.keysForSheet(sheetId);
|
|
63649
63901
|
}
|
|
63650
63902
|
getArrayFormulaSpreadingOn(position) {
|
|
63651
|
-
const
|
|
63652
|
-
|
|
63653
|
-
|
|
63654
|
-
return this.spreadingRelations.isArrayFormula(position) ? position : undefined;
|
|
63903
|
+
const isEmpty = this.getEvaluatedCell(position).type === CellValueType.empty;
|
|
63904
|
+
if (isEmpty) {
|
|
63905
|
+
return undefined;
|
|
63655
63906
|
}
|
|
63656
63907
|
const arrayFormulas = this.spreadingRelations.searchFormulaPositionsSpreadingOn(position.sheetId, positionToZone(position));
|
|
63657
63908
|
return Array.from(arrayFormulas).find((position) => !this.blockedArrayFormulas.has(position));
|
|
@@ -65053,6 +65304,323 @@ class EvaluationDataValidationPlugin extends CoreViewPlugin {
|
|
|
65053
65304
|
}
|
|
65054
65305
|
}
|
|
65055
65306
|
|
|
65307
|
+
const MARGIN = (GRID_ICON_EDGE_LENGTH - CHECKBOX_WIDTH) / 2;
|
|
65308
|
+
css /* scss */ `
|
|
65309
|
+
.o-dv-checkbox {
|
|
65310
|
+
margin: ${MARGIN}px;
|
|
65311
|
+
/* required to prevent the checkbox position to be sensible to the font-size (affects Firefox) */
|
|
65312
|
+
position: absolute;
|
|
65313
|
+
}
|
|
65314
|
+
`;
|
|
65315
|
+
class DataValidationCheckbox extends owl.Component {
|
|
65316
|
+
static template = "o-spreadsheet-DataValidationCheckbox";
|
|
65317
|
+
static components = {
|
|
65318
|
+
Checkbox,
|
|
65319
|
+
};
|
|
65320
|
+
static props = {
|
|
65321
|
+
cellPosition: Object,
|
|
65322
|
+
};
|
|
65323
|
+
onCheckboxChange(value) {
|
|
65324
|
+
const { sheetId, col, row } = this.props.cellPosition;
|
|
65325
|
+
const cellContent = value ? "TRUE" : "FALSE";
|
|
65326
|
+
this.env.model.dispatch("UPDATE_CELL", { sheetId, col, row, content: cellContent });
|
|
65327
|
+
}
|
|
65328
|
+
get checkBoxValue() {
|
|
65329
|
+
return !!this.env.model.getters.getEvaluatedCell(this.props.cellPosition).value;
|
|
65330
|
+
}
|
|
65331
|
+
get isDisabled() {
|
|
65332
|
+
const cell = this.env.model.getters.getCell(this.props.cellPosition);
|
|
65333
|
+
return this.env.model.getters.isReadonly() || !!cell?.isFormula;
|
|
65334
|
+
}
|
|
65335
|
+
}
|
|
65336
|
+
|
|
65337
|
+
const ICON_WIDTH = 13;
|
|
65338
|
+
css /* scss */ `
|
|
65339
|
+
.o-dv-list-icon {
|
|
65340
|
+
color: ${TEXT_BODY_MUTED};
|
|
65341
|
+
border-radius: 1px;
|
|
65342
|
+
height: ${GRID_ICON_EDGE_LENGTH}px;
|
|
65343
|
+
width: ${GRID_ICON_EDGE_LENGTH}px;
|
|
65344
|
+
|
|
65345
|
+
&:hover {
|
|
65346
|
+
color: #ffffff;
|
|
65347
|
+
background-color: ${TEXT_BODY_MUTED};
|
|
65348
|
+
}
|
|
65349
|
+
|
|
65350
|
+
svg {
|
|
65351
|
+
width: ${ICON_WIDTH}px;
|
|
65352
|
+
height: ${ICON_WIDTH}px;
|
|
65353
|
+
}
|
|
65354
|
+
}
|
|
65355
|
+
`;
|
|
65356
|
+
class DataValidationListIcon extends owl.Component {
|
|
65357
|
+
static template = "o-spreadsheet-DataValidationListIcon";
|
|
65358
|
+
static props = {
|
|
65359
|
+
cellPosition: Object,
|
|
65360
|
+
};
|
|
65361
|
+
onClick() {
|
|
65362
|
+
const { col, row } = this.props.cellPosition;
|
|
65363
|
+
this.env.model.selection.selectCell(col, row);
|
|
65364
|
+
this.env.startCellEdition();
|
|
65365
|
+
}
|
|
65366
|
+
}
|
|
65367
|
+
|
|
65368
|
+
css /* scss */ `
|
|
65369
|
+
.o-filter-icon {
|
|
65370
|
+
color: ${FILTERS_COLOR};
|
|
65371
|
+
display: flex;
|
|
65372
|
+
align-items: center;
|
|
65373
|
+
justify-content: center;
|
|
65374
|
+
width: ${GRID_ICON_EDGE_LENGTH}px;
|
|
65375
|
+
height: ${GRID_ICON_EDGE_LENGTH}px;
|
|
65376
|
+
|
|
65377
|
+
&:hover {
|
|
65378
|
+
background: ${FILTERS_COLOR};
|
|
65379
|
+
color: #fff;
|
|
65380
|
+
}
|
|
65381
|
+
|
|
65382
|
+
&.o-high-contrast {
|
|
65383
|
+
color: #defade;
|
|
65384
|
+
}
|
|
65385
|
+
&.o-high-contrast:hover {
|
|
65386
|
+
color: ${FILTERS_COLOR};
|
|
65387
|
+
background: #fff;
|
|
65388
|
+
}
|
|
65389
|
+
}
|
|
65390
|
+
.o-filter-icon:hover {
|
|
65391
|
+
background: ${FILTERS_COLOR};
|
|
65392
|
+
color: #fff;
|
|
65393
|
+
}
|
|
65394
|
+
`;
|
|
65395
|
+
class FilterIcon extends owl.Component {
|
|
65396
|
+
static template = "o-spreadsheet-FilterIcon";
|
|
65397
|
+
static props = {
|
|
65398
|
+
cellPosition: Object,
|
|
65399
|
+
};
|
|
65400
|
+
cellPopovers;
|
|
65401
|
+
setup() {
|
|
65402
|
+
this.cellPopovers = useStore(CellPopoverStore);
|
|
65403
|
+
}
|
|
65404
|
+
onClick() {
|
|
65405
|
+
const position = this.props.cellPosition;
|
|
65406
|
+
const activePopover = this.cellPopovers.persistentCellPopover;
|
|
65407
|
+
const { col, row } = position;
|
|
65408
|
+
if (activePopover.isOpen &&
|
|
65409
|
+
activePopover.col === col &&
|
|
65410
|
+
activePopover.row === row &&
|
|
65411
|
+
activePopover.type === "FilterMenu") {
|
|
65412
|
+
this.cellPopovers.close();
|
|
65413
|
+
return;
|
|
65414
|
+
}
|
|
65415
|
+
this.cellPopovers.open({ col, row }, "FilterMenu");
|
|
65416
|
+
}
|
|
65417
|
+
get isFilterActive() {
|
|
65418
|
+
return this.env.model.getters.isFilterActive(this.props.cellPosition);
|
|
65419
|
+
}
|
|
65420
|
+
get iconClass() {
|
|
65421
|
+
const cellStyle = this.env.model.getters.getCellComputedStyle(this.props.cellPosition);
|
|
65422
|
+
const luminance = relativeLuminance(cellStyle.fillColor || "#fff");
|
|
65423
|
+
return luminance < 0.45 ? "o-high-contrast" : "";
|
|
65424
|
+
}
|
|
65425
|
+
}
|
|
65426
|
+
|
|
65427
|
+
css /* scss */ `
|
|
65428
|
+
.o-spreadsheet {
|
|
65429
|
+
.o-pivot-collapse-icon {
|
|
65430
|
+
cursor: pointer;
|
|
65431
|
+
width: 11px;
|
|
65432
|
+
height: 11px;
|
|
65433
|
+
border: 1px solid #777;
|
|
65434
|
+
background-color: #eee;
|
|
65435
|
+
margin: 3px 0 3px 6px;
|
|
65436
|
+
|
|
65437
|
+
.o-icon {
|
|
65438
|
+
width: 5px;
|
|
65439
|
+
height: 5px;
|
|
65440
|
+
}
|
|
65441
|
+
}
|
|
65442
|
+
}
|
|
65443
|
+
`;
|
|
65444
|
+
class PivotCollapseIcon extends owl.Component {
|
|
65445
|
+
static template = "o-spreadsheet-PivotCollapseIcon";
|
|
65446
|
+
static props = {
|
|
65447
|
+
cellPosition: Object,
|
|
65448
|
+
};
|
|
65449
|
+
onClick() {
|
|
65450
|
+
const pivotCell = this.env.model.getters.getPivotCellFromPosition(this.props.cellPosition);
|
|
65451
|
+
const pivotId = this.env.model.getters.getPivotIdFromPosition(this.props.cellPosition);
|
|
65452
|
+
if (!pivotId || pivotCell.type !== "HEADER") {
|
|
65453
|
+
return;
|
|
65454
|
+
}
|
|
65455
|
+
const definition = this.env.model.getters.getPivotCoreDefinition(pivotId);
|
|
65456
|
+
const collapsedDomains = definition.collapsedDomains?.[pivotCell.dimension]
|
|
65457
|
+
? [...definition.collapsedDomains[pivotCell.dimension]]
|
|
65458
|
+
: [];
|
|
65459
|
+
const index = collapsedDomains.findIndex((domain) => deepEquals(domain, pivotCell.domain));
|
|
65460
|
+
if (index !== -1) {
|
|
65461
|
+
collapsedDomains.splice(index, 1);
|
|
65462
|
+
}
|
|
65463
|
+
else {
|
|
65464
|
+
collapsedDomains.push(pivotCell.domain);
|
|
65465
|
+
}
|
|
65466
|
+
const newDomains = definition.collapsedDomains
|
|
65467
|
+
? { ...definition.collapsedDomains }
|
|
65468
|
+
: { COL: [], ROW: [] };
|
|
65469
|
+
newDomains[pivotCell.dimension] = collapsedDomains;
|
|
65470
|
+
this.env.model.dispatch("UPDATE_PIVOT", {
|
|
65471
|
+
pivotId,
|
|
65472
|
+
pivot: { ...definition, collapsedDomains: newDomains },
|
|
65473
|
+
});
|
|
65474
|
+
}
|
|
65475
|
+
get isCollapsed() {
|
|
65476
|
+
const pivotCell = this.env.model.getters.getPivotCellFromPosition(this.props.cellPosition);
|
|
65477
|
+
const pivotId = this.env.model.getters.getPivotIdFromPosition(this.props.cellPosition);
|
|
65478
|
+
if (!pivotId || pivotCell.type !== "HEADER") {
|
|
65479
|
+
return false;
|
|
65480
|
+
}
|
|
65481
|
+
const definition = this.env.model.getters.getPivotCoreDefinition(pivotId);
|
|
65482
|
+
const domains = definition.collapsedDomains?.[pivotCell.dimension] ?? [];
|
|
65483
|
+
return domains?.some((domain) => deepEquals(domain, pivotCell.domain));
|
|
65484
|
+
}
|
|
65485
|
+
}
|
|
65486
|
+
|
|
65487
|
+
/**
|
|
65488
|
+
* Registry to draw icons on cells
|
|
65489
|
+
*/
|
|
65490
|
+
const iconsOnCellRegistry = new Registry();
|
|
65491
|
+
iconsOnCellRegistry.add("data_validation_checkbox", (getters, position) => {
|
|
65492
|
+
const hasIcon = getters.isCellValidCheckbox(position);
|
|
65493
|
+
if (hasIcon) {
|
|
65494
|
+
return {
|
|
65495
|
+
svg: undefined,
|
|
65496
|
+
priority: 2,
|
|
65497
|
+
horizontalAlign: "center",
|
|
65498
|
+
size: GRID_ICON_EDGE_LENGTH,
|
|
65499
|
+
margin: GRID_ICON_MARGIN,
|
|
65500
|
+
component: DataValidationCheckbox,
|
|
65501
|
+
position,
|
|
65502
|
+
};
|
|
65503
|
+
}
|
|
65504
|
+
return undefined;
|
|
65505
|
+
});
|
|
65506
|
+
iconsOnCellRegistry.add("data_validation_list_icon", (getters, position) => {
|
|
65507
|
+
const hasIcon = !getters.isReadonly() && getters.cellHasListDataValidationIcon(position);
|
|
65508
|
+
if (hasIcon) {
|
|
65509
|
+
return {
|
|
65510
|
+
svg: undefined,
|
|
65511
|
+
priority: 2,
|
|
65512
|
+
horizontalAlign: "right",
|
|
65513
|
+
size: GRID_ICON_EDGE_LENGTH,
|
|
65514
|
+
margin: GRID_ICON_MARGIN,
|
|
65515
|
+
component: DataValidationListIcon,
|
|
65516
|
+
position,
|
|
65517
|
+
};
|
|
65518
|
+
}
|
|
65519
|
+
return undefined;
|
|
65520
|
+
});
|
|
65521
|
+
iconsOnCellRegistry.add("filter_icon", (getters, position) => {
|
|
65522
|
+
const hasIcon = getters.isFilterHeader(position);
|
|
65523
|
+
if (hasIcon) {
|
|
65524
|
+
return {
|
|
65525
|
+
svg: undefined,
|
|
65526
|
+
priority: 3,
|
|
65527
|
+
horizontalAlign: "right",
|
|
65528
|
+
size: GRID_ICON_EDGE_LENGTH,
|
|
65529
|
+
margin: GRID_ICON_MARGIN,
|
|
65530
|
+
component: FilterIcon,
|
|
65531
|
+
position,
|
|
65532
|
+
};
|
|
65533
|
+
}
|
|
65534
|
+
return undefined;
|
|
65535
|
+
});
|
|
65536
|
+
iconsOnCellRegistry.add("conditional_formatting", (getters, position) => {
|
|
65537
|
+
const icon = getters.getConditionalIcon(position);
|
|
65538
|
+
if (icon) {
|
|
65539
|
+
const style = getters.getCellStyle(position);
|
|
65540
|
+
return {
|
|
65541
|
+
svg: ICONS[icon].svg,
|
|
65542
|
+
priority: 1,
|
|
65543
|
+
horizontalAlign: "left",
|
|
65544
|
+
size: computeTextFontSizeInPixels(style),
|
|
65545
|
+
margin: MIN_CF_ICON_MARGIN,
|
|
65546
|
+
position,
|
|
65547
|
+
};
|
|
65548
|
+
}
|
|
65549
|
+
return undefined;
|
|
65550
|
+
});
|
|
65551
|
+
iconsOnCellRegistry.add("pivot_collapse", (getters, position) => {
|
|
65552
|
+
if (!getters.isSpillPivotFormula(position)) {
|
|
65553
|
+
return undefined;
|
|
65554
|
+
}
|
|
65555
|
+
const pivotCell = getters.getPivotCellFromPosition(position);
|
|
65556
|
+
const pivotId = getters.getPivotIdFromPosition(position);
|
|
65557
|
+
if (pivotCell.type === "HEADER" && pivotId && pivotCell.domain.length) {
|
|
65558
|
+
const definition = getters.getPivotCoreDefinition(pivotId);
|
|
65559
|
+
const isDashboard = getters.isDashboard();
|
|
65560
|
+
const fields = pivotCell.dimension === "COL" ? definition.columns : definition.rows;
|
|
65561
|
+
const component = !isDashboard && pivotCell.domain.length !== fields.length ? PivotCollapseIcon : undefined;
|
|
65562
|
+
return {
|
|
65563
|
+
priority: 4,
|
|
65564
|
+
horizontalAlign: "left",
|
|
65565
|
+
size: !!component || (!isDashboard && pivotCell.dimension === "ROW" && definition.rows.length > 1)
|
|
65566
|
+
? GRID_ICON_EDGE_LENGTH
|
|
65567
|
+
: 0,
|
|
65568
|
+
margin: pivotCell.dimension === "ROW" ? (pivotCell.domain.length - 1) * PIVOT_INDENT : 0,
|
|
65569
|
+
component,
|
|
65570
|
+
position,
|
|
65571
|
+
};
|
|
65572
|
+
}
|
|
65573
|
+
return undefined;
|
|
65574
|
+
});
|
|
65575
|
+
|
|
65576
|
+
class CellIconPlugin extends CoreViewPlugin {
|
|
65577
|
+
static getters = ["doesCellHaveGridIcon", "getCellIcons"];
|
|
65578
|
+
cellIconsCache = {};
|
|
65579
|
+
handle(cmd) {
|
|
65580
|
+
if (cmd.type !== "SET_VIEWPORT_OFFSET") {
|
|
65581
|
+
this.cellIconsCache = {};
|
|
65582
|
+
}
|
|
65583
|
+
}
|
|
65584
|
+
getCellIcons(position) {
|
|
65585
|
+
if (!this.cellIconsCache[position.sheetId]) {
|
|
65586
|
+
this.cellIconsCache[position.sheetId] = {};
|
|
65587
|
+
}
|
|
65588
|
+
if (!this.cellIconsCache[position.sheetId][position.col]) {
|
|
65589
|
+
this.cellIconsCache[position.sheetId][position.col] = {};
|
|
65590
|
+
}
|
|
65591
|
+
if (!this.cellIconsCache[position.sheetId][position.col][position.row]) {
|
|
65592
|
+
this.cellIconsCache[position.sheetId][position.col][position.row] =
|
|
65593
|
+
this.computeCellIcons(position);
|
|
65594
|
+
}
|
|
65595
|
+
return this.cellIconsCache[position.sheetId][position.col][position.row];
|
|
65596
|
+
}
|
|
65597
|
+
computeCellIcons(position) {
|
|
65598
|
+
const icons = { left: undefined, right: undefined, center: undefined };
|
|
65599
|
+
const callbacks = iconsOnCellRegistry.getAll();
|
|
65600
|
+
for (const callback of callbacks) {
|
|
65601
|
+
const icon = callback(this.getters, position);
|
|
65602
|
+
if (icon &&
|
|
65603
|
+
(!icons[icon.horizontalAlign] || icon.priority > icons[icon.horizontalAlign].priority)) {
|
|
65604
|
+
icons[icon.horizontalAlign] = icon;
|
|
65605
|
+
}
|
|
65606
|
+
}
|
|
65607
|
+
if (icons.center && (icons.left || icons.right)) {
|
|
65608
|
+
const sideIconsPriority = Math.max(icons.left?.priority || 0, icons.right?.priority || 0);
|
|
65609
|
+
if (icons.center.priority < sideIconsPriority) {
|
|
65610
|
+
icons.center = undefined;
|
|
65611
|
+
}
|
|
65612
|
+
else {
|
|
65613
|
+
icons.left = undefined;
|
|
65614
|
+
icons.right = undefined;
|
|
65615
|
+
}
|
|
65616
|
+
}
|
|
65617
|
+
return Object.values(icons).filter(isDefined);
|
|
65618
|
+
}
|
|
65619
|
+
doesCellHaveGridIcon(position) {
|
|
65620
|
+
return Boolean(this.getCellIcons(position).length);
|
|
65621
|
+
}
|
|
65622
|
+
}
|
|
65623
|
+
|
|
65056
65624
|
class DynamicTablesPlugin extends CoreViewPlugin {
|
|
65057
65625
|
static getters = [
|
|
65058
65626
|
"canCreateDynamicTableOnZones",
|
|
@@ -65492,7 +66060,7 @@ function withPivotPresentationLayer (PivotClass) {
|
|
|
65492
66060
|
}
|
|
65493
66061
|
getValuesToAggregate(measure, domain) {
|
|
65494
66062
|
const { rowDomain, colDomain } = domainToColRowDomain(this, domain);
|
|
65495
|
-
const table = super.
|
|
66063
|
+
const table = super.getExpandedTableStructure();
|
|
65496
66064
|
const values = [];
|
|
65497
66065
|
if (colDomain.length === 0 &&
|
|
65498
66066
|
rowDomain.length < this.definition.rows.length &&
|
|
@@ -65912,7 +66480,7 @@ function withPivotPresentationLayer (PivotClass) {
|
|
|
65912
66480
|
return this.strictMeasureValueToNumber(comparedValue);
|
|
65913
66481
|
}
|
|
65914
66482
|
getPivotValueCells(measureId) {
|
|
65915
|
-
return this.
|
|
66483
|
+
return this.getCollapsedTableStructure()
|
|
65916
66484
|
.getPivotCells()
|
|
65917
66485
|
.map((col) => col.filter((cell) => cell.type === "VALUE" && cell.measure === measureId))
|
|
65918
66486
|
.filter((col) => col.length > 0);
|
|
@@ -65936,8 +66504,13 @@ function withPivotPresentationLayer (PivotClass) {
|
|
|
65936
66504
|
}
|
|
65937
66505
|
throw new Error(`Value ${result.value} is not a number`);
|
|
65938
66506
|
}
|
|
65939
|
-
|
|
65940
|
-
const table = super.
|
|
66507
|
+
getCollapsedTableStructure() {
|
|
66508
|
+
const table = super.getCollapsedTableStructure();
|
|
66509
|
+
this.sortTableStructure(table);
|
|
66510
|
+
return table;
|
|
66511
|
+
}
|
|
66512
|
+
getExpandedTableStructure() {
|
|
66513
|
+
const table = super.getExpandedTableStructure();
|
|
65941
66514
|
this.sortTableStructure(table);
|
|
65942
66515
|
return table;
|
|
65943
66516
|
}
|
|
@@ -66127,7 +66700,7 @@ class PivotUIPlugin extends CoreViewPlugin {
|
|
|
66127
66700
|
const includeColumnHeaders = toScalar(args[3]);
|
|
66128
66701
|
const shouldIncludeColumnHeaders = includeColumnHeaders === undefined ? true : toBoolean(includeColumnHeaders);
|
|
66129
66702
|
const pivotCells = pivot
|
|
66130
|
-
.
|
|
66703
|
+
.getCollapsedTableStructure()
|
|
66131
66704
|
.getPivotCells(shouldIncludeTotal, shouldIncludeColumnHeaders);
|
|
66132
66705
|
const pivotCol = position.col - mainPosition.col;
|
|
66133
66706
|
const pivotRow = position.row - mainPosition.row;
|
|
@@ -66144,9 +66717,11 @@ class PivotUIPlugin extends CoreViewPlugin {
|
|
|
66144
66717
|
}
|
|
66145
66718
|
else if (functionName === "PIVOT.HEADER") {
|
|
66146
66719
|
const domain = pivot.parseArgsToPivotDomain(args.slice(1).map((value) => ({ value })));
|
|
66720
|
+
const colRowDomain = domainToColRowDomain(pivot, domain);
|
|
66147
66721
|
return {
|
|
66148
66722
|
type: "HEADER",
|
|
66149
66723
|
domain,
|
|
66724
|
+
dimension: colRowDomain.colDomain.length ? "COL" : "ROW",
|
|
66150
66725
|
};
|
|
66151
66726
|
}
|
|
66152
66727
|
const [measure, ...domainArgs] = args.slice(1);
|
|
@@ -68146,8 +68721,11 @@ class Session extends EventBus {
|
|
|
68146
68721
|
version: MESSAGE_VERSION,
|
|
68147
68722
|
});
|
|
68148
68723
|
}
|
|
68149
|
-
|
|
68150
|
-
|
|
68724
|
+
getCurrentClient() {
|
|
68725
|
+
return this.getClient(this.clientId);
|
|
68726
|
+
}
|
|
68727
|
+
getClient(clientId) {
|
|
68728
|
+
const client = this.clients[clientId];
|
|
68151
68729
|
if (!client) {
|
|
68152
68730
|
throw new ClientDisconnectedError("The client left the session");
|
|
68153
68731
|
}
|
|
@@ -68181,7 +68759,7 @@ class Session extends EventBus {
|
|
|
68181
68759
|
return;
|
|
68182
68760
|
}
|
|
68183
68761
|
const type = currentPosition ? "CLIENT_MOVED" : "CLIENT_JOINED";
|
|
68184
|
-
const client = this.
|
|
68762
|
+
const client = this.getCurrentClient();
|
|
68185
68763
|
this.clients[this.clientId] = { ...client, position };
|
|
68186
68764
|
this.transportService.sendMessage({
|
|
68187
68765
|
type,
|
|
@@ -68392,6 +68970,7 @@ class CollaborativePlugin extends UIPlugin {
|
|
|
68392
68970
|
static getters = [
|
|
68393
68971
|
"getClientsToDisplay",
|
|
68394
68972
|
"getClient",
|
|
68973
|
+
"getCurrentClient",
|
|
68395
68974
|
"getConnectedClients",
|
|
68396
68975
|
"isFullySynchronized",
|
|
68397
68976
|
];
|
|
@@ -68407,11 +68986,16 @@ class CollaborativePlugin extends UIPlugin {
|
|
|
68407
68986
|
return (position.row < this.getters.getNumberRows(position.sheetId) &&
|
|
68408
68987
|
position.col < this.getters.getNumberCols(position.sheetId));
|
|
68409
68988
|
}
|
|
68410
|
-
getClient() {
|
|
68411
|
-
return this.session.getClient();
|
|
68989
|
+
getClient(clientId) {
|
|
68990
|
+
return this.session.getClient(clientId);
|
|
68991
|
+
}
|
|
68992
|
+
getCurrentClient() {
|
|
68993
|
+
return this.session.getCurrentClient();
|
|
68412
68994
|
}
|
|
68413
68995
|
getConnectedClients() {
|
|
68414
|
-
return this.session.getConnectedClients()
|
|
68996
|
+
return [...this.session.getConnectedClients()].map((client) => {
|
|
68997
|
+
return { ...client, color: this.colors[client.id] };
|
|
68998
|
+
});
|
|
68415
68999
|
}
|
|
68416
69000
|
isFullySynchronized() {
|
|
68417
69001
|
return this.session.isFullySynchronized();
|
|
@@ -68422,7 +69006,7 @@ class CollaborativePlugin extends UIPlugin {
|
|
|
68422
69006
|
*/
|
|
68423
69007
|
getClientsToDisplay() {
|
|
68424
69008
|
try {
|
|
68425
|
-
this.getters.
|
|
69009
|
+
this.getters.getCurrentClient();
|
|
68426
69010
|
}
|
|
68427
69011
|
catch (e) {
|
|
68428
69012
|
if (e instanceof ClientDisconnectedError) {
|
|
@@ -68435,16 +69019,14 @@ class CollaborativePlugin extends UIPlugin {
|
|
|
68435
69019
|
const sheetId = this.getters.getActiveSheetId();
|
|
68436
69020
|
const clients = [];
|
|
68437
69021
|
for (const client of this.getters.getConnectedClients()) {
|
|
68438
|
-
if (client.id !== this.getters.
|
|
69022
|
+
if (client.id !== this.getters.getCurrentClient().id &&
|
|
68439
69023
|
client.position &&
|
|
68440
69024
|
client.position.sheetId === sheetId &&
|
|
68441
69025
|
this.isPositionValid(client.position)) {
|
|
68442
|
-
const position = client.position;
|
|
68443
69026
|
if (!this.colors[client.id]) {
|
|
68444
69027
|
this.colors[client.id] = this.availableColors.next();
|
|
68445
69028
|
}
|
|
68446
|
-
|
|
68447
|
-
clients.push({ ...client, position, color });
|
|
69029
|
+
clients.push({ ...client, color: this.colors[client.id], position: client.position });
|
|
68448
69030
|
}
|
|
68449
69031
|
}
|
|
68450
69032
|
return clients;
|
|
@@ -68970,7 +69552,7 @@ class InsertPivotPlugin extends UIPlugin {
|
|
|
68970
69552
|
sheetIdTo: sheetId,
|
|
68971
69553
|
});
|
|
68972
69554
|
const pivot = this.getters.getPivot(pivotId);
|
|
68973
|
-
this.insertPivotWithTable(sheetId, 0, 0, pivotId, pivot.
|
|
69555
|
+
this.insertPivotWithTable(sheetId, 0, 0, pivotId, pivot.getCollapsedTableStructure().export(), "dynamic");
|
|
68974
69556
|
}
|
|
68975
69557
|
duplicatePivotInNewSheet(pivotId, newPivotId, newSheetId) {
|
|
68976
69558
|
this.dispatch("DUPLICATE_PIVOT", {
|
|
@@ -68993,7 +69575,7 @@ class InsertPivotPlugin extends UIPlugin {
|
|
|
68993
69575
|
if (result.isSuccessful) {
|
|
68994
69576
|
this.dispatch("ACTIVATE_SHEET", { sheetIdFrom: activeSheetId, sheetIdTo: newSheetId });
|
|
68995
69577
|
const pivot = this.getters.getPivot(pivotId);
|
|
68996
|
-
this.insertPivotWithTable(newSheetId, 0, 0, newPivotId, pivot.
|
|
69578
|
+
this.insertPivotWithTable(newSheetId, 0, 0, newPivotId, pivot.getCollapsedTableStructure().export(), "dynamic");
|
|
68997
69579
|
}
|
|
68998
69580
|
}
|
|
68999
69581
|
getPivotDuplicateSheetName(pivotName) {
|
|
@@ -69285,9 +69867,7 @@ class UIOptionsPlugin extends UIPlugin {
|
|
|
69285
69867
|
|
|
69286
69868
|
class SheetUIPlugin extends UIPlugin {
|
|
69287
69869
|
static getters = [
|
|
69288
|
-
"doesCellHaveGridIcon",
|
|
69289
69870
|
"getCellWidth",
|
|
69290
|
-
"getCellIconSvg",
|
|
69291
69871
|
"getTextWidth",
|
|
69292
69872
|
"getCellText",
|
|
69293
69873
|
"getCellMultiLineText",
|
|
@@ -69342,12 +69922,8 @@ class SheetUIPlugin extends UIPlugin {
|
|
|
69342
69922
|
const multiLineText = splitTextToWidth(this.ctx, content, style, undefined);
|
|
69343
69923
|
contentWidth += Math.max(...multiLineText.map((line) => computeTextWidth(this.ctx, line, style)));
|
|
69344
69924
|
}
|
|
69345
|
-
const icon
|
|
69346
|
-
|
|
69347
|
-
contentWidth += computeIconWidth(style);
|
|
69348
|
-
}
|
|
69349
|
-
if (this.getters.doesCellHaveGridIcon(position)) {
|
|
69350
|
-
contentWidth += ICON_EDGE_LENGTH + GRID_ICON_MARGIN;
|
|
69925
|
+
for (const icon of this.getters.getCellIcons(position)) {
|
|
69926
|
+
contentWidth += icon.margin + icon.size;
|
|
69351
69927
|
}
|
|
69352
69928
|
if (contentWidth === 0) {
|
|
69353
69929
|
return 0;
|
|
@@ -69359,16 +69935,6 @@ class SheetUIPlugin extends UIPlugin {
|
|
|
69359
69935
|
}
|
|
69360
69936
|
return contentWidth;
|
|
69361
69937
|
}
|
|
69362
|
-
getCellIconSvg(position) {
|
|
69363
|
-
const callbacks = iconsOnCellRegistry.getAll();
|
|
69364
|
-
for (const callback of callbacks) {
|
|
69365
|
-
const imageSrc = callback(this.getters, position);
|
|
69366
|
-
if (imageSrc) {
|
|
69367
|
-
return imageSrc;
|
|
69368
|
-
}
|
|
69369
|
-
}
|
|
69370
|
-
return undefined;
|
|
69371
|
-
}
|
|
69372
69938
|
getTextWidth(text, style) {
|
|
69373
69939
|
return computeTextWidth(this.ctx, text, style);
|
|
69374
69940
|
}
|
|
@@ -69408,11 +69974,6 @@ class SheetUIPlugin extends UIPlugin {
|
|
|
69408
69974
|
});
|
|
69409
69975
|
return splitTextToWidth(this.ctx, text, style, args.wrapText ? args.maxWidth : undefined);
|
|
69410
69976
|
}
|
|
69411
|
-
doesCellHaveGridIcon(position) {
|
|
69412
|
-
const isFilterHeader = this.getters.isFilterHeader(position);
|
|
69413
|
-
const hasListIcon = !this.getters.isReadonly() && this.getters.cellHasListDataValidationIcon(position);
|
|
69414
|
-
return isFilterHeader || hasListIcon;
|
|
69415
|
-
}
|
|
69416
69977
|
/**
|
|
69417
69978
|
* Expands the given zone until bordered by empty cells or reached the sheet boundaries.
|
|
69418
69979
|
*/
|
|
@@ -73304,7 +73865,8 @@ const coreViewsPluginRegistry = new Registry()
|
|
|
73304
73865
|
.add("data_validation_ui", EvaluationDataValidationPlugin)
|
|
73305
73866
|
.add("dynamic_tables", DynamicTablesPlugin)
|
|
73306
73867
|
.add("custom_colors", CustomColorsPlugin)
|
|
73307
|
-
.add("pivot_ui", PivotUIPlugin)
|
|
73868
|
+
.add("pivot_ui", PivotUIPlugin)
|
|
73869
|
+
.add("cell_icon", CellIconPlugin);
|
|
73308
73870
|
|
|
73309
73871
|
autoCompleteProviders.add("dataValidation", {
|
|
73310
73872
|
displayAllOnInitialContent: true,
|
|
@@ -74454,6 +75016,21 @@ class TopBarComponentRegistry extends Registry {
|
|
|
74454
75016
|
}
|
|
74455
75017
|
const topbarComponentRegistry = new TopBarComponentRegistry();
|
|
74456
75018
|
|
|
75019
|
+
class LocalTransportService {
|
|
75020
|
+
listeners = [];
|
|
75021
|
+
async sendMessage(message) {
|
|
75022
|
+
for (const { callback } of this.listeners) {
|
|
75023
|
+
callback(message);
|
|
75024
|
+
}
|
|
75025
|
+
}
|
|
75026
|
+
onNewMessage(id, callback) {
|
|
75027
|
+
this.listeners.push({ id, callback });
|
|
75028
|
+
}
|
|
75029
|
+
leave(id) {
|
|
75030
|
+
this.listeners = this.listeners.filter((listener) => listener.id !== id);
|
|
75031
|
+
}
|
|
75032
|
+
}
|
|
75033
|
+
|
|
74457
75034
|
class ImageProvider {
|
|
74458
75035
|
fileStore;
|
|
74459
75036
|
constructor(fileStore) {
|
|
@@ -77291,6 +77868,7 @@ class Spreadsheet extends owl.Component {
|
|
|
77291
77868
|
SidePanel,
|
|
77292
77869
|
SpreadsheetDashboard,
|
|
77293
77870
|
HeaderGroupContainer,
|
|
77871
|
+
FullScreenChart,
|
|
77294
77872
|
};
|
|
77295
77873
|
sidePanel;
|
|
77296
77874
|
spreadsheetRef = owl.useRef("spreadsheet");
|
|
@@ -77449,21 +78027,6 @@ class Spreadsheet extends owl.Component {
|
|
|
77449
78027
|
}
|
|
77450
78028
|
}
|
|
77451
78029
|
|
|
77452
|
-
class LocalTransportService {
|
|
77453
|
-
listeners = [];
|
|
77454
|
-
async sendMessage(message) {
|
|
77455
|
-
for (const { callback } of this.listeners) {
|
|
77456
|
-
callback(message);
|
|
77457
|
-
}
|
|
77458
|
-
}
|
|
77459
|
-
onNewMessage(id, callback) {
|
|
77460
|
-
this.listeners.push({ id, callback });
|
|
77461
|
-
}
|
|
77462
|
-
leave(id) {
|
|
77463
|
-
this.listeners = this.listeners.filter((listener) => listener.id !== id);
|
|
77464
|
-
}
|
|
77465
|
-
}
|
|
77466
|
-
|
|
77467
78030
|
function inverseCommand(cmd) {
|
|
77468
78031
|
return inverseCommandRegistry.get(cmd.type)(cmd);
|
|
77469
78032
|
}
|
|
@@ -81887,6 +82450,7 @@ const components = {
|
|
|
81887
82450
|
RadioSelection,
|
|
81888
82451
|
GeoChartRegionSelectSection,
|
|
81889
82452
|
ChartDashboardMenu,
|
|
82453
|
+
FullScreenChart,
|
|
81890
82454
|
};
|
|
81891
82455
|
const hooks = {
|
|
81892
82456
|
useDragAndDropListItems,
|
|
@@ -81913,6 +82477,7 @@ const stores = {
|
|
|
81913
82477
|
SidePanelStore,
|
|
81914
82478
|
PivotSidePanelStore,
|
|
81915
82479
|
PivotMeasureDisplayPanelStore,
|
|
82480
|
+
ClientFocusStore,
|
|
81916
82481
|
};
|
|
81917
82482
|
function addFunction(functionName, functionDescription) {
|
|
81918
82483
|
functionRegistry.add(functionName, functionDescription);
|
|
@@ -81936,6 +82501,7 @@ exports.CorePlugin = CorePlugin;
|
|
|
81936
82501
|
exports.CoreViewPlugin = CoreViewPlugin;
|
|
81937
82502
|
exports.DispatchResult = DispatchResult;
|
|
81938
82503
|
exports.EvaluationError = EvaluationError;
|
|
82504
|
+
exports.LocalTransportService = LocalTransportService;
|
|
81939
82505
|
exports.Model = Model;
|
|
81940
82506
|
exports.PivotRuntimeDefinition = PivotRuntimeDefinition;
|
|
81941
82507
|
exports.Registry = Registry;
|
|
@@ -81977,6 +82543,6 @@ exports.tokenColors = tokenColors;
|
|
|
81977
82543
|
exports.tokenize = tokenize;
|
|
81978
82544
|
|
|
81979
82545
|
|
|
81980
|
-
__info__.version = "18.4.0-alpha.
|
|
81981
|
-
__info__.date = "2025-05-
|
|
81982
|
-
__info__.hash = "
|
|
82546
|
+
__info__.version = "18.4.0-alpha.4";
|
|
82547
|
+
__info__.date = "2025-05-20T05:57:45.452Z";
|
|
82548
|
+
__info__.hash = "5c28bca";
|