@odoo/o-spreadsheet 19.0.5 → 19.0.7
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 +795 -397
- package/dist/o-spreadsheet.d.ts +24 -6
- package/dist/o-spreadsheet.esm.js +795 -397
- package/dist/o-spreadsheet.iife.js +795 -397
- package/dist/o-spreadsheet.iife.min.js +429 -428
- package/dist/o_spreadsheet.xml +13 -4
- 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 19.0.
|
|
6
|
-
* @date 2025-10-
|
|
7
|
-
* @hash
|
|
5
|
+
* @version 19.0.7
|
|
6
|
+
* @date 2025-10-23T08:19:01.764Z
|
|
7
|
+
* @hash 1c1d1ec
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
(function (exports, owl) {
|
|
@@ -1892,6 +1892,29 @@
|
|
|
1892
1892
|
removeContiguousProfiles(profilesStartingPosition, profiles, leftIndex, rightIndex);
|
|
1893
1893
|
}
|
|
1894
1894
|
}
|
|
1895
|
+
function profilesContainsZone(profilesStartingPosition, profiles, zone) {
|
|
1896
|
+
const leftValue = zone.left;
|
|
1897
|
+
const rightValue = zone.right;
|
|
1898
|
+
const topValue = zone.top;
|
|
1899
|
+
const bottomValue = zone.bottom + 1;
|
|
1900
|
+
const leftIndex = binaryPredecessorSearch(profilesStartingPosition, leftValue, 0);
|
|
1901
|
+
const rightIndex = binaryPredecessorSearch(profilesStartingPosition, rightValue, leftIndex);
|
|
1902
|
+
if (leftIndex === -1 || rightIndex === -1) {
|
|
1903
|
+
return false;
|
|
1904
|
+
}
|
|
1905
|
+
for (let i = leftIndex; i <= rightIndex; i++) {
|
|
1906
|
+
const profile = profiles.get(profilesStartingPosition[i]);
|
|
1907
|
+
const topPredIndex = binaryPredecessorSearch(profile, topValue, 0, true);
|
|
1908
|
+
const bottomSuccIndex = binarySuccessorSearch(profile, bottomValue, 0, true);
|
|
1909
|
+
if (topPredIndex === -1 || topPredIndex % 2 !== 0) {
|
|
1910
|
+
return false;
|
|
1911
|
+
}
|
|
1912
|
+
if (topValue < profile[topPredIndex] || bottomValue > profile[bottomSuccIndex]) {
|
|
1913
|
+
return false;
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
return true;
|
|
1917
|
+
}
|
|
1895
1918
|
function findIndexAndCreateProfile(profilesStartingPosition, profiles, value, searchLeft, startIndex) {
|
|
1896
1919
|
if (value === undefined) {
|
|
1897
1920
|
// this is only the case when the value correspond to a bottom value that could be undefined
|
|
@@ -1976,7 +1999,18 @@
|
|
|
1976
1999
|
}
|
|
1977
2000
|
// add the top and bottom value to the profile and
|
|
1978
2001
|
// remove all information between the top and bottom index
|
|
1979
|
-
|
|
2002
|
+
const toDelete = bottomSuccIndex - topPredIndex - 1;
|
|
2003
|
+
const toInsert = newPoints.length;
|
|
2004
|
+
const start = topPredIndex + 1;
|
|
2005
|
+
// fast path and slow path
|
|
2006
|
+
if (start === profile.length - 1 && toDelete === 1 && toInsert === 1) {
|
|
2007
|
+
// fast path: we just need to replace the last element
|
|
2008
|
+
profile[start] = newPoints[0] ?? newPoints[1];
|
|
2009
|
+
}
|
|
2010
|
+
else {
|
|
2011
|
+
// equivalent but slower and with memory allocation
|
|
2012
|
+
profile.splice(start, toDelete, ...newPoints);
|
|
2013
|
+
}
|
|
1980
2014
|
}
|
|
1981
2015
|
function removeContiguousProfiles(profilesStartingPosition, profiles, leftIndex, rightIndex) {
|
|
1982
2016
|
const start = leftIndex - 1 === -1 ? 0 : leftIndex - 1;
|
|
@@ -2015,8 +2049,10 @@
|
|
|
2015
2049
|
left,
|
|
2016
2050
|
bottom,
|
|
2017
2051
|
right,
|
|
2018
|
-
hasHeader: (bottom === undefined && top !== 0) || (right === undefined && left !== 0),
|
|
2019
2052
|
};
|
|
2053
|
+
if ((bottom === undefined && top !== 0) || (right === undefined && left !== 0)) {
|
|
2054
|
+
profileZone.hasHeader = true;
|
|
2055
|
+
}
|
|
2020
2056
|
let findCorrespondingZone = false;
|
|
2021
2057
|
for (let j = pendingZones.length - 1; j >= 0; j--) {
|
|
2022
2058
|
const pendingZone = pendingZones[j];
|
|
@@ -2501,17 +2537,6 @@
|
|
|
2501
2537
|
}
|
|
2502
2538
|
return [leftColumnZone, rightPartZone];
|
|
2503
2539
|
}
|
|
2504
|
-
function aggregatePositionsToZones(positions) {
|
|
2505
|
-
const result = {};
|
|
2506
|
-
for (const position of positions) {
|
|
2507
|
-
result[position.sheetId] ??= [];
|
|
2508
|
-
result[position.sheetId].push(positionToZone(position));
|
|
2509
|
-
}
|
|
2510
|
-
for (const sheetId in result) {
|
|
2511
|
-
result[sheetId] = recomputeZones(result[sheetId]);
|
|
2512
|
-
}
|
|
2513
|
-
return result;
|
|
2514
|
-
}
|
|
2515
2540
|
/**
|
|
2516
2541
|
* Array of all positions in the zone.
|
|
2517
2542
|
*/
|
|
@@ -4514,7 +4539,17 @@
|
|
|
4514
4539
|
return toMatrix(data).map((row) => {
|
|
4515
4540
|
return row.map((cell) => {
|
|
4516
4541
|
if (typeof cell.value !== "number") {
|
|
4517
|
-
|
|
4542
|
+
let message = "";
|
|
4543
|
+
if (typeof cell === "object") {
|
|
4544
|
+
message = _t("Function [[FUNCTION_NAME]] expects number values for %s, but got an empty value.", argName);
|
|
4545
|
+
}
|
|
4546
|
+
else if (typeof cell === "string") {
|
|
4547
|
+
message = _t("Function [[FUNCTION_NAME]] expects number values for %s, but got a string.", argName);
|
|
4548
|
+
}
|
|
4549
|
+
else if (typeof cell === "boolean") {
|
|
4550
|
+
message = _t("Function [[FUNCTION_NAME]] expects number values for %s, but got a boolean.", argName);
|
|
4551
|
+
}
|
|
4552
|
+
throw new EvaluationError(message);
|
|
4518
4553
|
}
|
|
4519
4554
|
return cell.value;
|
|
4520
4555
|
});
|
|
@@ -6933,6 +6968,10 @@
|
|
|
6933
6968
|
}
|
|
6934
6969
|
return parts;
|
|
6935
6970
|
}
|
|
6971
|
+
function positionToBoundedRange(position) {
|
|
6972
|
+
const zone = { left: position.col, top: position.row, right: position.col, bottom: position.row };
|
|
6973
|
+
return { sheetId: position.sheetId, zone };
|
|
6974
|
+
}
|
|
6936
6975
|
/**
|
|
6937
6976
|
* Check that a zone is valid regarding the order of top-bottom and left-right.
|
|
6938
6977
|
* Left should be smaller than right, top should be smaller than bottom.
|
|
@@ -9529,7 +9568,7 @@
|
|
|
9529
9568
|
pasteCell(origin, target, clipboardOption) {
|
|
9530
9569
|
const { sheetId, col, row } = target;
|
|
9531
9570
|
const targetCell = this.getters.getEvaluatedCell(target);
|
|
9532
|
-
const originFormat = origin?.format
|
|
9571
|
+
const originFormat = origin?.format || origin.evaluatedCell.format;
|
|
9533
9572
|
if (clipboardOption?.pasteOption === "asValue") {
|
|
9534
9573
|
this.dispatch("UPDATE_CELL", {
|
|
9535
9574
|
...target,
|
|
@@ -13902,7 +13941,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
13902
13941
|
if (knownDataY.length === 0 || knownDataY[0].length === 0) {
|
|
13903
13942
|
return new EvaluationError(emptyDataErrorMessage("known_data_y"));
|
|
13904
13943
|
}
|
|
13905
|
-
return expM(predictLinearValues(logM(toNumberMatrix(knownDataY, "
|
|
13944
|
+
return expM(predictLinearValues(logM(toNumberMatrix(knownDataY, "known_data_y")), toNumberMatrix(knownDataX, "known_data_x"), toNumberMatrix(newDataX, "new_data_y"), toBoolean(b)));
|
|
13906
13945
|
},
|
|
13907
13946
|
};
|
|
13908
13947
|
// -----------------------------------------------------------------------------
|
|
@@ -13975,7 +14014,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
13975
14014
|
if (dataY.length === 0 || dataY[0].length === 0) {
|
|
13976
14015
|
return new EvaluationError(emptyDataErrorMessage("data_y"));
|
|
13977
14016
|
}
|
|
13978
|
-
return fullLinearRegression(toNumberMatrix(dataX, "
|
|
14017
|
+
return fullLinearRegression(toNumberMatrix(dataX, "data_x"), toNumberMatrix(dataY, "data_y"), toBoolean(calculateB), toBoolean(verbose));
|
|
13979
14018
|
},
|
|
13980
14019
|
isExported: true,
|
|
13981
14020
|
};
|
|
@@ -13994,7 +14033,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
13994
14033
|
if (dataY.length === 0 || dataY[0].length === 0) {
|
|
13995
14034
|
return new EvaluationError(emptyDataErrorMessage("data_y"));
|
|
13996
14035
|
}
|
|
13997
|
-
const coeffs = fullLinearRegression(toNumberMatrix(dataX, "
|
|
14036
|
+
const coeffs = fullLinearRegression(toNumberMatrix(dataX, "data_x"), logM(toNumberMatrix(dataY, "data_y")), toBoolean(calculateB), toBoolean(verbose));
|
|
13998
14037
|
for (let i = 0; i < coeffs.length; i++) {
|
|
13999
14038
|
coeffs[i][0] = Math.exp(coeffs[i][0]);
|
|
14000
14039
|
}
|
|
@@ -14615,7 +14654,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
14615
14654
|
if (knownDataY.length === 0 || knownDataY[0].length === 0) {
|
|
14616
14655
|
return new EvaluationError(emptyDataErrorMessage("known_data_y"));
|
|
14617
14656
|
}
|
|
14618
|
-
return predictLinearValues(toNumberMatrix(knownDataY, "
|
|
14657
|
+
return predictLinearValues(toNumberMatrix(knownDataY, "known_data_y"), toNumberMatrix(knownDataX, "known_data_x"), toNumberMatrix(newDataX, "new_data_y"), toBoolean(b));
|
|
14619
14658
|
},
|
|
14620
14659
|
};
|
|
14621
14660
|
// -----------------------------------------------------------------------------
|
|
@@ -23136,6 +23175,10 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
23136
23175
|
}
|
|
23137
23176
|
const ctx = chart.ctx;
|
|
23138
23177
|
ctx.save();
|
|
23178
|
+
const { left, top, height, width } = chart.chartArea;
|
|
23179
|
+
ctx.beginPath();
|
|
23180
|
+
ctx.rect(left, top, width, height);
|
|
23181
|
+
ctx.clip();
|
|
23139
23182
|
ctx.textAlign = "center";
|
|
23140
23183
|
ctx.textBaseline = "middle";
|
|
23141
23184
|
ctx.miterLimit = 1; // Avoid sharp artifacts on strokeText
|
|
@@ -24179,7 +24222,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
24179
24222
|
this.chart.update();
|
|
24180
24223
|
}
|
|
24181
24224
|
hasChartDataChanged() {
|
|
24182
|
-
return !deepEquals(this.currentRuntime
|
|
24225
|
+
return !deepEquals(this.getChartDataInRuntime(this.currentRuntime), this.getChartDataInRuntime(this.chartRuntime));
|
|
24183
24226
|
}
|
|
24184
24227
|
enableAnimationInChartData(chartData) {
|
|
24185
24228
|
return {
|
|
@@ -24187,6 +24230,17 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
24187
24230
|
options: { ...chartData.options, animation: { animateRotate: true } },
|
|
24188
24231
|
};
|
|
24189
24232
|
}
|
|
24233
|
+
getChartDataInRuntime(runtime) {
|
|
24234
|
+
const data = runtime.chartJsConfig.data;
|
|
24235
|
+
return {
|
|
24236
|
+
labels: data.labels,
|
|
24237
|
+
dataset: data.datasets.map((dataset) => ({
|
|
24238
|
+
data: dataset.data,
|
|
24239
|
+
label: dataset.label,
|
|
24240
|
+
tree: dataset.tree,
|
|
24241
|
+
})),
|
|
24242
|
+
};
|
|
24243
|
+
}
|
|
24190
24244
|
get animationChartId() {
|
|
24191
24245
|
return this.props.isFullScreen ? this.props.chartId + "-fullscreen" : this.props.chartId;
|
|
24192
24246
|
}
|
|
@@ -25610,6 +25664,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
25610
25664
|
parser: luxonFormat,
|
|
25611
25665
|
displayFormats,
|
|
25612
25666
|
unit: timeUnit ?? false,
|
|
25667
|
+
tooltipFormat: luxonFormat,
|
|
25613
25668
|
};
|
|
25614
25669
|
}
|
|
25615
25670
|
/**
|
|
@@ -26678,6 +26733,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
26678
26733
|
};
|
|
26679
26734
|
Object.assign(scales.x, axis);
|
|
26680
26735
|
scales.x.ticks.maxTicksLimit = 15;
|
|
26736
|
+
delete scales?.x?.ticks?.callback;
|
|
26681
26737
|
}
|
|
26682
26738
|
else if (axisType === "linear") {
|
|
26683
26739
|
scales.x.type = "linear";
|
|
@@ -32363,7 +32419,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
32363
32419
|
}
|
|
32364
32420
|
openContextMenu(ev) {
|
|
32365
32421
|
this.menuState.isOpen = true;
|
|
32366
|
-
this.menuState.anchorRect =
|
|
32422
|
+
this.menuState.anchorRect = getBoundingRectAsPOJO(ev.currentTarget);
|
|
32367
32423
|
const figureId = this.env.model.getters.getFigureIdFromChartId(this.props.chartId);
|
|
32368
32424
|
this.menuState.menuItems = getChartMenuActions(figureId, () => { }, this.env);
|
|
32369
32425
|
}
|
|
@@ -32395,6 +32451,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
32395
32451
|
onFigureDeleted: Function,
|
|
32396
32452
|
editFigureStyle: { type: Function, optional: true },
|
|
32397
32453
|
isFullScreen: { type: Boolean, optional: true },
|
|
32454
|
+
openContextMenu: { type: Function, optional: true },
|
|
32398
32455
|
};
|
|
32399
32456
|
static components = { ChartDashboardMenu, MenuPopover };
|
|
32400
32457
|
carouselTabsRef = owl.useRef("carouselTabs");
|
|
@@ -32528,6 +32585,12 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
32528
32585
|
get visibleCarouselItems() {
|
|
32529
32586
|
return this.carousel.items.filter((item) => item.type === "carouselDataView" && this.props.isFullScreen ? false : true);
|
|
32530
32587
|
}
|
|
32588
|
+
openContextMenu(event) {
|
|
32589
|
+
const target = event.currentTarget;
|
|
32590
|
+
if (target) {
|
|
32591
|
+
this.props.openContextMenu?.(getBoundingRectAsPOJO(target));
|
|
32592
|
+
}
|
|
32593
|
+
}
|
|
32531
32594
|
}
|
|
32532
32595
|
|
|
32533
32596
|
class ChartFigure extends owl.Component {
|
|
@@ -32537,6 +32600,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
32537
32600
|
onFigureDeleted: Function,
|
|
32538
32601
|
editFigureStyle: { type: Function, optional: true },
|
|
32539
32602
|
isFullScreen: { type: Boolean, optional: true },
|
|
32603
|
+
openContextMenu: { type: Function, optional: true },
|
|
32540
32604
|
};
|
|
32541
32605
|
static components = { ChartDashboardMenu };
|
|
32542
32606
|
onDoubleClick() {
|
|
@@ -32569,6 +32633,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
32569
32633
|
figureUI: Object,
|
|
32570
32634
|
onFigureDeleted: Function,
|
|
32571
32635
|
editFigureStyle: { type: Function, optional: true },
|
|
32636
|
+
openContextMenu: { type: Function, optional: true },
|
|
32572
32637
|
};
|
|
32573
32638
|
static components = {};
|
|
32574
32639
|
// ---------------------------------------------------------------------------
|
|
@@ -34620,8 +34685,11 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
34620
34685
|
}
|
|
34621
34686
|
const newSelection = this.contentHelper.getCurrentSelection();
|
|
34622
34687
|
this.props.composerStore.stopComposerRangeSelection();
|
|
34623
|
-
this.props.
|
|
34624
|
-
this.props.
|
|
34688
|
+
const isCurrentlyInactive = this.props.composerStore.editionMode === "inactive";
|
|
34689
|
+
this.props.onComposerContentFocused(newSelection);
|
|
34690
|
+
if (!isCurrentlyInactive) {
|
|
34691
|
+
this.props.composerStore.changeComposerCursorSelection(newSelection.start, newSelection.end);
|
|
34692
|
+
}
|
|
34625
34693
|
this.processTokenAtCursor();
|
|
34626
34694
|
}
|
|
34627
34695
|
onDblClick() {
|
|
@@ -35116,13 +35184,6 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
35116
35184
|
}
|
|
35117
35185
|
}
|
|
35118
35186
|
startEdition(text, selection) {
|
|
35119
|
-
if (selection) {
|
|
35120
|
-
const content = text || this.getComposerContent(this.getters.getActivePosition());
|
|
35121
|
-
const validSelection = this.isSelectionValid(content.length, selection.start, selection.end);
|
|
35122
|
-
if (!validSelection) {
|
|
35123
|
-
return;
|
|
35124
|
-
}
|
|
35125
|
-
}
|
|
35126
35187
|
const { col, row } = this.getters.getActivePosition();
|
|
35127
35188
|
this.model.dispatch("SELECT_FIGURE", { figureId: null });
|
|
35128
35189
|
this.model.dispatch("SCROLL_TO_CELL", { col, row });
|
|
@@ -35179,7 +35240,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
35179
35240
|
// ---------------------------------------------------------------------------
|
|
35180
35241
|
get currentContent() {
|
|
35181
35242
|
if (this.editionMode === "inactive") {
|
|
35182
|
-
return this.getComposerContent(this.getters.getActivePosition());
|
|
35243
|
+
return this.getComposerContent(this.getters.getActivePosition()).text;
|
|
35183
35244
|
}
|
|
35184
35245
|
return this._currentContent;
|
|
35185
35246
|
}
|
|
@@ -35378,8 +35439,9 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
35378
35439
|
this.sheetId = sheetId;
|
|
35379
35440
|
this.row = row;
|
|
35380
35441
|
this.editionMode = "editing";
|
|
35381
|
-
|
|
35382
|
-
this.
|
|
35442
|
+
const { text, adjustedSelection } = this.getComposerContent({ sheetId, col, row }, selection);
|
|
35443
|
+
this.initialContent = text;
|
|
35444
|
+
this.setContent(str || this.initialContent, adjustedSelection ?? selection);
|
|
35383
35445
|
this.colorIndexByRange = {};
|
|
35384
35446
|
const zone = positionToZone({ col: this.col, row: this.row });
|
|
35385
35447
|
this.captureSelection(zone, col, row);
|
|
@@ -35856,7 +35918,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
35856
35918
|
constructor(get, args) {
|
|
35857
35919
|
super(get);
|
|
35858
35920
|
this.args = args;
|
|
35859
|
-
this._currentContent = this.getComposerContent();
|
|
35921
|
+
this._currentContent = this.getComposerContent().text;
|
|
35860
35922
|
}
|
|
35861
35923
|
getAutoCompleteProviders() {
|
|
35862
35924
|
const providersDefinitions = super.getAutoCompleteProviders();
|
|
@@ -35893,7 +35955,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
35893
35955
|
})
|
|
35894
35956
|
.join("");
|
|
35895
35957
|
}
|
|
35896
|
-
return localizeContent(content, this.getters.getLocale());
|
|
35958
|
+
return { text: localizeContent(content, this.getters.getLocale()) };
|
|
35897
35959
|
}
|
|
35898
35960
|
stopEdition() {
|
|
35899
35961
|
this._stopEdition();
|
|
@@ -38663,12 +38725,23 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
38663
38725
|
.add("LinkEditor", LinkEditorPopoverBuilder)
|
|
38664
38726
|
.add("FilterMenu", FilterMenuPopoverBuilder);
|
|
38665
38727
|
|
|
38666
|
-
const
|
|
38667
|
-
|
|
38668
|
-
|
|
38669
|
-
|
|
38670
|
-
|
|
38671
|
-
|
|
38728
|
+
const DEFAULT_BAR_CHART_CONFIG = {
|
|
38729
|
+
type: "bar",
|
|
38730
|
+
title: {},
|
|
38731
|
+
dataSets: [],
|
|
38732
|
+
legendPosition: "none",
|
|
38733
|
+
dataSetsHaveTitle: false,
|
|
38734
|
+
stacked: false,
|
|
38735
|
+
};
|
|
38736
|
+
const DEFAULT_LINE_CHART_CONFIG = {
|
|
38737
|
+
type: "line",
|
|
38738
|
+
title: {},
|
|
38739
|
+
dataSets: [],
|
|
38740
|
+
legendPosition: "none",
|
|
38741
|
+
dataSetsHaveTitle: false,
|
|
38742
|
+
stacked: false,
|
|
38743
|
+
cumulative: false,
|
|
38744
|
+
labelsAsText: false,
|
|
38672
38745
|
};
|
|
38673
38746
|
function getUnboundRange(getters, zone) {
|
|
38674
38747
|
return zoneToXc(getters.getUnboundedZone(getters.getActiveSheetId(), zone));
|
|
@@ -38707,43 +38780,19 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
38707
38780
|
return detectedType;
|
|
38708
38781
|
}
|
|
38709
38782
|
function categorizeColumns(zones, getters) {
|
|
38710
|
-
const columns =
|
|
38711
|
-
number: [],
|
|
38712
|
-
text: [],
|
|
38713
|
-
date: [],
|
|
38714
|
-
};
|
|
38783
|
+
const columns = [];
|
|
38715
38784
|
for (const zone of getZonesByColumns(zones)) {
|
|
38716
38785
|
const cells = getters.getEvaluatedCellsInZone(getters.getActiveSheetId(), zone);
|
|
38717
|
-
|
|
38718
|
-
if (type !== "empty") {
|
|
38719
|
-
const targetType = type === "percentage" ? "number" : type;
|
|
38720
|
-
columns[targetType].push({ zone, type });
|
|
38721
|
-
}
|
|
38786
|
+
columns.push({ zone, type: detectColumnType(cells) });
|
|
38722
38787
|
}
|
|
38723
38788
|
return columns;
|
|
38724
38789
|
}
|
|
38725
38790
|
function getCellStats(getters, zone) {
|
|
38726
38791
|
const cells = getters.getEvaluatedCellsInZone(getters.getActiveSheetId(), zone);
|
|
38727
|
-
const
|
|
38728
|
-
let totalCount = 0;
|
|
38729
|
-
let percentageSum = 0;
|
|
38730
|
-
for (let i = 0; i < cells.length; i++) {
|
|
38731
|
-
const { value } = cells[i];
|
|
38732
|
-
const str = value?.toString().trim();
|
|
38733
|
-
if (!str) {
|
|
38734
|
-
continue;
|
|
38735
|
-
}
|
|
38736
|
-
uniqueValues.add(str);
|
|
38737
|
-
totalCount++;
|
|
38738
|
-
const num = Number(value);
|
|
38739
|
-
if (!isNaN(num)) {
|
|
38740
|
-
percentageSum += Math.abs(num) * 100;
|
|
38741
|
-
}
|
|
38742
|
-
}
|
|
38792
|
+
const values = cells.map((c) => c.value?.toString().trim() || "").filter((s) => s);
|
|
38743
38793
|
return {
|
|
38744
|
-
uniqueCount:
|
|
38745
|
-
totalCount,
|
|
38746
|
-
percentageSum,
|
|
38794
|
+
uniqueCount: new Set(values).size,
|
|
38795
|
+
totalCount: values.length,
|
|
38747
38796
|
};
|
|
38748
38797
|
}
|
|
38749
38798
|
function isDatasetTitled(getters, column) {
|
|
@@ -38754,167 +38803,191 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
38754
38803
|
});
|
|
38755
38804
|
return ![CellValueType.number, CellValueType.empty].includes(titleCell.type);
|
|
38756
38805
|
}
|
|
38757
|
-
|
|
38758
|
-
|
|
38759
|
-
|
|
38760
|
-
|
|
38761
|
-
|
|
38762
|
-
|
|
38763
|
-
|
|
38764
|
-
|
|
38765
|
-
}
|
|
38806
|
+
/**
|
|
38807
|
+
* Builds a chart definition for a single column selection. The logic to detect the chart type is as follows:
|
|
38808
|
+
* - If the column contains a single cell, create a scorecard.
|
|
38809
|
+
* - If the column type is "percentage", create a pie chart.
|
|
38810
|
+
* - If the column type is "text", create a pie chart
|
|
38811
|
+
* - If the column type is "date", create a line chart.
|
|
38812
|
+
* - Otherwise, create a bar chart.
|
|
38813
|
+
*/
|
|
38766
38814
|
function buildSingleColumnChart(column, getters) {
|
|
38767
38815
|
const { type, zone } = column;
|
|
38768
38816
|
const sheetId = getters.getActiveSheetId();
|
|
38769
38817
|
const dataSetsHaveTitle = isDatasetTitled(getters, column);
|
|
38770
38818
|
const dataRange = getUnboundRange(getters, zone);
|
|
38771
38819
|
const titleCell = getters.getEvaluatedCell({ sheetId, col: zone.left, row: zone.top });
|
|
38820
|
+
if (getZoneArea(zone) === 1) {
|
|
38821
|
+
return buildScorecard(zone, getters);
|
|
38822
|
+
}
|
|
38772
38823
|
switch (type) {
|
|
38773
38824
|
case "percentage":
|
|
38774
|
-
|
|
38775
|
-
|
|
38825
|
+
return {
|
|
38826
|
+
type: "pie",
|
|
38776
38827
|
title: dataSetsHaveTitle ? { text: String(titleCell.value) } : {},
|
|
38828
|
+
dataSets: [{ dataRange }],
|
|
38829
|
+
legendPosition: "none",
|
|
38777
38830
|
dataSetsHaveTitle,
|
|
38778
|
-
|
|
38779
|
-
});
|
|
38831
|
+
};
|
|
38780
38832
|
case "text":
|
|
38781
38833
|
const cells = getters.getEvaluatedCellsInZone(sheetId, zone);
|
|
38782
38834
|
const titleCount = cells.reduce((count, cell) => (cell.value === titleCell.value ? count + 1 : count), 0);
|
|
38783
38835
|
const hasUniqueTitle = titleCell.value !== null && titleCount === 1;
|
|
38784
|
-
return
|
|
38836
|
+
return {
|
|
38837
|
+
type: "pie",
|
|
38785
38838
|
title: hasUniqueTitle ? { text: String(titleCell.value) } : {},
|
|
38839
|
+
dataSets: [{ dataRange }],
|
|
38786
38840
|
labelRange: dataRange,
|
|
38787
38841
|
dataSetsHaveTitle: hasUniqueTitle,
|
|
38788
|
-
isDoughnut: false,
|
|
38789
38842
|
aggregated: true,
|
|
38790
38843
|
legendPosition: "top",
|
|
38791
|
-
}
|
|
38792
|
-
// TODO: Handle date column with matrix chart when matrix chart is supported
|
|
38844
|
+
};
|
|
38793
38845
|
case "date":
|
|
38794
|
-
return
|
|
38795
|
-
|
|
38846
|
+
return {
|
|
38847
|
+
...DEFAULT_LINE_CHART_CONFIG,
|
|
38848
|
+
type: "line",
|
|
38849
|
+
title: dataSetsHaveTitle ? { text: String(titleCell.value) } : {},
|
|
38850
|
+
dataSets: [{ dataRange }],
|
|
38796
38851
|
dataSetsHaveTitle,
|
|
38797
|
-
|
|
38798
|
-
labelsAsText: false,
|
|
38799
|
-
});
|
|
38852
|
+
};
|
|
38800
38853
|
}
|
|
38801
|
-
return
|
|
38854
|
+
return {
|
|
38855
|
+
...DEFAULT_BAR_CHART_CONFIG,
|
|
38856
|
+
title: dataSetsHaveTitle ? { text: String(titleCell.value) } : {},
|
|
38857
|
+
dataSets: [{ dataRange }],
|
|
38858
|
+
dataSetsHaveTitle,
|
|
38859
|
+
};
|
|
38802
38860
|
}
|
|
38861
|
+
/**
|
|
38862
|
+
* Builds a chart definition for a selection of two columns. The logic to detect the chart type always consider the
|
|
38863
|
+
* columns left to right, and is as follows:
|
|
38864
|
+
* - any type + percentage columns: pie chart
|
|
38865
|
+
* - number + number columns: scatter chart
|
|
38866
|
+
* - date + number columns: line chart
|
|
38867
|
+
* - text + number columns: treemap if repetition in labels
|
|
38868
|
+
* - any other combination: bar chart
|
|
38869
|
+
*/
|
|
38803
38870
|
function buildTwoColumnChart(columns, getters) {
|
|
38804
|
-
|
|
38805
|
-
|
|
38806
|
-
return createBaseChart("scatter", [{ dataRange: getUnboundRange(getters, numberColumns[1].zone) }], {
|
|
38807
|
-
labelRange: getUnboundRange(getters, numberColumns[0].zone),
|
|
38808
|
-
dataSetsHaveTitle: isDatasetTitled(getters, numberColumns[1]),
|
|
38809
|
-
labelsAsText: false,
|
|
38810
|
-
});
|
|
38871
|
+
if (columns.length !== 2) {
|
|
38872
|
+
throw new Error("buildTwoColumnChart expects exactly two columns");
|
|
38811
38873
|
}
|
|
38812
|
-
|
|
38813
|
-
|
|
38814
|
-
|
|
38815
|
-
|
|
38816
|
-
|
|
38817
|
-
|
|
38818
|
-
|
|
38874
|
+
if (columns[1].type === "percentage") {
|
|
38875
|
+
return {
|
|
38876
|
+
type: "pie",
|
|
38877
|
+
title: {},
|
|
38878
|
+
dataSets: [{ dataRange: getUnboundRange(getters, columns[1].zone) }],
|
|
38879
|
+
labelRange: getUnboundRange(getters, columns[0].zone),
|
|
38880
|
+
dataSetsHaveTitle: isDatasetTitled(getters, columns[1]),
|
|
38881
|
+
aggregated: true,
|
|
38882
|
+
legendPosition: "none",
|
|
38883
|
+
};
|
|
38884
|
+
}
|
|
38885
|
+
if (columns[0].type === "number" && columns[1].type === "number") {
|
|
38886
|
+
return {
|
|
38887
|
+
type: "scatter",
|
|
38888
|
+
title: {},
|
|
38889
|
+
dataSets: [{ dataRange: getUnboundRange(getters, columns[1].zone) }],
|
|
38890
|
+
labelRange: getUnboundRange(getters, columns[0].zone),
|
|
38891
|
+
dataSetsHaveTitle: isDatasetTitled(getters, columns[1]),
|
|
38819
38892
|
labelsAsText: false,
|
|
38820
|
-
|
|
38893
|
+
legendPosition: "none",
|
|
38894
|
+
};
|
|
38895
|
+
}
|
|
38896
|
+
// TODO: Handle date + number with calendar chart when implemented (and change the docstring)
|
|
38897
|
+
if (columns[0].type === "date" && columns[1].type === "number") {
|
|
38898
|
+
return {
|
|
38899
|
+
...DEFAULT_LINE_CHART_CONFIG,
|
|
38900
|
+
type: "line",
|
|
38901
|
+
dataSets: [{ dataRange: getUnboundRange(getters, columns[1].zone) }],
|
|
38902
|
+
labelRange: getUnboundRange(getters, columns[0].zone),
|
|
38903
|
+
dataSetsHaveTitle: isDatasetTitled(getters, columns[0]),
|
|
38904
|
+
};
|
|
38821
38905
|
}
|
|
38822
|
-
if (
|
|
38823
|
-
const
|
|
38824
|
-
const
|
|
38906
|
+
if (columns[0].type === "text" && columns[1].type === "number") {
|
|
38907
|
+
const textColumn = columns[0];
|
|
38908
|
+
const numberColumn = columns[1];
|
|
38825
38909
|
const { uniqueCount, totalCount } = getCellStats(getters, textColumn.zone);
|
|
38826
38910
|
const dataSetsHaveTitle = isDatasetTitled(getters, numberColumn);
|
|
38827
|
-
const maxCategories = dataSetsHaveTitle
|
|
38828
|
-
? CHART_LIMITS.MAX_PIE_CATEGORIES
|
|
38829
|
-
: CHART_LIMITS.MAX_PIE_CATEGORIES_NO_TITLE;
|
|
38830
|
-
const labelRange = getUnboundRange(getters, textColumn.zone);
|
|
38831
|
-
const dataRange = getUnboundRange(getters, numberColumn.zone);
|
|
38832
|
-
if (uniqueCount <= maxCategories) {
|
|
38833
|
-
const { percentageSum } = getCellStats(getters, numberColumn.zone);
|
|
38834
|
-
return createBaseChart("pie", [{ dataRange }], {
|
|
38835
|
-
labelRange,
|
|
38836
|
-
dataSetsHaveTitle,
|
|
38837
|
-
isDoughnut: numberColumn.type === "percentage" && percentageSum < CHART_LIMITS.PERCENTAGE_THRESHOLD,
|
|
38838
|
-
aggregated: true,
|
|
38839
|
-
legendPosition: "top",
|
|
38840
|
-
});
|
|
38841
|
-
}
|
|
38842
|
-
// Use treemap when categories repeat, as pie chart would be cluttered
|
|
38843
38911
|
if (uniqueCount !== totalCount) {
|
|
38844
|
-
return
|
|
38845
|
-
|
|
38912
|
+
return {
|
|
38913
|
+
type: "treemap",
|
|
38914
|
+
title: {},
|
|
38915
|
+
dataSets: [{ dataRange: getUnboundRange(getters, textColumn.zone) }],
|
|
38916
|
+
labelRange: getUnboundRange(getters, numberColumn.zone),
|
|
38846
38917
|
dataSetsHaveTitle,
|
|
38847
|
-
|
|
38918
|
+
legendPosition: "none",
|
|
38919
|
+
};
|
|
38848
38920
|
}
|
|
38849
|
-
return createBaseChart("bar", [{ dataRange }], {
|
|
38850
|
-
labelRange,
|
|
38851
|
-
dataSetsHaveTitle,
|
|
38852
|
-
});
|
|
38853
38921
|
}
|
|
38854
|
-
|
|
38855
|
-
|
|
38856
|
-
|
|
38857
|
-
labelRange: getUnboundRange(getters,
|
|
38858
|
-
dataSetsHaveTitle: isDatasetTitled(getters,
|
|
38859
|
-
|
|
38860
|
-
labelsAsText: true,
|
|
38861
|
-
});
|
|
38922
|
+
return {
|
|
38923
|
+
...DEFAULT_BAR_CHART_CONFIG,
|
|
38924
|
+
dataSets: [{ dataRange: getUnboundRange(getters, columns[1].zone) }],
|
|
38925
|
+
labelRange: getUnboundRange(getters, columns[0].zone),
|
|
38926
|
+
dataSetsHaveTitle: isDatasetTitled(getters, columns[1]),
|
|
38927
|
+
};
|
|
38862
38928
|
}
|
|
38929
|
+
/**
|
|
38930
|
+
* Builds a chart definition for a selection more than two columns. The logic to detect the chart type always consider
|
|
38931
|
+
* the columns left to right, and is as follows:
|
|
38932
|
+
* - multiple text + single number/percentage columns: sunburst if 3+ text columns, treemap otherwise
|
|
38933
|
+
* - any type + multiple percentage columns: pie chart
|
|
38934
|
+
* - date + multiple number columns: line chart
|
|
38935
|
+
* - any other combination: bar chart
|
|
38936
|
+
*/
|
|
38863
38937
|
function buildMultiColumnChart(columns, getters) {
|
|
38864
|
-
|
|
38865
|
-
|
|
38866
|
-
|
|
38867
|
-
|
|
38868
|
-
|
|
38938
|
+
if (columns.length < 3) {
|
|
38939
|
+
throw new Error("buildMultiColumnChart expects at least three columns");
|
|
38940
|
+
}
|
|
38941
|
+
const dataSetsHaveTitle = columns.some((col) => col.type !== "text" && isDatasetTitled(getters, col));
|
|
38942
|
+
const lastColumn = columns[columns.length - 1];
|
|
38943
|
+
const columnsExceptLast = columns.slice(0, columns.length - 1);
|
|
38944
|
+
if ((lastColumn.type === "percentage" || lastColumn.type === "number") &&
|
|
38945
|
+
columnsExceptLast.every((col) => col.type === "text")) {
|
|
38946
|
+
const dataSets = columnsExceptLast.map(({ zone }) => ({
|
|
38869
38947
|
dataRange: getUnboundRange(getters, zone),
|
|
38870
38948
|
}));
|
|
38871
|
-
return
|
|
38872
|
-
|
|
38949
|
+
return {
|
|
38950
|
+
type: columnsExceptLast.length >= 3 ? "sunburst" : "treemap",
|
|
38951
|
+
title: {},
|
|
38952
|
+
dataSets,
|
|
38953
|
+
labelRange: getUnboundRange(getters, lastColumn.zone),
|
|
38873
38954
|
dataSetsHaveTitle,
|
|
38874
|
-
|
|
38955
|
+
legendPosition: "none",
|
|
38956
|
+
};
|
|
38875
38957
|
}
|
|
38876
|
-
const
|
|
38958
|
+
const firstColumn = columns[0];
|
|
38959
|
+
const columnsExceptFirst = columns.slice(1);
|
|
38960
|
+
const rangesOfColumnsExceptFirst = columnsExceptFirst.map(({ zone }) => ({
|
|
38877
38961
|
dataRange: getUnboundRange(getters, zone),
|
|
38878
38962
|
}));
|
|
38879
|
-
if (
|
|
38880
|
-
return
|
|
38881
|
-
|
|
38963
|
+
if (columnsExceptFirst.every((col) => col.type === "percentage")) {
|
|
38964
|
+
return {
|
|
38965
|
+
type: "pie",
|
|
38966
|
+
title: {},
|
|
38967
|
+
dataSets: rangesOfColumnsExceptFirst,
|
|
38968
|
+
labelRange: getUnboundRange(getters, firstColumn.zone),
|
|
38882
38969
|
dataSetsHaveTitle,
|
|
38883
|
-
|
|
38884
|
-
labelsAsText: false,
|
|
38970
|
+
aggregated: false,
|
|
38885
38971
|
legendPosition: "top",
|
|
38886
|
-
}
|
|
38972
|
+
};
|
|
38887
38973
|
}
|
|
38888
|
-
if (
|
|
38889
|
-
|
|
38890
|
-
|
|
38891
|
-
|
|
38892
|
-
|
|
38893
|
-
|
|
38894
|
-
|
|
38895
|
-
|
|
38896
|
-
|
|
38897
|
-
const expectedDataCount = categoryCount * numberColumns.length + (dataSetsHaveTitle ? numberColumns.length : 0);
|
|
38898
|
-
const actualDataCount = numberColumns.reduce((sum, dataCol) => sum + getCellStats(getters, dataCol.zone).totalCount, 0);
|
|
38899
|
-
if (uniqueCount === totalCount &&
|
|
38900
|
-
uniqueCount >= CHART_LIMITS.MIN_RADAR_CATEGORIES &&
|
|
38901
|
-
uniqueCount <= CHART_LIMITS.MAX_RADAR_CATEGORIES &&
|
|
38902
|
-
expectedDataCount === actualDataCount) {
|
|
38903
|
-
return createBaseChart("radar", dataSets, {
|
|
38904
|
-
title: dataSetsHaveTitle && firstCell.value ? { text: String(firstCell.value) } : {},
|
|
38905
|
-
labelRange: getUnboundRange(getters, textColumn.zone),
|
|
38906
|
-
dataSetsHaveTitle,
|
|
38907
|
-
legendPosition: "top",
|
|
38908
|
-
});
|
|
38909
|
-
}
|
|
38974
|
+
if (firstColumn.type === "date" && columnsExceptFirst.every((col) => col.type === "number")) {
|
|
38975
|
+
return {
|
|
38976
|
+
...DEFAULT_LINE_CHART_CONFIG,
|
|
38977
|
+
type: "line",
|
|
38978
|
+
dataSets: rangesOfColumnsExceptFirst,
|
|
38979
|
+
labelRange: getUnboundRange(getters, firstColumn.zone),
|
|
38980
|
+
dataSetsHaveTitle,
|
|
38981
|
+
legendPosition: "top",
|
|
38982
|
+
};
|
|
38910
38983
|
}
|
|
38911
|
-
|
|
38912
|
-
|
|
38913
|
-
|
|
38984
|
+
return {
|
|
38985
|
+
...DEFAULT_BAR_CHART_CONFIG,
|
|
38986
|
+
dataSets: rangesOfColumnsExceptFirst,
|
|
38987
|
+
labelRange: getUnboundRange(getters, firstColumn.zone),
|
|
38914
38988
|
dataSetsHaveTitle,
|
|
38915
|
-
aggregated: true,
|
|
38916
38989
|
legendPosition: "top",
|
|
38917
|
-
}
|
|
38990
|
+
};
|
|
38918
38991
|
}
|
|
38919
38992
|
function buildScorecard(zone, getters) {
|
|
38920
38993
|
const cell = getters.getCell({
|
|
@@ -38937,22 +39010,18 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
38937
39010
|
*/
|
|
38938
39011
|
function getSmartChartDefinition(zones, getters) {
|
|
38939
39012
|
const columns = categorizeColumns(zones, getters);
|
|
38940
|
-
|
|
38941
|
-
|
|
38942
|
-
|
|
38943
|
-
|
|
38944
|
-
|
|
38945
|
-
|
|
38946
|
-
});
|
|
39013
|
+
if (columns.length === 0 || columns.every((col) => col.type === "empty")) {
|
|
39014
|
+
const dataSets = columns.map(({ zone }) => ({ dataRange: getUnboundRange(getters, zone) }));
|
|
39015
|
+
return { ...DEFAULT_BAR_CHART_CONFIG, dataSets };
|
|
39016
|
+
}
|
|
39017
|
+
const nonEmptyColumns = columns.filter((col) => col.type !== "empty");
|
|
39018
|
+
switch (nonEmptyColumns.length) {
|
|
38947
39019
|
case 1:
|
|
38948
|
-
|
|
38949
|
-
return getZoneArea(singleColumn.zone) === 1
|
|
38950
|
-
? buildScorecard(singleColumn.zone, getters)
|
|
38951
|
-
: buildSingleColumnChart(singleColumn, getters);
|
|
39020
|
+
return buildSingleColumnChart(nonEmptyColumns[0], getters);
|
|
38952
39021
|
case 2:
|
|
38953
|
-
return buildTwoColumnChart(
|
|
39022
|
+
return buildTwoColumnChart(nonEmptyColumns, getters);
|
|
38954
39023
|
default:
|
|
38955
|
-
return buildMultiColumnChart(
|
|
39024
|
+
return buildMultiColumnChart(nonEmptyColumns, getters);
|
|
38956
39025
|
}
|
|
38957
39026
|
}
|
|
38958
39027
|
|
|
@@ -39571,6 +39640,74 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
39571
39640
|
return path2D;
|
|
39572
39641
|
}
|
|
39573
39642
|
|
|
39643
|
+
/**
|
|
39644
|
+
* Get the relative path between two files
|
|
39645
|
+
*
|
|
39646
|
+
* Eg.:
|
|
39647
|
+
* from "folder1/file1.txt" to "folder2/file2.txt" => "../folder2/file2.txt"
|
|
39648
|
+
*/
|
|
39649
|
+
function getRelativePath(from, to) {
|
|
39650
|
+
const fromPathParts = from.split("/");
|
|
39651
|
+
const toPathParts = to.split("/");
|
|
39652
|
+
let relPath = "";
|
|
39653
|
+
let startIndex = 0;
|
|
39654
|
+
for (let i = 0; i < fromPathParts.length - 1; i++) {
|
|
39655
|
+
if (fromPathParts[i] === toPathParts[i]) {
|
|
39656
|
+
startIndex++;
|
|
39657
|
+
}
|
|
39658
|
+
else {
|
|
39659
|
+
relPath += "../";
|
|
39660
|
+
}
|
|
39661
|
+
}
|
|
39662
|
+
relPath += toPathParts.slice(startIndex).join("/");
|
|
39663
|
+
return relPath;
|
|
39664
|
+
}
|
|
39665
|
+
/**
|
|
39666
|
+
* Convert an array of element into an object where the objects keys were the elements position in the array.
|
|
39667
|
+
* Can give an offset as argument, and all the array indexes will we shifted by this offset in the returned object.
|
|
39668
|
+
*
|
|
39669
|
+
* eg. : ["a", "b"] => {0:"a", 1:"b"}
|
|
39670
|
+
*/
|
|
39671
|
+
function arrayToObject(array, indexOffset = 0) {
|
|
39672
|
+
const obj = {};
|
|
39673
|
+
for (let i = 0; i < array.length; i++) {
|
|
39674
|
+
if (array[i]) {
|
|
39675
|
+
obj[i + indexOffset] = array[i];
|
|
39676
|
+
}
|
|
39677
|
+
}
|
|
39678
|
+
return obj;
|
|
39679
|
+
}
|
|
39680
|
+
/**
|
|
39681
|
+
* In xlsx we can have string with unicode characters with the format _x00fa_.
|
|
39682
|
+
* Replace with characters understandable by JS
|
|
39683
|
+
*/
|
|
39684
|
+
function fixXlsxUnicode(str) {
|
|
39685
|
+
return str.replace(/_x([0-9a-zA-Z]{4})_/g, (match, code) => {
|
|
39686
|
+
return String.fromCharCode(parseInt(code, 16));
|
|
39687
|
+
});
|
|
39688
|
+
}
|
|
39689
|
+
/** Get a header in the SheetData. Create the header if it doesn't exist in the SheetData */
|
|
39690
|
+
function getSheetDataHeader(sheetData, dimension, index) {
|
|
39691
|
+
if (dimension === "COL") {
|
|
39692
|
+
if (!sheetData.cols[index]) {
|
|
39693
|
+
sheetData.cols[index] = {};
|
|
39694
|
+
}
|
|
39695
|
+
return sheetData.cols[index];
|
|
39696
|
+
}
|
|
39697
|
+
if (!sheetData.rows[index]) {
|
|
39698
|
+
sheetData.rows[index] = {};
|
|
39699
|
+
}
|
|
39700
|
+
return sheetData.rows[index];
|
|
39701
|
+
}
|
|
39702
|
+
/** Prefix the string by "=" if the string looks like a formula */
|
|
39703
|
+
function prefixFormulaWithEqual(formula) {
|
|
39704
|
+
if (formula[0] === "=") {
|
|
39705
|
+
return formula;
|
|
39706
|
+
}
|
|
39707
|
+
const tokens = tokenize(formula);
|
|
39708
|
+
return tokens.length === 1 && tokens[0].type !== "REFERENCE" ? formula : "=" + formula;
|
|
39709
|
+
}
|
|
39710
|
+
|
|
39574
39711
|
/**
|
|
39575
39712
|
* Map of the different types of conversions warnings and their name in error messages
|
|
39576
39713
|
*/
|
|
@@ -40093,66 +40230,6 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
40093
40230
|
*/
|
|
40094
40231
|
const DEFAULT_SYSTEM_COLOR = "FF000000";
|
|
40095
40232
|
|
|
40096
|
-
/**
|
|
40097
|
-
* Get the relative path between two files
|
|
40098
|
-
*
|
|
40099
|
-
* Eg.:
|
|
40100
|
-
* from "folder1/file1.txt" to "folder2/file2.txt" => "../folder2/file2.txt"
|
|
40101
|
-
*/
|
|
40102
|
-
function getRelativePath(from, to) {
|
|
40103
|
-
const fromPathParts = from.split("/");
|
|
40104
|
-
const toPathParts = to.split("/");
|
|
40105
|
-
let relPath = "";
|
|
40106
|
-
let startIndex = 0;
|
|
40107
|
-
for (let i = 0; i < fromPathParts.length - 1; i++) {
|
|
40108
|
-
if (fromPathParts[i] === toPathParts[i]) {
|
|
40109
|
-
startIndex++;
|
|
40110
|
-
}
|
|
40111
|
-
else {
|
|
40112
|
-
relPath += "../";
|
|
40113
|
-
}
|
|
40114
|
-
}
|
|
40115
|
-
relPath += toPathParts.slice(startIndex).join("/");
|
|
40116
|
-
return relPath;
|
|
40117
|
-
}
|
|
40118
|
-
/**
|
|
40119
|
-
* Convert an array of element into an object where the objects keys were the elements position in the array.
|
|
40120
|
-
* Can give an offset as argument, and all the array indexes will we shifted by this offset in the returned object.
|
|
40121
|
-
*
|
|
40122
|
-
* eg. : ["a", "b"] => {0:"a", 1:"b"}
|
|
40123
|
-
*/
|
|
40124
|
-
function arrayToObject(array, indexOffset = 0) {
|
|
40125
|
-
const obj = {};
|
|
40126
|
-
for (let i = 0; i < array.length; i++) {
|
|
40127
|
-
if (array[i]) {
|
|
40128
|
-
obj[i + indexOffset] = array[i];
|
|
40129
|
-
}
|
|
40130
|
-
}
|
|
40131
|
-
return obj;
|
|
40132
|
-
}
|
|
40133
|
-
/**
|
|
40134
|
-
* In xlsx we can have string with unicode characters with the format _x00fa_.
|
|
40135
|
-
* Replace with characters understandable by JS
|
|
40136
|
-
*/
|
|
40137
|
-
function fixXlsxUnicode(str) {
|
|
40138
|
-
return str.replace(/_x([0-9a-zA-Z]{4})_/g, (match, code) => {
|
|
40139
|
-
return String.fromCharCode(parseInt(code, 16));
|
|
40140
|
-
});
|
|
40141
|
-
}
|
|
40142
|
-
/** Get a header in the SheetData. Create the header if it doesn't exist in the SheetData */
|
|
40143
|
-
function getSheetDataHeader(sheetData, dimension, index) {
|
|
40144
|
-
if (dimension === "COL") {
|
|
40145
|
-
if (!sheetData.cols[index]) {
|
|
40146
|
-
sheetData.cols[index] = {};
|
|
40147
|
-
}
|
|
40148
|
-
return sheetData.cols[index];
|
|
40149
|
-
}
|
|
40150
|
-
if (!sheetData.rows[index]) {
|
|
40151
|
-
sheetData.rows[index] = {};
|
|
40152
|
-
}
|
|
40153
|
-
return sheetData.rows[index];
|
|
40154
|
-
}
|
|
40155
|
-
|
|
40156
40233
|
const XLSX_DATE_FORMAT_REGEX = /^(yy|yyyy|m{1,5}|d{1,4}|h{1,2}|s{1,2}|am\/pm|a\/m|\s|-|\/|\.|:)+$/i;
|
|
40157
40234
|
/**
|
|
40158
40235
|
* Convert excel format to o_spreadsheet format
|
|
@@ -40367,9 +40444,9 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
40367
40444
|
if (!rule.operator || !rule.formula || rule.formula.length === 0)
|
|
40368
40445
|
continue;
|
|
40369
40446
|
operator = CF_OPERATOR_TYPE_CONVERSION_MAP[rule.operator];
|
|
40370
|
-
values.push(
|
|
40447
|
+
values.push(prefixFormulaWithEqual(rule.formula[0]));
|
|
40371
40448
|
if (rule.formula.length === 2) {
|
|
40372
|
-
values.push(
|
|
40449
|
+
values.push(prefixFormulaWithEqual(rule.formula[1]));
|
|
40373
40450
|
}
|
|
40374
40451
|
break;
|
|
40375
40452
|
}
|
|
@@ -40527,11 +40604,6 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
40527
40604
|
? ICON_SETS[iconSet].neutral
|
|
40528
40605
|
: ICON_SETS[iconSet].good;
|
|
40529
40606
|
}
|
|
40530
|
-
/** Prefix the string by "=" if the string looks like a formula */
|
|
40531
|
-
function prefixFormula(formula) {
|
|
40532
|
-
const tokens = tokenize(formula);
|
|
40533
|
-
return tokens.length === 1 && tokens[0].type !== "REFERENCE" ? formula : "=" + formula;
|
|
40534
|
-
}
|
|
40535
40607
|
// ---------------------------------------------------------------------------
|
|
40536
40608
|
// Warnings
|
|
40537
40609
|
// ---------------------------------------------------------------------------
|
|
@@ -41007,7 +41079,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
41007
41079
|
dvRules.push(decimalRule);
|
|
41008
41080
|
break;
|
|
41009
41081
|
case "list":
|
|
41010
|
-
const listRule =
|
|
41082
|
+
const listRule = convertListRule(dvId++, dv);
|
|
41011
41083
|
dvRules.push(listRule);
|
|
41012
41084
|
break;
|
|
41013
41085
|
case "date":
|
|
@@ -41027,9 +41099,9 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
41027
41099
|
return dvRules;
|
|
41028
41100
|
}
|
|
41029
41101
|
function convertDecimalRule(id, dv) {
|
|
41030
|
-
const values = [dv.formula1.toString()];
|
|
41102
|
+
const values = [prefixFormulaWithEqual(dv.formula1.toString())];
|
|
41031
41103
|
if (dv.formula2) {
|
|
41032
|
-
values.push(dv.formula2.toString());
|
|
41104
|
+
values.push(prefixFormulaWithEqual(dv.formula2.toString()));
|
|
41033
41105
|
}
|
|
41034
41106
|
return {
|
|
41035
41107
|
id: id.toString(),
|
|
@@ -41041,7 +41113,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
41041
41113
|
},
|
|
41042
41114
|
};
|
|
41043
41115
|
}
|
|
41044
|
-
function
|
|
41116
|
+
function convertListRule(id, dv) {
|
|
41045
41117
|
const formula1 = dv.formula1.toString();
|
|
41046
41118
|
const isRangeRule = rangeReference.test(formula1);
|
|
41047
41119
|
return {
|
|
@@ -41057,9 +41129,9 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
41057
41129
|
}
|
|
41058
41130
|
function convertDateRule(id, dv) {
|
|
41059
41131
|
let criterion;
|
|
41060
|
-
const values = [dv.formula1.toString()];
|
|
41132
|
+
const values = [prefixFormulaWithEqual(dv.formula1.toString())];
|
|
41061
41133
|
if (dv.formula2) {
|
|
41062
|
-
values.push(dv.formula2.toString());
|
|
41134
|
+
values.push(prefixFormulaWithEqual(dv.formula2.toString()));
|
|
41063
41135
|
criterion = {
|
|
41064
41136
|
type: XLSX_DV_DATE_OPERATOR_TO_DV_TYPE_MAPPING[dv.operator],
|
|
41065
41137
|
values: getDateCriterionFormattedValues(values, DEFAULT_LOCALE),
|
|
@@ -41086,7 +41158,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
41086
41158
|
isBlocking: dv.errorStyle !== "warning",
|
|
41087
41159
|
criterion: {
|
|
41088
41160
|
type: "customFormula",
|
|
41089
|
-
values: [
|
|
41161
|
+
values: [prefixFormulaWithEqual(dv.formula1.toString())],
|
|
41090
41162
|
},
|
|
41091
41163
|
};
|
|
41092
41164
|
}
|
|
@@ -43990,6 +44062,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
43990
44062
|
return data;
|
|
43991
44063
|
}
|
|
43992
44064
|
const figureIds = new Set();
|
|
44065
|
+
const chartIds = new Set();
|
|
43993
44066
|
const uuidGenerator = new UuidGenerator();
|
|
43994
44067
|
for (const sheet of data.sheets || []) {
|
|
43995
44068
|
for (const figure of sheet.figures || []) {
|
|
@@ -43997,6 +44070,12 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
43997
44070
|
figure.id += uuidGenerator.smallUuid();
|
|
43998
44071
|
}
|
|
43999
44072
|
figureIds.add(figure.id);
|
|
44073
|
+
if (figure.tag === "chart") {
|
|
44074
|
+
if (chartIds.has(figure.data?.chartId)) {
|
|
44075
|
+
figure.data.chartId += uuidGenerator.smallUuid();
|
|
44076
|
+
}
|
|
44077
|
+
chartIds.add(figure.data?.chartId);
|
|
44078
|
+
}
|
|
44000
44079
|
}
|
|
44001
44080
|
}
|
|
44002
44081
|
data.uniqueFigureIds = true;
|
|
@@ -49750,39 +49829,63 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
49750
49829
|
this.model.dispatch("AUTOFILL_TABLE_COLUMN", { ...this.currentEditedCell });
|
|
49751
49830
|
this.setContent("");
|
|
49752
49831
|
}
|
|
49753
|
-
getComposerContent(position) {
|
|
49832
|
+
getComposerContent(position, selection) {
|
|
49754
49833
|
const locale = this.getters.getLocale();
|
|
49755
49834
|
const cell = this.getters.getCell(position);
|
|
49756
49835
|
if (cell?.isFormula) {
|
|
49757
49836
|
const prettifiedContent = this.getPrettifiedFormula(cell);
|
|
49758
|
-
|
|
49837
|
+
// when a formula is prettified (multi lines, indented), adapt the cursor position
|
|
49838
|
+
// to take into account line breaks and tabs
|
|
49839
|
+
function adjustCursorIndex(targetIndex) {
|
|
49840
|
+
let adjustedIndex = 0;
|
|
49841
|
+
let originalIndex = 0;
|
|
49842
|
+
while (originalIndex < targetIndex) {
|
|
49843
|
+
adjustedIndex++;
|
|
49844
|
+
const char = prettifiedContent[adjustedIndex];
|
|
49845
|
+
if (char !== "\n" && char !== "\t") {
|
|
49846
|
+
originalIndex++;
|
|
49847
|
+
}
|
|
49848
|
+
}
|
|
49849
|
+
return adjustedIndex;
|
|
49850
|
+
}
|
|
49851
|
+
let adjustedSelection = selection;
|
|
49852
|
+
if (selection) {
|
|
49853
|
+
adjustedSelection = {
|
|
49854
|
+
start: adjustCursorIndex(selection.start),
|
|
49855
|
+
end: adjustCursorIndex(selection.end),
|
|
49856
|
+
};
|
|
49857
|
+
}
|
|
49858
|
+
return {
|
|
49859
|
+
text: localizeFormula(prettifiedContent, locale),
|
|
49860
|
+
adjustedSelection,
|
|
49861
|
+
};
|
|
49759
49862
|
}
|
|
49760
49863
|
const spreader = this.model.getters.getArrayFormulaSpreadingOn(position);
|
|
49761
49864
|
if (spreader) {
|
|
49762
|
-
return "";
|
|
49865
|
+
return { text: "" };
|
|
49763
49866
|
}
|
|
49764
49867
|
const { format, value, type, formattedValue } = this.getters.getEvaluatedCell(position);
|
|
49765
49868
|
switch (type) {
|
|
49766
49869
|
case CellValueType.empty:
|
|
49767
|
-
return "";
|
|
49870
|
+
return { text: "" };
|
|
49768
49871
|
case CellValueType.text:
|
|
49769
49872
|
case CellValueType.error:
|
|
49770
|
-
return value;
|
|
49873
|
+
return { text: value };
|
|
49771
49874
|
case CellValueType.boolean:
|
|
49772
|
-
return formattedValue;
|
|
49875
|
+
return { text: formattedValue };
|
|
49773
49876
|
case CellValueType.number:
|
|
49774
49877
|
if (format && isDateTimeFormat(format)) {
|
|
49775
49878
|
if (parseDateTime(formattedValue, locale) !== null) {
|
|
49776
49879
|
// formatted string can be parsed again
|
|
49777
|
-
return formattedValue;
|
|
49880
|
+
return { text: formattedValue };
|
|
49778
49881
|
}
|
|
49779
49882
|
// display a simplified and parsable string otherwise
|
|
49780
49883
|
const timeFormat = Number.isInteger(value)
|
|
49781
49884
|
? locale.dateFormat
|
|
49782
49885
|
: getDateTimeFormat(locale);
|
|
49783
|
-
return formatValue(value, { locale, format: timeFormat });
|
|
49886
|
+
return { text: formatValue(value, { locale, format: timeFormat }) };
|
|
49784
49887
|
}
|
|
49785
|
-
return this.numberComposerContent(value, format, locale);
|
|
49888
|
+
return { text: this.numberComposerContent(value, format, locale) };
|
|
49786
49889
|
}
|
|
49787
49890
|
}
|
|
49788
49891
|
getPrettifiedFormula(cell) {
|
|
@@ -49951,8 +50054,9 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
49951
50054
|
},
|
|
49952
50055
|
focus: this.focus,
|
|
49953
50056
|
isDefaultFocus: true,
|
|
49954
|
-
onComposerContentFocused: () => this.composerFocusStore.focusComposer(this.composerInterface, {
|
|
50057
|
+
onComposerContentFocused: (selection) => this.composerFocusStore.focusComposer(this.composerInterface, {
|
|
49955
50058
|
focusMode: "contentFocus",
|
|
50059
|
+
selection,
|
|
49956
50060
|
}),
|
|
49957
50061
|
onComposerCellFocused: (content) => this.composerFocusStore.focusComposer(this.composerInterface, {
|
|
49958
50062
|
focusMode: "cellFocus",
|
|
@@ -57358,12 +57462,13 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
57358
57462
|
onCloseSidePanel: { type: Function, optional: true },
|
|
57359
57463
|
};
|
|
57360
57464
|
state = owl.useState({ rule: this.defaultDataValidationRule, errors: [] });
|
|
57465
|
+
editingSheetId;
|
|
57361
57466
|
setup() {
|
|
57467
|
+
this.editingSheetId = this.env.model.getters.getActiveSheetId();
|
|
57362
57468
|
if (this.props.rule) {
|
|
57363
|
-
const sheetId = this.env.model.getters.getActiveSheetId();
|
|
57364
57469
|
this.state.rule = {
|
|
57365
57470
|
...this.props.rule,
|
|
57366
|
-
ranges: this.props.rule.ranges.map((range) => this.env.model.getters.getRangeString(range,
|
|
57471
|
+
ranges: this.props.rule.ranges.map((range) => this.env.model.getters.getRangeString(range, this.editingSheetId)),
|
|
57367
57472
|
};
|
|
57368
57473
|
this.state.rule.criterion.type = this.props.rule.criterion.type;
|
|
57369
57474
|
}
|
|
@@ -57397,7 +57502,6 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
57397
57502
|
const locale = this.env.model.getters.getLocale();
|
|
57398
57503
|
const criterion = rule.criterion;
|
|
57399
57504
|
const criterionEvaluator = criterionEvaluatorRegistry.get(criterion.type);
|
|
57400
|
-
const sheetId = this.env.model.getters.getActiveSheetId();
|
|
57401
57505
|
const values = criterion.values
|
|
57402
57506
|
.slice(0, criterionEvaluator.numberOfValues(criterion))
|
|
57403
57507
|
.map((value) => value?.trim())
|
|
@@ -57405,8 +57509,8 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
57405
57509
|
.map((value) => canonicalizeContent(value, locale));
|
|
57406
57510
|
rule.criterion = { ...criterion, values };
|
|
57407
57511
|
return {
|
|
57408
|
-
sheetId,
|
|
57409
|
-
ranges: this.state.rule.ranges.map((xc) => this.env.model.getters.getRangeDataFromXc(
|
|
57512
|
+
sheetId: this.editingSheetId,
|
|
57513
|
+
ranges: this.state.rule.ranges.map((xc) => this.env.model.getters.getRangeDataFromXc(this.editingSheetId, xc)),
|
|
57410
57514
|
rule,
|
|
57411
57515
|
};
|
|
57412
57516
|
}
|
|
@@ -57933,6 +58037,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
57933
58037
|
.o-button {
|
|
57934
58038
|
height: 19px;
|
|
57935
58039
|
width: 19px;
|
|
58040
|
+
box-sizing: content-box;
|
|
57936
58041
|
.o-icon {
|
|
57937
58042
|
height: 14px;
|
|
57938
58043
|
width: 14px;
|
|
@@ -68706,6 +68811,281 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
68706
68811
|
}
|
|
68707
68812
|
}
|
|
68708
68813
|
|
|
68814
|
+
class ZoneSet {
|
|
68815
|
+
profilesStartingPosition = [0];
|
|
68816
|
+
profiles = new Map([[0, []]]);
|
|
68817
|
+
constructor(zones = []) {
|
|
68818
|
+
for (const zone of zones) {
|
|
68819
|
+
this.add(zone);
|
|
68820
|
+
}
|
|
68821
|
+
}
|
|
68822
|
+
isEmpty() {
|
|
68823
|
+
return this.profiles.size === 1 && this.profiles.get(0)?.length === 0;
|
|
68824
|
+
}
|
|
68825
|
+
add(zone) {
|
|
68826
|
+
modifyProfiles(this.profilesStartingPosition, this.profiles, [zone]);
|
|
68827
|
+
}
|
|
68828
|
+
delete(zone) {
|
|
68829
|
+
modifyProfiles(this.profilesStartingPosition, this.profiles, [zone], true);
|
|
68830
|
+
}
|
|
68831
|
+
has(zone) {
|
|
68832
|
+
return profilesContainsZone(this.profilesStartingPosition, this.profiles, zone);
|
|
68833
|
+
}
|
|
68834
|
+
difference(other) {
|
|
68835
|
+
const result = this.copy();
|
|
68836
|
+
for (const zone of other) {
|
|
68837
|
+
result.delete(zone);
|
|
68838
|
+
}
|
|
68839
|
+
return result;
|
|
68840
|
+
}
|
|
68841
|
+
copy() {
|
|
68842
|
+
const result = new ZoneSet();
|
|
68843
|
+
result.profilesStartingPosition = [...this.profilesStartingPosition];
|
|
68844
|
+
result.profiles = new Map();
|
|
68845
|
+
for (const [key, value] of this.profiles) {
|
|
68846
|
+
result.profiles.set(key, [...value]);
|
|
68847
|
+
}
|
|
68848
|
+
return result;
|
|
68849
|
+
}
|
|
68850
|
+
size() {
|
|
68851
|
+
let size = 0;
|
|
68852
|
+
for (const profile of this.profiles.values()) {
|
|
68853
|
+
size += profile.length;
|
|
68854
|
+
}
|
|
68855
|
+
return size / 2;
|
|
68856
|
+
}
|
|
68857
|
+
/**
|
|
68858
|
+
* iterator of all the zones in the ZoneSet
|
|
68859
|
+
*/
|
|
68860
|
+
[Symbol.iterator]() {
|
|
68861
|
+
return constructZonesFromProfiles(this.profilesStartingPosition, this.profiles)[Symbol.iterator]();
|
|
68862
|
+
}
|
|
68863
|
+
}
|
|
68864
|
+
|
|
68865
|
+
class RangeSet {
|
|
68866
|
+
setsBySheetId = {};
|
|
68867
|
+
constructor(ranges = []) {
|
|
68868
|
+
for (const range of ranges) {
|
|
68869
|
+
this.add(range);
|
|
68870
|
+
}
|
|
68871
|
+
}
|
|
68872
|
+
add(range) {
|
|
68873
|
+
if (!this.setsBySheetId[range.sheetId]) {
|
|
68874
|
+
this.setsBySheetId[range.sheetId] = new ZoneSet();
|
|
68875
|
+
}
|
|
68876
|
+
this.setsBySheetId[range.sheetId].add(range.zone);
|
|
68877
|
+
}
|
|
68878
|
+
addMany(ranges) {
|
|
68879
|
+
for (const range of ranges) {
|
|
68880
|
+
this.add(range);
|
|
68881
|
+
}
|
|
68882
|
+
}
|
|
68883
|
+
addPosition(position) {
|
|
68884
|
+
this.add(positionToBoundedRange(position));
|
|
68885
|
+
}
|
|
68886
|
+
addManyPositions(positions) {
|
|
68887
|
+
for (const position of positions) {
|
|
68888
|
+
this.addPosition(position);
|
|
68889
|
+
}
|
|
68890
|
+
}
|
|
68891
|
+
has(range) {
|
|
68892
|
+
if (!this.setsBySheetId[range.sheetId]) {
|
|
68893
|
+
return false;
|
|
68894
|
+
}
|
|
68895
|
+
return this.setsBySheetId[range.sheetId].has(range.zone);
|
|
68896
|
+
}
|
|
68897
|
+
hasPosition(position) {
|
|
68898
|
+
return this.has(positionToBoundedRange(position));
|
|
68899
|
+
}
|
|
68900
|
+
delete(range) {
|
|
68901
|
+
if (!this.setsBySheetId[range.sheetId]) {
|
|
68902
|
+
return;
|
|
68903
|
+
}
|
|
68904
|
+
this.setsBySheetId[range.sheetId].delete(range.zone);
|
|
68905
|
+
}
|
|
68906
|
+
deleteMany(ranges) {
|
|
68907
|
+
for (const range of ranges) {
|
|
68908
|
+
this.delete(range);
|
|
68909
|
+
}
|
|
68910
|
+
}
|
|
68911
|
+
deleteManyPositions(positions) {
|
|
68912
|
+
for (const position of positions) {
|
|
68913
|
+
this.delete(positionToBoundedRange(position));
|
|
68914
|
+
}
|
|
68915
|
+
}
|
|
68916
|
+
difference(other) {
|
|
68917
|
+
const result = new RangeSet();
|
|
68918
|
+
for (const sheetId in this.setsBySheetId) {
|
|
68919
|
+
result.setsBySheetId[sheetId] = this.setsBySheetId[sheetId];
|
|
68920
|
+
}
|
|
68921
|
+
for (const sheetId in other.setsBySheetId) {
|
|
68922
|
+
if (result.setsBySheetId[sheetId]) {
|
|
68923
|
+
result.setsBySheetId[sheetId] = result.setsBySheetId[sheetId].difference(other.setsBySheetId[sheetId]);
|
|
68924
|
+
}
|
|
68925
|
+
}
|
|
68926
|
+
return result;
|
|
68927
|
+
}
|
|
68928
|
+
copy() {
|
|
68929
|
+
const result = new RangeSet();
|
|
68930
|
+
for (const sheetId in this.setsBySheetId) {
|
|
68931
|
+
result.setsBySheetId[sheetId] = this.setsBySheetId[sheetId].copy();
|
|
68932
|
+
}
|
|
68933
|
+
return result;
|
|
68934
|
+
}
|
|
68935
|
+
clear() {
|
|
68936
|
+
this.setsBySheetId = {};
|
|
68937
|
+
}
|
|
68938
|
+
size() {
|
|
68939
|
+
let size = 0;
|
|
68940
|
+
for (const sheetId in this.setsBySheetId) {
|
|
68941
|
+
size += this.setsBySheetId[sheetId].size();
|
|
68942
|
+
}
|
|
68943
|
+
return size;
|
|
68944
|
+
}
|
|
68945
|
+
isEmpty() {
|
|
68946
|
+
for (const sheetId in this.setsBySheetId) {
|
|
68947
|
+
if (!this.setsBySheetId[sheetId].isEmpty()) {
|
|
68948
|
+
return false;
|
|
68949
|
+
}
|
|
68950
|
+
}
|
|
68951
|
+
return true;
|
|
68952
|
+
}
|
|
68953
|
+
/**
|
|
68954
|
+
* iterator of all the ranges in the RangeSet
|
|
68955
|
+
*/
|
|
68956
|
+
[Symbol.iterator]() {
|
|
68957
|
+
const result = [];
|
|
68958
|
+
for (const sheetId in this.setsBySheetId) {
|
|
68959
|
+
for (const zone of this.setsBySheetId[sheetId]) {
|
|
68960
|
+
result.push({ sheetId: sheetId, zone });
|
|
68961
|
+
}
|
|
68962
|
+
}
|
|
68963
|
+
return result[Symbol.iterator]();
|
|
68964
|
+
}
|
|
68965
|
+
}
|
|
68966
|
+
|
|
68967
|
+
/**
|
|
68968
|
+
* R-Tree of ranges, mapping zones (r-tree bounding boxes) to ranges (data of the r-tree item).
|
|
68969
|
+
* Ranges associated to the exact same bounding box are grouped together
|
|
68970
|
+
* to reduce the number of nodes in the R-tree.
|
|
68971
|
+
*/
|
|
68972
|
+
class DependenciesRTree {
|
|
68973
|
+
rTree;
|
|
68974
|
+
constructor(items = []) {
|
|
68975
|
+
const compactedBoxes = groupSameBoundingBoxes(items);
|
|
68976
|
+
this.rTree = new SpreadsheetRTree(compactedBoxes);
|
|
68977
|
+
}
|
|
68978
|
+
insert(item) {
|
|
68979
|
+
const data = this.rTree.search(item.boundingBox);
|
|
68980
|
+
const itemBoundingBox = item.boundingBox;
|
|
68981
|
+
const exactBoundingBox = data.find(({ boundingBox }) => boundingBox.sheetId === itemBoundingBox.sheetId &&
|
|
68982
|
+
boundingBox.zone.left === itemBoundingBox.zone.left &&
|
|
68983
|
+
boundingBox.zone.top === itemBoundingBox.zone.top &&
|
|
68984
|
+
boundingBox.zone.right === itemBoundingBox.zone.right &&
|
|
68985
|
+
boundingBox.zone.bottom === itemBoundingBox.zone.bottom);
|
|
68986
|
+
if (exactBoundingBox) {
|
|
68987
|
+
exactBoundingBox.data.add(item.data);
|
|
68988
|
+
}
|
|
68989
|
+
else {
|
|
68990
|
+
this.rTree.insert({ ...item, data: new RangeSet([item.data]) });
|
|
68991
|
+
}
|
|
68992
|
+
}
|
|
68993
|
+
search({ zone, sheetId }) {
|
|
68994
|
+
const results = new RangeSet();
|
|
68995
|
+
for (const { data } of this.rTree.search({ zone, sheetId })) {
|
|
68996
|
+
results.addMany(data);
|
|
68997
|
+
}
|
|
68998
|
+
return results;
|
|
68999
|
+
}
|
|
69000
|
+
remove(item) {
|
|
69001
|
+
const data = this.rTree.search(item.boundingBox);
|
|
69002
|
+
const itemBoundingBox = item.boundingBox;
|
|
69003
|
+
const exactBoundingBox = data.find(({ boundingBox }) => boundingBox.sheetId === itemBoundingBox.sheetId &&
|
|
69004
|
+
boundingBox.zone.left === itemBoundingBox.zone.left &&
|
|
69005
|
+
boundingBox.zone.top === itemBoundingBox.zone.top &&
|
|
69006
|
+
boundingBox.zone.right === itemBoundingBox.zone.right &&
|
|
69007
|
+
boundingBox.zone.bottom === itemBoundingBox.zone.bottom);
|
|
69008
|
+
if (exactBoundingBox) {
|
|
69009
|
+
exactBoundingBox.data.delete(item.data);
|
|
69010
|
+
}
|
|
69011
|
+
else {
|
|
69012
|
+
this.rTree.remove({ ...item, data: new RangeSet([item.data]) });
|
|
69013
|
+
}
|
|
69014
|
+
}
|
|
69015
|
+
}
|
|
69016
|
+
/**
|
|
69017
|
+
* Group together all formulas pointing to the exact same dependency (bounding box).
|
|
69018
|
+
* The goal is to optimize the following case:
|
|
69019
|
+
* - if any cell in B1:B1000 changes, C1 must be recomputed
|
|
69020
|
+
* - if any cell in B1:B1000 changes, C2 must be recomputed
|
|
69021
|
+
* - if any cell in B1:B1000 changes, C3 must be recomputed
|
|
69022
|
+
* ...
|
|
69023
|
+
* - if any cell in B1:B1000 changes, C1000 must be recomputed
|
|
69024
|
+
*
|
|
69025
|
+
* Instead of having 1000 entries in the R-tree, we want to have a single entry
|
|
69026
|
+
* with B1:B1000 (bounding box) pointing to C1:C1000 (formulas).
|
|
69027
|
+
*/
|
|
69028
|
+
function groupSameBoundingBoxes(items) {
|
|
69029
|
+
// Important: this function must be as fast as possible. It is on the evaluation hot path.
|
|
69030
|
+
let maxCol = 0;
|
|
69031
|
+
let maxRow = 0;
|
|
69032
|
+
for (let i = 0; i < items.length; i++) {
|
|
69033
|
+
const zone = items[i].boundingBox.zone;
|
|
69034
|
+
if (zone.right > maxCol) {
|
|
69035
|
+
maxCol = zone.right;
|
|
69036
|
+
}
|
|
69037
|
+
if (zone.bottom > maxRow) {
|
|
69038
|
+
maxRow = zone.bottom;
|
|
69039
|
+
}
|
|
69040
|
+
}
|
|
69041
|
+
maxCol += 1;
|
|
69042
|
+
maxRow += 1;
|
|
69043
|
+
// in most real-world cases, we can use a fast numeric key
|
|
69044
|
+
// but if the zones are too far right or bottom, we fallback to a slower string key
|
|
69045
|
+
const maxPossibleKey = (((maxRow + 1) * maxCol + 1) * maxRow + 1) * maxCol;
|
|
69046
|
+
const useFastKey = maxPossibleKey <= Number.MAX_SAFE_INTEGER;
|
|
69047
|
+
if (!useFastKey) {
|
|
69048
|
+
console.warn("Max col/row size exceeded, using slow zone key");
|
|
69049
|
+
}
|
|
69050
|
+
const groupedByBBox = {};
|
|
69051
|
+
for (const item of items) {
|
|
69052
|
+
const sheetId = item.boundingBox.sheetId;
|
|
69053
|
+
if (!groupedByBBox[sheetId]) {
|
|
69054
|
+
groupedByBBox[sheetId] = {};
|
|
69055
|
+
}
|
|
69056
|
+
const bBox = item.boundingBox.zone;
|
|
69057
|
+
let bBoxKey = 0;
|
|
69058
|
+
if (useFastKey) {
|
|
69059
|
+
bBoxKey =
|
|
69060
|
+
bBox.left +
|
|
69061
|
+
bBox.top * maxCol +
|
|
69062
|
+
bBox.right * maxCol * maxRow +
|
|
69063
|
+
bBox.bottom * maxCol * maxRow * maxCol;
|
|
69064
|
+
}
|
|
69065
|
+
else {
|
|
69066
|
+
bBoxKey = `${bBox.left},${bBox.top},${bBox.right},${bBox.bottom}`;
|
|
69067
|
+
}
|
|
69068
|
+
if (groupedByBBox[sheetId][bBoxKey]) {
|
|
69069
|
+
const ranges = groupedByBBox[sheetId][bBoxKey].data;
|
|
69070
|
+
ranges.add(item.data);
|
|
69071
|
+
}
|
|
69072
|
+
else {
|
|
69073
|
+
groupedByBBox[sheetId][bBoxKey] = {
|
|
69074
|
+
boundingBox: item.boundingBox,
|
|
69075
|
+
data: new RangeSet([item.data]),
|
|
69076
|
+
};
|
|
69077
|
+
}
|
|
69078
|
+
}
|
|
69079
|
+
const result = [];
|
|
69080
|
+
for (const sheetId in groupedByBBox) {
|
|
69081
|
+
const map = groupedByBBox[sheetId];
|
|
69082
|
+
for (const key in map) {
|
|
69083
|
+
result.push(map[key]);
|
|
69084
|
+
}
|
|
69085
|
+
}
|
|
69086
|
+
return result;
|
|
69087
|
+
}
|
|
69088
|
+
|
|
68709
69089
|
/**
|
|
68710
69090
|
* Implementation of a dependency Graph.
|
|
68711
69091
|
* The graph is used to evaluate the cells in the correct
|
|
@@ -68714,12 +69094,10 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
68714
69094
|
* It uses an R-Tree data structure to efficiently find dependent cells.
|
|
68715
69095
|
*/
|
|
68716
69096
|
class FormulaDependencyGraph {
|
|
68717
|
-
createEmptyPositionSet;
|
|
68718
69097
|
dependencies = new PositionMap();
|
|
68719
69098
|
rTree;
|
|
68720
|
-
constructor(
|
|
68721
|
-
this.
|
|
68722
|
-
this.rTree = new SpreadsheetRTree(data);
|
|
69099
|
+
constructor(data = []) {
|
|
69100
|
+
this.rTree = new DependenciesRTree(data);
|
|
68723
69101
|
}
|
|
68724
69102
|
removeAllDependencies(formulaPosition) {
|
|
68725
69103
|
const ranges = this.dependencies.get(formulaPosition);
|
|
@@ -68733,7 +69111,10 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
68733
69111
|
}
|
|
68734
69112
|
addDependencies(formulaPosition, dependencies) {
|
|
68735
69113
|
const rTreeItems = dependencies.map(({ sheetId, zone }) => ({
|
|
68736
|
-
data:
|
|
69114
|
+
data: {
|
|
69115
|
+
sheetId: formulaPosition.sheetId,
|
|
69116
|
+
zone: positionToZone(formulaPosition),
|
|
69117
|
+
},
|
|
68737
69118
|
boundingBox: {
|
|
68738
69119
|
zone,
|
|
68739
69120
|
sheetId,
|
|
@@ -68751,46 +69132,20 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
68751
69132
|
}
|
|
68752
69133
|
}
|
|
68753
69134
|
/**
|
|
68754
|
-
* Return all the cells that depend on the provided ranges
|
|
68755
|
-
* in the correct order they should be evaluated.
|
|
68756
|
-
* This is called a topological ordering (excluding cycles)
|
|
69135
|
+
* Return all the cells that depend on the provided ranges.
|
|
68757
69136
|
*/
|
|
68758
|
-
getCellsDependingOn(ranges) {
|
|
68759
|
-
|
|
69137
|
+
getCellsDependingOn(ranges, visited = new RangeSet()) {
|
|
69138
|
+
visited = visited.copy();
|
|
68760
69139
|
const queue = Array.from(ranges).reverse();
|
|
68761
69140
|
while (queue.length > 0) {
|
|
68762
69141
|
const range = queue.pop();
|
|
68763
|
-
|
|
68764
|
-
const
|
|
68765
|
-
|
|
68766
|
-
for (let row = zone.top; row <= zone.bottom; row++) {
|
|
68767
|
-
visited.add({ sheetId, col, row });
|
|
68768
|
-
}
|
|
68769
|
-
}
|
|
68770
|
-
const impactedPositions = this.rTree.search(range).map((dep) => dep.data);
|
|
68771
|
-
const nextInQueue = {};
|
|
68772
|
-
for (const position of impactedPositions) {
|
|
68773
|
-
if (!visited.has(position)) {
|
|
68774
|
-
if (!nextInQueue[position.sheetId]) {
|
|
68775
|
-
nextInQueue[position.sheetId] = [];
|
|
68776
|
-
}
|
|
68777
|
-
nextInQueue[position.sheetId].push(positionToZone(position));
|
|
68778
|
-
}
|
|
68779
|
-
}
|
|
68780
|
-
for (const sheetId in nextInQueue) {
|
|
68781
|
-
const zones = recomputeZones(nextInQueue[sheetId], []);
|
|
68782
|
-
queue.push(...zones.map((zone) => ({ sheetId, zone })));
|
|
68783
|
-
}
|
|
69142
|
+
visited.add(range);
|
|
69143
|
+
const impactedRanges = this.rTree.search(range);
|
|
69144
|
+
queue.push(...impactedRanges.difference(visited));
|
|
68784
69145
|
}
|
|
68785
69146
|
// remove initial ranges
|
|
68786
69147
|
for (const range of ranges) {
|
|
68787
|
-
|
|
68788
|
-
const sheetId = range.sheetId;
|
|
68789
|
-
for (let col = zone.left; col <= zone.right; col++) {
|
|
68790
|
-
for (let row = zone.top; row <= zone.bottom; row++) {
|
|
68791
|
-
visited.delete({ sheetId, col, row });
|
|
68792
|
-
}
|
|
68793
|
-
}
|
|
69148
|
+
visited.delete(range);
|
|
68794
69149
|
}
|
|
68795
69150
|
return visited;
|
|
68796
69151
|
}
|
|
@@ -69053,7 +69408,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
69053
69408
|
getters;
|
|
69054
69409
|
compilationParams;
|
|
69055
69410
|
evaluatedCells = new PositionMap();
|
|
69056
|
-
formulaDependencies = lazy(new FormulaDependencyGraph(
|
|
69411
|
+
formulaDependencies = lazy(new FormulaDependencyGraph());
|
|
69057
69412
|
blockedArrayFormulas = new PositionSet({});
|
|
69058
69413
|
spreadingRelations = new SpreadingRelation();
|
|
69059
69414
|
constructor(context, getters) {
|
|
@@ -69088,7 +69443,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
69088
69443
|
return undefined;
|
|
69089
69444
|
}
|
|
69090
69445
|
const arrayFormulas = this.spreadingRelations.searchFormulaPositionsSpreadingOn(position.sheetId, positionToZone(position));
|
|
69091
|
-
return
|
|
69446
|
+
return arrayFormulas.find((position) => !this.blockedArrayFormulas.has(position));
|
|
69092
69447
|
}
|
|
69093
69448
|
updateDependencies(position) {
|
|
69094
69449
|
// removing dependencies is slow because it requires
|
|
@@ -69132,57 +69487,72 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
69132
69487
|
}
|
|
69133
69488
|
evaluateCells(positions) {
|
|
69134
69489
|
const start = performance.now();
|
|
69135
|
-
const
|
|
69136
|
-
|
|
69490
|
+
const rangesToCompute = new RangeSet();
|
|
69491
|
+
rangesToCompute.addManyPositions(positions);
|
|
69137
69492
|
const arrayFormulasPositions = this.getArrayFormulasImpactedByChangesOf(positions);
|
|
69138
|
-
|
|
69139
|
-
|
|
69140
|
-
|
|
69141
|
-
this.evaluate(
|
|
69493
|
+
rangesToCompute.addMany(this.getCellsDependingOn(rangesToCompute));
|
|
69494
|
+
rangesToCompute.addMany(arrayFormulasPositions);
|
|
69495
|
+
rangesToCompute.addMany(this.getCellsDependingOn(arrayFormulasPositions));
|
|
69496
|
+
this.evaluate(rangesToCompute);
|
|
69142
69497
|
console.debug("evaluate Cells", performance.now() - start, "ms");
|
|
69143
69498
|
}
|
|
69144
69499
|
getArrayFormulasImpactedByChangesOf(positions) {
|
|
69145
|
-
const
|
|
69500
|
+
const impactedRanges = new RangeSet();
|
|
69146
69501
|
for (const position of positions) {
|
|
69147
69502
|
const content = this.getters.getCell(position)?.content;
|
|
69148
69503
|
const arrayFormulaPosition = this.getArrayFormulaSpreadingOn(position);
|
|
69149
69504
|
if (arrayFormulaPosition !== undefined) {
|
|
69150
69505
|
// take into account new collisions.
|
|
69151
|
-
|
|
69506
|
+
impactedRanges.addPosition(arrayFormulaPosition);
|
|
69152
69507
|
}
|
|
69153
69508
|
if (!content) {
|
|
69154
69509
|
// The previous content could have blocked some array formulas
|
|
69155
|
-
|
|
69510
|
+
impactedRanges.addPosition(position);
|
|
69156
69511
|
}
|
|
69157
69512
|
}
|
|
69158
|
-
const
|
|
69159
|
-
|
|
69160
|
-
for (const zone of zonesBySheetIds[sheetId]) {
|
|
69161
|
-
impactedPositions.addMany(this.getArrayFormulasBlockedBy(sheetId, zone));
|
|
69162
|
-
}
|
|
69513
|
+
for (const range of [...impactedRanges]) {
|
|
69514
|
+
impactedRanges.addMany(this.getArrayFormulasBlockedBy(range.sheetId, range.zone));
|
|
69163
69515
|
}
|
|
69164
|
-
return
|
|
69516
|
+
return impactedRanges;
|
|
69165
69517
|
}
|
|
69166
69518
|
buildDependencyGraph() {
|
|
69167
69519
|
this.blockedArrayFormulas = this.createEmptyPositionSet();
|
|
69168
69520
|
this.spreadingRelations = new SpreadingRelation();
|
|
69169
69521
|
this.formulaDependencies = lazy(() => {
|
|
69170
|
-
const
|
|
69171
|
-
|
|
69172
|
-
.
|
|
69173
|
-
|
|
69174
|
-
|
|
69175
|
-
|
|
69176
|
-
|
|
69177
|
-
|
|
69178
|
-
|
|
69179
|
-
|
|
69522
|
+
const rTreeItems = [];
|
|
69523
|
+
for (const sheetId of this.getters.getSheetIds()) {
|
|
69524
|
+
const cells = this.getters.getCells(sheetId);
|
|
69525
|
+
for (const cellId in cells) {
|
|
69526
|
+
const cell = cells[cellId];
|
|
69527
|
+
if (cell.isFormula) {
|
|
69528
|
+
const directDependencies = cell.compiledFormula.dependencies;
|
|
69529
|
+
for (const range of directDependencies) {
|
|
69530
|
+
if (range.invalidSheetName || range.invalidXc) {
|
|
69531
|
+
continue;
|
|
69532
|
+
}
|
|
69533
|
+
rTreeItems.push({
|
|
69534
|
+
data: {
|
|
69535
|
+
sheetId,
|
|
69536
|
+
zone: positionToZone(this.getters.getCellPosition(cellId)),
|
|
69537
|
+
},
|
|
69538
|
+
boundingBox: { sheetId: range.sheetId, zone: range.zone },
|
|
69539
|
+
});
|
|
69540
|
+
}
|
|
69541
|
+
}
|
|
69542
|
+
}
|
|
69543
|
+
}
|
|
69544
|
+
return new FormulaDependencyGraph(rTreeItems);
|
|
69180
69545
|
});
|
|
69181
69546
|
}
|
|
69182
69547
|
evaluateAllCells() {
|
|
69183
69548
|
const start = performance.now();
|
|
69184
69549
|
this.evaluatedCells = new PositionMap();
|
|
69185
|
-
|
|
69550
|
+
const ranges = [];
|
|
69551
|
+
for (const sheetId of this.getters.getSheetIds()) {
|
|
69552
|
+
const zone = this.getters.getSheetZone(sheetId);
|
|
69553
|
+
ranges.push({ sheetId, zone });
|
|
69554
|
+
}
|
|
69555
|
+
this.evaluate(ranges);
|
|
69186
69556
|
console.debug("evaluate all cells", performance.now() - start, "ms");
|
|
69187
69557
|
}
|
|
69188
69558
|
evaluateFormulaResult(sheetId, formulaString) {
|
|
@@ -69206,48 +69576,47 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
69206
69576
|
return handleError(error, "");
|
|
69207
69577
|
}
|
|
69208
69578
|
}
|
|
69209
|
-
getAllCells() {
|
|
69210
|
-
const positions = this.createEmptyPositionSet();
|
|
69211
|
-
positions.fillAllPositions();
|
|
69212
|
-
return positions;
|
|
69213
|
-
}
|
|
69214
69579
|
/**
|
|
69215
69580
|
* Return the position of formulas blocked by the given positions
|
|
69216
69581
|
* as well as all their dependencies.
|
|
69217
69582
|
*/
|
|
69218
69583
|
getArrayFormulasBlockedBy(sheetId, zone) {
|
|
69219
|
-
const arrayFormulaPositions =
|
|
69584
|
+
const arrayFormulaPositions = new RangeSet();
|
|
69220
69585
|
const arrayFormulas = this.spreadingRelations.searchFormulaPositionsSpreadingOn(sheetId, zone);
|
|
69221
|
-
arrayFormulaPositions.
|
|
69586
|
+
arrayFormulaPositions.addManyPositions(arrayFormulas);
|
|
69222
69587
|
const spilledPositions = [...arrayFormulas].filter((position) => !this.blockedArrayFormulas.has(position));
|
|
69223
69588
|
if (spilledPositions.length) {
|
|
69224
69589
|
// ignore the formula spreading on the position. Keep only the blocked ones
|
|
69225
|
-
arrayFormulaPositions.
|
|
69590
|
+
arrayFormulaPositions.deleteManyPositions(spilledPositions);
|
|
69226
69591
|
}
|
|
69227
69592
|
arrayFormulaPositions.addMany(this.getCellsDependingOn(arrayFormulaPositions));
|
|
69228
69593
|
return arrayFormulaPositions;
|
|
69229
69594
|
}
|
|
69230
|
-
|
|
69595
|
+
nextRangesToUpdate = new RangeSet();
|
|
69231
69596
|
cellsBeingComputed = new Set();
|
|
69232
69597
|
symbolsBeingComputed = new Set();
|
|
69233
|
-
evaluate(
|
|
69598
|
+
evaluate(ranges) {
|
|
69234
69599
|
this.cellsBeingComputed = new Set();
|
|
69235
|
-
this.
|
|
69600
|
+
this.nextRangesToUpdate = new RangeSet(ranges);
|
|
69236
69601
|
let currentIteration = 0;
|
|
69237
|
-
while (!this.
|
|
69602
|
+
while (!this.nextRangesToUpdate.isEmpty() && currentIteration++ < MAX_ITERATION) {
|
|
69238
69603
|
this.updateCompilationParameters();
|
|
69239
|
-
const
|
|
69240
|
-
|
|
69241
|
-
|
|
69242
|
-
|
|
69243
|
-
|
|
69244
|
-
|
|
69245
|
-
|
|
69246
|
-
|
|
69247
|
-
|
|
69248
|
-
|
|
69249
|
-
|
|
69250
|
-
|
|
69604
|
+
const ranges = [...this.nextRangesToUpdate];
|
|
69605
|
+
this.nextRangesToUpdate.clear();
|
|
69606
|
+
this.clearEvaluatedRanges(ranges);
|
|
69607
|
+
for (const range of ranges) {
|
|
69608
|
+
const { left, bottom, right, top } = range.zone;
|
|
69609
|
+
for (let col = left; col <= right; col++) {
|
|
69610
|
+
for (let row = top; row <= bottom; row++) {
|
|
69611
|
+
const position = { sheetId: range.sheetId, col, row };
|
|
69612
|
+
if (this.nextRangesToUpdate.hasPosition(position)) {
|
|
69613
|
+
continue;
|
|
69614
|
+
}
|
|
69615
|
+
const evaluatedCell = this.computeCell(position);
|
|
69616
|
+
if (evaluatedCell !== EMPTY_CELL) {
|
|
69617
|
+
this.evaluatedCells.set(position, evaluatedCell);
|
|
69618
|
+
}
|
|
69619
|
+
}
|
|
69251
69620
|
}
|
|
69252
69621
|
}
|
|
69253
69622
|
onIterationEndEvaluationRegistry.getAll().forEach((callback) => callback(this.getters));
|
|
@@ -69256,6 +69625,16 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
69256
69625
|
console.warn("Maximum iteration reached while evaluating cells");
|
|
69257
69626
|
}
|
|
69258
69627
|
}
|
|
69628
|
+
clearEvaluatedRanges(ranges) {
|
|
69629
|
+
for (const range of ranges) {
|
|
69630
|
+
const { left, bottom, right, top } = range.zone;
|
|
69631
|
+
for (let col = left; col <= right; col++) {
|
|
69632
|
+
for (let row = top; row <= bottom; row++) {
|
|
69633
|
+
this.evaluatedCells.delete({ sheetId: range.sheetId, col, row });
|
|
69634
|
+
}
|
|
69635
|
+
}
|
|
69636
|
+
}
|
|
69637
|
+
}
|
|
69259
69638
|
computeCell(position) {
|
|
69260
69639
|
const evaluation = this.evaluatedCells.get(position);
|
|
69261
69640
|
if (evaluation) {
|
|
@@ -69328,9 +69707,9 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
69328
69707
|
}
|
|
69329
69708
|
invalidatePositionsDependingOnSpread(sheetId, resultZone) {
|
|
69330
69709
|
// the result matrix is split in 2 zones to exclude the array formula position
|
|
69331
|
-
const invalidatedPositions = this.
|
|
69332
|
-
invalidatedPositions.delete({ sheetId,
|
|
69333
|
-
this.
|
|
69710
|
+
const invalidatedPositions = this.getCellsDependingOn(excludeTopLeft(resultZone).map((zone) => ({ sheetId, zone })));
|
|
69711
|
+
invalidatedPositions.delete({ sheetId, zone: resultZone });
|
|
69712
|
+
this.nextRangesToUpdate.addMany(invalidatedPositions);
|
|
69334
69713
|
}
|
|
69335
69714
|
assertSheetHasEnoughSpaceToSpreadFormulaResult({ sheetId, col, row }, matrixResult) {
|
|
69336
69715
|
const numberOfCols = this.getters.getNumberCols(sheetId);
|
|
@@ -69405,7 +69784,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
69405
69784
|
}
|
|
69406
69785
|
const sheetId = position.sheetId;
|
|
69407
69786
|
this.invalidatePositionsDependingOnSpread(sheetId, zone);
|
|
69408
|
-
this.
|
|
69787
|
+
this.nextRangesToUpdate.addMany(this.getArrayFormulasBlockedBy(sheetId, zone));
|
|
69409
69788
|
}
|
|
69410
69789
|
/**
|
|
69411
69790
|
* Wraps a GetSymbolValue function to add cycle detection
|
|
@@ -69440,13 +69819,8 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
69440
69819
|
}
|
|
69441
69820
|
return cell.compiledFormula.dependencies;
|
|
69442
69821
|
}
|
|
69443
|
-
getCellsDependingOn(
|
|
69444
|
-
|
|
69445
|
-
const zonesBySheetIds = aggregatePositionsToZones(positions);
|
|
69446
|
-
for (const sheetId in zonesBySheetIds) {
|
|
69447
|
-
ranges.push(...zonesBySheetIds[sheetId].map((zone) => ({ sheetId, zone })));
|
|
69448
|
-
}
|
|
69449
|
-
return this.formulaDependencies().getCellsDependingOn(ranges);
|
|
69822
|
+
getCellsDependingOn(ranges) {
|
|
69823
|
+
return this.formulaDependencies().getCellsDependingOn(ranges, this.nextRangesToUpdate);
|
|
69450
69824
|
}
|
|
69451
69825
|
}
|
|
69452
69826
|
function forEachSpreadPositionInMatrix(nbColumns, nbRows, callback) {
|
|
@@ -70957,7 +71331,8 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
70957
71331
|
const topLeft = { col: unionZone.left, row: unionZone.top, sheetId };
|
|
70958
71332
|
const parentSpreadingCell = this.getters.getArrayFormulaSpreadingOn(topLeft);
|
|
70959
71333
|
if (!parentSpreadingCell) {
|
|
70960
|
-
|
|
71334
|
+
const evaluatedCell = this.getters.getEvaluatedCell(topLeft);
|
|
71335
|
+
return (evaluatedCell.value === CellErrorType.SpilledBlocked && !evaluatedCell.errorOriginPosition);
|
|
70961
71336
|
}
|
|
70962
71337
|
else if (deepEquals(parentSpreadingCell, topLeft) && getZoneArea(unionZone) === 1) {
|
|
70963
71338
|
return true;
|
|
@@ -82129,6 +82504,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
82129
82504
|
static components = { Menu };
|
|
82130
82505
|
rootItems = topbarMenuRegistry.getMenuItems();
|
|
82131
82506
|
menuRef = owl.useRef("menu");
|
|
82507
|
+
containerRef = owl.useRef("container");
|
|
82132
82508
|
state = owl.useState({
|
|
82133
82509
|
menuItems: this.rootItems,
|
|
82134
82510
|
title: _t("Menu Bar"),
|
|
@@ -82136,6 +82512,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
82136
82512
|
});
|
|
82137
82513
|
setup() {
|
|
82138
82514
|
owl.useExternalListener(window, "click", this.onExternalClick, { capture: true });
|
|
82515
|
+
owl.onMounted(this.updateShadows);
|
|
82139
82516
|
}
|
|
82140
82517
|
onExternalClick(ev) {
|
|
82141
82518
|
if (!this.menuRef.el?.contains(ev.target)) {
|
|
@@ -82148,6 +82525,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
82148
82525
|
this.state.parentState = { ...this.state };
|
|
82149
82526
|
this.state.menuItems = children;
|
|
82150
82527
|
this.state.title = menu.name(this.env);
|
|
82528
|
+
this.containerRef.el?.scrollTo({ top: 0 });
|
|
82151
82529
|
}
|
|
82152
82530
|
else {
|
|
82153
82531
|
this.state.menuItems = this.rootItems;
|
|
@@ -82169,6 +82547,19 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
82169
82547
|
height: `${this.props.height}px`,
|
|
82170
82548
|
});
|
|
82171
82549
|
}
|
|
82550
|
+
updateShadows() {
|
|
82551
|
+
if (!this.containerRef.el) {
|
|
82552
|
+
return;
|
|
82553
|
+
}
|
|
82554
|
+
this.containerRef.el.classList.remove("scroll-top", "scroll-bottom");
|
|
82555
|
+
const maxScroll = this.containerRef.el.scrollHeight - this.containerRef.el.clientHeight || 0;
|
|
82556
|
+
if (this.containerRef.el.scrollTop < maxScroll - 1) {
|
|
82557
|
+
this.containerRef.el.classList.add("scroll-bottom");
|
|
82558
|
+
}
|
|
82559
|
+
if (this.containerRef.el.scrollTop > 0) {
|
|
82560
|
+
this.containerRef.el.classList.add("scroll-top");
|
|
82561
|
+
}
|
|
82562
|
+
}
|
|
82172
82563
|
onClickBack() {
|
|
82173
82564
|
if (!this.state.parentState) {
|
|
82174
82565
|
this.props.onClose();
|
|
@@ -82177,6 +82568,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
82177
82568
|
this.state.menuItems = this.state.parentState.menuItems;
|
|
82178
82569
|
this.state.title = this.state.parentState.title;
|
|
82179
82570
|
this.state.parentState = this.state.parentState.parentState;
|
|
82571
|
+
this.containerRef.el?.scrollTo({ top: 0 });
|
|
82180
82572
|
}
|
|
82181
82573
|
get backTitle() {
|
|
82182
82574
|
return this.state.parentState ? _t("Go to previous menu") : _t("Close menu bar");
|
|
@@ -82233,7 +82625,9 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
82233
82625
|
: "inactive";
|
|
82234
82626
|
}
|
|
82235
82627
|
get showFxIcon() {
|
|
82236
|
-
return this.focus === "inactive" &&
|
|
82628
|
+
return (this.focus === "inactive" &&
|
|
82629
|
+
!this.composerStore.currentContent &&
|
|
82630
|
+
!this.composerStore.placeholder);
|
|
82237
82631
|
}
|
|
82238
82632
|
get rect() {
|
|
82239
82633
|
return this.composerRef.el
|
|
@@ -82250,8 +82644,9 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
82250
82644
|
},
|
|
82251
82645
|
focus: this.focus,
|
|
82252
82646
|
composerStore: this.composerStore,
|
|
82253
|
-
onComposerContentFocused: () => this.composerFocusStore.focusComposer(this.composerInterface, {
|
|
82647
|
+
onComposerContentFocused: (selection) => this.composerFocusStore.focusComposer(this.composerInterface, {
|
|
82254
82648
|
focusMode: "contentFocus",
|
|
82649
|
+
selection,
|
|
82255
82650
|
}),
|
|
82256
82651
|
isDefaultFocus: false,
|
|
82257
82652
|
inputStyle: cssPropertiesToCss({
|
|
@@ -82259,6 +82654,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
82259
82654
|
"max-height": `130px`,
|
|
82260
82655
|
}),
|
|
82261
82656
|
showAssistant: !isIOS(), // Hide assistant on iOS as it breaks visually
|
|
82657
|
+
placeholder: this.composerStore.placeholder,
|
|
82262
82658
|
};
|
|
82263
82659
|
}
|
|
82264
82660
|
get symbols() {
|
|
@@ -82321,7 +82717,9 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
82321
82717
|
: "inactive";
|
|
82322
82718
|
}
|
|
82323
82719
|
get showFxIcon() {
|
|
82324
|
-
return this.focus === "inactive" &&
|
|
82720
|
+
return (this.focus === "inactive" &&
|
|
82721
|
+
!this.composerStore.currentContent &&
|
|
82722
|
+
!this.composerStore.placeholder);
|
|
82325
82723
|
}
|
|
82326
82724
|
get composerStyle() {
|
|
82327
82725
|
const style = {
|
|
@@ -88537,9 +88935,9 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
88537
88935
|
exports.tokenize = tokenize;
|
|
88538
88936
|
|
|
88539
88937
|
|
|
88540
|
-
__info__.version = "19.0.
|
|
88541
|
-
__info__.date = "2025-10-
|
|
88542
|
-
__info__.hash = "
|
|
88938
|
+
__info__.version = "19.0.7";
|
|
88939
|
+
__info__.date = "2025-10-23T08:19:01.764Z";
|
|
88940
|
+
__info__.hash = "1c1d1ec";
|
|
88543
88941
|
|
|
88544
88942
|
|
|
88545
88943
|
})(this.o_spreadsheet = this.o_spreadsheet || {}, owl);
|