@odoo/o-spreadsheet 17.2.3 → 17.2.5
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 +439 -68
- package/dist/o-spreadsheet.d.ts +3 -9
- package/dist/o-spreadsheet.esm.js +439 -68
- package/dist/o-spreadsheet.iife.js +439 -68
- package/dist/o-spreadsheet.iife.min.js +268 -264
- package/dist/o_spreadsheet.xml +4 -4
- package/package.json +1 -1
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* This file is generated by o-spreadsheet build tools. Do not edit it.
|
|
5
5
|
* @see https://github.com/odoo/o-spreadsheet
|
|
6
|
-
* @version 17.2.
|
|
7
|
-
* @date 2024-04-
|
|
8
|
-
* @hash
|
|
6
|
+
* @version 17.2.5
|
|
7
|
+
* @date 2024-04-26T07:41:13.193Z
|
|
8
|
+
* @hash a730f5c
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
(function (exports, owl) {
|
|
@@ -2508,6 +2508,15 @@
|
|
|
2508
2508
|
}
|
|
2509
2509
|
return generateMatrix(matrix.length, matrix[0].length, (col, row) => fn(matrix[col][row]));
|
|
2510
2510
|
}
|
|
2511
|
+
function matrixForEach(matrix, fn) {
|
|
2512
|
+
const numberOfCols = matrix.length;
|
|
2513
|
+
const numberOfRows = matrix[0]?.length ?? 0;
|
|
2514
|
+
for (let col = 0; col < numberOfCols; col++) {
|
|
2515
|
+
for (let row = 0; row < numberOfRows; row++) {
|
|
2516
|
+
fn(matrix[col][row]);
|
|
2517
|
+
}
|
|
2518
|
+
}
|
|
2519
|
+
}
|
|
2511
2520
|
function transposeMatrix(matrix) {
|
|
2512
2521
|
if (!matrix.length) {
|
|
2513
2522
|
return [];
|
|
@@ -18635,7 +18644,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
18635
18644
|
}
|
|
18636
18645
|
const descr = addMetaInfoFromArg(addDescr);
|
|
18637
18646
|
validateArguments(descr.args);
|
|
18638
|
-
this.mapping[name] = addErrorHandling(addResultHandling(addInputHandling(descr)), name);
|
|
18647
|
+
this.mapping[name] = addErrorHandling(addResultHandling(addInputHandling(descr), name), name);
|
|
18639
18648
|
super.add(name, descr);
|
|
18640
18649
|
return this;
|
|
18641
18650
|
}
|
|
@@ -18674,9 +18683,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
18674
18683
|
// so we fallback to a generic error
|
|
18675
18684
|
if (hasStringValue(e) && isEvaluationError(e.value)) {
|
|
18676
18685
|
if (hasStringMessage(e)) {
|
|
18677
|
-
|
|
18678
|
-
e.message = e.message.replace("[[FUNCTION_NAME]]", functionName);
|
|
18679
|
-
}
|
|
18686
|
+
replaceFunctionNamePlaceholder(e, functionName);
|
|
18680
18687
|
}
|
|
18681
18688
|
return e;
|
|
18682
18689
|
}
|
|
@@ -18691,21 +18698,29 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
18691
18698
|
return (obj?.message !== undefined &&
|
|
18692
18699
|
typeof obj.message === "string");
|
|
18693
18700
|
}
|
|
18694
|
-
function addResultHandling(compute) {
|
|
18695
|
-
return function (...args) {
|
|
18701
|
+
function addResultHandling(compute, functionName) {
|
|
18702
|
+
return function computeWithResultHandling(...args) {
|
|
18696
18703
|
const result = compute.apply(this, args);
|
|
18697
18704
|
if (!isMatrix(result)) {
|
|
18698
18705
|
if (typeof result === "object" && result !== null && "value" in result) {
|
|
18706
|
+
replaceFunctionNamePlaceholder(result, functionName);
|
|
18699
18707
|
return result;
|
|
18700
18708
|
}
|
|
18701
18709
|
return { value: result };
|
|
18702
18710
|
}
|
|
18703
18711
|
if (typeof result[0][0] === "object" && result[0][0] !== null && "value" in result[0][0]) {
|
|
18712
|
+
matrixForEach(result, (result) => replaceFunctionNamePlaceholder(result, functionName));
|
|
18704
18713
|
return result;
|
|
18705
18714
|
}
|
|
18706
18715
|
return matrixMap(result, (row) => ({ value: row }));
|
|
18707
18716
|
};
|
|
18708
18717
|
}
|
|
18718
|
+
function replaceFunctionNamePlaceholder(fPayload, functionName) {
|
|
18719
|
+
// for performance reasons: change in place and only if needed
|
|
18720
|
+
if (fPayload.message?.includes("[[FUNCTION_NAME]]")) {
|
|
18721
|
+
fPayload.message = fPayload.message.replace("[[FUNCTION_NAME]]", functionName);
|
|
18722
|
+
}
|
|
18723
|
+
}
|
|
18709
18724
|
const functionRegistry = new FunctionRegistry();
|
|
18710
18725
|
for (let category of categories) {
|
|
18711
18726
|
const fns = category.functions;
|
|
@@ -19473,7 +19488,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
19473
19488
|
const xLabel = tooltipItem.dataset?.label || tooltipItem.label;
|
|
19474
19489
|
// tooltipItem.parsed.y can be an object or a number for pie charts
|
|
19475
19490
|
const yLabel = tooltipItem.parsed.y ?? tooltipItem.parsed;
|
|
19476
|
-
const toolTipFormat = !format && yLabel
|
|
19491
|
+
const toolTipFormat = !format && Math.abs(yLabel) >= 1000 ? "#,##" : format;
|
|
19477
19492
|
const yLabelStr = formatValue(yLabel, { format: toolTipFormat, locale });
|
|
19478
19493
|
return xLabel ? `${xLabel}: ${yLabelStr}` : yLabelStr;
|
|
19479
19494
|
},
|
|
@@ -19773,7 +19788,10 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
19773
19788
|
if (isNaN(value))
|
|
19774
19789
|
return value;
|
|
19775
19790
|
const { locale, format } = localeFormat;
|
|
19776
|
-
return formatValue(value, {
|
|
19791
|
+
return formatValue(value, {
|
|
19792
|
+
locale,
|
|
19793
|
+
format: !format && Math.abs(value) >= 1000 ? "#,##" : format,
|
|
19794
|
+
});
|
|
19777
19795
|
},
|
|
19778
19796
|
},
|
|
19779
19797
|
},
|
|
@@ -20294,7 +20312,10 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
20294
20312
|
if (isNaN(value))
|
|
20295
20313
|
return value;
|
|
20296
20314
|
const { locale, format } = localeFormat;
|
|
20297
|
-
return formatValue(value, {
|
|
20315
|
+
return formatValue(value, {
|
|
20316
|
+
locale,
|
|
20317
|
+
format: !format && Math.abs(value) >= 1000 ? "#,##" : format,
|
|
20318
|
+
});
|
|
20298
20319
|
},
|
|
20299
20320
|
},
|
|
20300
20321
|
},
|
|
@@ -20634,7 +20655,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
20634
20655
|
const percentage = calculatePercentage(data, dataIndex);
|
|
20635
20656
|
const xLabel = tooltipItem.label || tooltipItem.dataset.label;
|
|
20636
20657
|
const yLabel = tooltipItem.parsed.y ?? tooltipItem.parsed;
|
|
20637
|
-
const toolTipFormat = !format && yLabel
|
|
20658
|
+
const toolTipFormat = !format && yLabel >= 1000 ? "#,##" : format;
|
|
20638
20659
|
const yLabelStr = formatValue(yLabel, { format: toolTipFormat, locale });
|
|
20639
20660
|
return xLabel ? `${xLabel}: ${yLabelStr} (${percentage}%)` : `${yLabelStr} (${percentage}%)`;
|
|
20640
20661
|
};
|
|
@@ -22886,6 +22907,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
22886
22907
|
this.save();
|
|
22887
22908
|
}
|
|
22888
22909
|
ev.stopPropagation();
|
|
22910
|
+
ev.preventDefault();
|
|
22889
22911
|
break;
|
|
22890
22912
|
case "Escape":
|
|
22891
22913
|
this.cancel();
|
|
@@ -32050,17 +32072,13 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
32050
32072
|
this.DOMFocusableElementStore.setFocusableElement(el);
|
|
32051
32073
|
}
|
|
32052
32074
|
this.contentHelper.updateEl(el);
|
|
32053
|
-
this.processTokenAtCursor();
|
|
32054
32075
|
});
|
|
32055
32076
|
owl.useEffect(() => {
|
|
32056
32077
|
this.processContent();
|
|
32057
32078
|
});
|
|
32058
|
-
owl.
|
|
32059
|
-
|
|
32060
|
-
|
|
32061
|
-
this.processTokenAtCursor();
|
|
32062
|
-
}
|
|
32063
|
-
});
|
|
32079
|
+
owl.useEffect(() => {
|
|
32080
|
+
this.processTokenAtCursor();
|
|
32081
|
+
}, () => [this.composerStore.editionMode !== "inactive"]);
|
|
32064
32082
|
}
|
|
32065
32083
|
// ---------------------------------------------------------------------------
|
|
32066
32084
|
// Handlers
|
|
@@ -33162,6 +33180,10 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
33162
33180
|
height: 0px;
|
|
33163
33181
|
}
|
|
33164
33182
|
}
|
|
33183
|
+
.o-figure-container {
|
|
33184
|
+
-webkit-user-select: none; // safari
|
|
33185
|
+
user-select: none;
|
|
33186
|
+
}
|
|
33165
33187
|
`;
|
|
33166
33188
|
/**
|
|
33167
33189
|
* Each figure ⭐ is positioned inside a container `div` placed and sized
|
|
@@ -40353,7 +40375,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
40353
40375
|
this.clearBorders(cmd.sheetId, cmd.target);
|
|
40354
40376
|
break;
|
|
40355
40377
|
case "REMOVE_COLUMNS_ROWS":
|
|
40356
|
-
for (let el of cmd.elements) {
|
|
40378
|
+
for (let el of [...cmd.elements].sort((a, b) => b - a)) {
|
|
40357
40379
|
if (cmd.dimension === "COL") {
|
|
40358
40380
|
this.shiftBordersHorizontally(cmd.sheetId, el + 1, -1);
|
|
40359
40381
|
}
|
|
@@ -45892,6 +45914,353 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
45892
45914
|
}
|
|
45893
45915
|
}
|
|
45894
45916
|
|
|
45917
|
+
/**
|
|
45918
|
+
* ####################################################
|
|
45919
|
+
* # INTRODUCTION
|
|
45920
|
+
* ####################################################
|
|
45921
|
+
*
|
|
45922
|
+
* This file contain the function recomputeZones.
|
|
45923
|
+
* This function try to recompute in a performant way
|
|
45924
|
+
* an ensemble of zones possibly overlapping to avoid
|
|
45925
|
+
* overlapping and to reduce the number of zones.
|
|
45926
|
+
*
|
|
45927
|
+
* It also allows to remove some zones from the ensemble.
|
|
45928
|
+
*
|
|
45929
|
+
* In the following example, 2 zones are overlapping.
|
|
45930
|
+
* Applying recomputeZones will return zones without
|
|
45931
|
+
* overlapping:
|
|
45932
|
+
*
|
|
45933
|
+
* ["B3:D4", "D2:E3"] ["B3:C4", "D2:D4", "E2:E3"]
|
|
45934
|
+
*
|
|
45935
|
+
* A B C D E A B C D E
|
|
45936
|
+
* 1 ___ 1 ___
|
|
45937
|
+
* 2 ___|_ | 2 ___| | |
|
|
45938
|
+
* 3 | |_|_| ---> 3 | | |_|
|
|
45939
|
+
* 4 |_____| 4 |___|_|
|
|
45940
|
+
* 6 6
|
|
45941
|
+
* 7 7
|
|
45942
|
+
*
|
|
45943
|
+
*
|
|
45944
|
+
* In the following example, 2 zones are contiguous.
|
|
45945
|
+
* Applying recomputeZones will return only one zone:
|
|
45946
|
+
*
|
|
45947
|
+
* ["B2:B3", "C2:D3"] ["B2:D3"]
|
|
45948
|
+
*
|
|
45949
|
+
* A B C D E A B C D E
|
|
45950
|
+
* 1 _ ___ 1 _____
|
|
45951
|
+
* 2 | | | ---> 2 | |
|
|
45952
|
+
* 3 |_|___| 3 |_____|
|
|
45953
|
+
* 4 4
|
|
45954
|
+
*
|
|
45955
|
+
*
|
|
45956
|
+
* In the following example, we want to remove a zone
|
|
45957
|
+
* from the ensemble. Applying recomputeZones will
|
|
45958
|
+
* return the ensemble without the zone to remove:
|
|
45959
|
+
*
|
|
45960
|
+
* remove ["C3:D3"] ["B2:B4", "C2:D2",
|
|
45961
|
+
* "C4:D4", "E2:E4"]
|
|
45962
|
+
*
|
|
45963
|
+
* A B C D E F A B C D E F
|
|
45964
|
+
* 1 _______ 1 _______
|
|
45965
|
+
* 2 | | ---> 2 | |___| |
|
|
45966
|
+
* 3 | xxx | 3 | |___| |
|
|
45967
|
+
* 4 |_______| 4 |_|___|_|
|
|
45968
|
+
* 5 5
|
|
45969
|
+
*
|
|
45970
|
+
*
|
|
45971
|
+
* The exercise seems simple when we have only 2 zones.
|
|
45972
|
+
* But with n zones and in a performant way, we want to
|
|
45973
|
+
* avoid comparing each zone with all the others.
|
|
45974
|
+
*
|
|
45975
|
+
*
|
|
45976
|
+
* ####################################################
|
|
45977
|
+
* # Methodological approach
|
|
45978
|
+
* ####################################################
|
|
45979
|
+
*
|
|
45980
|
+
* The methodological approach to avoid comparing each
|
|
45981
|
+
* zone with all the others is to use a data structure
|
|
45982
|
+
* that allow to quickly find which zones are
|
|
45983
|
+
* overlapping with any other given zone.
|
|
45984
|
+
*
|
|
45985
|
+
* Here the idea is to profile the zones at the columns level.
|
|
45986
|
+
*
|
|
45987
|
+
* To do that, we propose to use a data structure
|
|
45988
|
+
* composed of 2 parts:
|
|
45989
|
+
* - profilesStartingPosition: a sorted number array
|
|
45990
|
+
* indicating on which columns a new profile begins.
|
|
45991
|
+
* - profiles: a map where the key is a column
|
|
45992
|
+
* position (from profilesStartingPosition) and the
|
|
45993
|
+
* value is a sorted number array representing a
|
|
45994
|
+
* profile.
|
|
45995
|
+
*
|
|
45996
|
+
*
|
|
45997
|
+
* See the following example: here profileStartingPosition
|
|
45998
|
+
* corresponds to [A,C,E,G,K]
|
|
45999
|
+
* A B C D E F G H I J K so with number [0,2,4,6,10]
|
|
46000
|
+
* 1 ' ' ' '
|
|
46001
|
+
* 2 ' ' '_______' here profile correspond
|
|
46002
|
+
* 3 '___' |_______| for A to []
|
|
46003
|
+
* 4 | | for C to [3, 5]
|
|
46004
|
+
* 5 |___| for E to []
|
|
46005
|
+
* 6 for G to [2, 3]
|
|
46006
|
+
* 7 for K to []
|
|
46007
|
+
*
|
|
46008
|
+
*
|
|
46009
|
+
* Now we can easily find which zones are overlapping
|
|
46010
|
+
* with a given zone. Suppose we want to add a new zone
|
|
46011
|
+
* D5:H6 to the ensemble:
|
|
46012
|
+
*
|
|
46013
|
+
* With a binary search of left and right
|
|
46014
|
+
* A B C D E F G H I J K on profilesStartingPosition, we can
|
|
46015
|
+
* 1 ' ' ' ' find the indexes of the profiles on which
|
|
46016
|
+
* 2 ' ' '_______' to apply a modification.
|
|
46017
|
+
* 3 '___' |_______|
|
|
46018
|
+
* 4 | _|_______ Here we will:
|
|
46019
|
+
* 5 |_|_| | - add a new profile in D --> become [3, 6]
|
|
46020
|
+
* 6 |_________| - modify the profile in E --> become [4, 6]
|
|
46021
|
+
* 7 - modify the profile in G --> become [2, 3, 4, 6]
|
|
46022
|
+
* - add a new profile in I --> become [8, 10]
|
|
46023
|
+
*
|
|
46024
|
+
* See below the result:
|
|
46025
|
+
*
|
|
46026
|
+
* Note the particularity of the profile
|
|
46027
|
+
* A B C D E F G H I J K for G: it will correspond to [2, 3, 4, 6]
|
|
46028
|
+
* 1 ' ' ' ' ' '
|
|
46029
|
+
* 2 ' ' ' '___'___' To know how to modify the profile (add a
|
|
46030
|
+
* 3 '_'_' |___|___| zone or remove it) we do a binary
|
|
46031
|
+
* 4 | | |___ ___ search of the top and bottom value on the
|
|
46032
|
+
* 5 |_| | | | profile array. Depending on the result index
|
|
46033
|
+
* 6 |_|___|___| parity (odd or even), because zone boundaries
|
|
46034
|
+
* 7 go by pairs, we know if we are in a zone or
|
|
46035
|
+
* not and how operate.
|
|
46036
|
+
*/
|
|
46037
|
+
/**
|
|
46038
|
+
* Recompute the zone without the cells in toRemoveZones and avoid overlapping.
|
|
46039
|
+
* This compute is particularly useful because after this function:
|
|
46040
|
+
* - you will find coordinate of a cell only once among all the zones
|
|
46041
|
+
* - the number of zones will be reduced to the minimum
|
|
46042
|
+
*/
|
|
46043
|
+
function futureRecomputeZones(zones, zonesToRemove = []) {
|
|
46044
|
+
const profilesStartingPosition = [0];
|
|
46045
|
+
const profiles = new Map([[0, []]]);
|
|
46046
|
+
modifyProfiles(profilesStartingPosition, profiles, zones, false);
|
|
46047
|
+
modifyProfiles(profilesStartingPosition, profiles, zonesToRemove, true);
|
|
46048
|
+
return constructZonesFromProfiles(profilesStartingPosition, profiles);
|
|
46049
|
+
}
|
|
46050
|
+
function modifyProfiles(// export for testing only
|
|
46051
|
+
profilesStartingPosition, profiles, zones, toRemove = false) {
|
|
46052
|
+
for (const zone of zones) {
|
|
46053
|
+
const leftValue = zone.left;
|
|
46054
|
+
const rightValue = zone.right === undefined ? undefined : zone.right + 1;
|
|
46055
|
+
const leftIndex = findIndexAndCreateProfile(profilesStartingPosition, profiles, leftValue, true, 0);
|
|
46056
|
+
const rightIndex = findIndexAndCreateProfile(profilesStartingPosition, profiles, rightValue, false, leftIndex);
|
|
46057
|
+
for (let i = leftIndex; i <= rightIndex; i++) {
|
|
46058
|
+
const profile = profiles.get(profilesStartingPosition[i]);
|
|
46059
|
+
modifyProfile(profile, zone, toRemove);
|
|
46060
|
+
}
|
|
46061
|
+
// maybe this part cost in performance, and maybe it's not necessary (depending on the use case). To be checked
|
|
46062
|
+
removeContiguousProfiles(profilesStartingPosition, profiles, leftIndex, rightIndex);
|
|
46063
|
+
}
|
|
46064
|
+
}
|
|
46065
|
+
function findIndexAndCreateProfile(profilesStartingPosition, profiles, value, searchLeft, startIndex) {
|
|
46066
|
+
if (value === undefined) {
|
|
46067
|
+
// this is only the case when the value correspond to a bottom value that could be undefined
|
|
46068
|
+
return profilesStartingPosition.length - 1;
|
|
46069
|
+
}
|
|
46070
|
+
const predecessorIndex = binaryPredecessorSearch(profilesStartingPosition, value, startIndex);
|
|
46071
|
+
if (value != profilesStartingPosition[predecessorIndex]) {
|
|
46072
|
+
// mean that the value is not ending/starting at the same position as the previous/next profile
|
|
46073
|
+
// --> it's a new profile
|
|
46074
|
+
// --> we need to add it
|
|
46075
|
+
profilesStartingPosition.splice(predecessorIndex + 1, 0, value);
|
|
46076
|
+
// suppose the we want to add the for the left value
|
|
46077
|
+
// following profile following zone: 'C', the predecessor index
|
|
46078
|
+
// for B: [1, 3] "C3:D4" correspond to 'B'.
|
|
46079
|
+
// The next line code will
|
|
46080
|
+
// A B C D A B C D copy the profile of 'B'
|
|
46081
|
+
// 1 '___' 1 '___' to 'C'. In the rest of the
|
|
46082
|
+
// 2 | | ---> 2 | _|_ process the 'modifyProfile'
|
|
46083
|
+
// 3 |___| 3 |_|_| | function will adapt the waiting
|
|
46084
|
+
// 4 4 |___| 'C' profile [1, 3] to the
|
|
46085
|
+
// correct 'C' profile [1, 4]
|
|
46086
|
+
profiles.set(value, [...profiles.get(profilesStartingPosition[predecessorIndex])]);
|
|
46087
|
+
return searchLeft ? predecessorIndex + 1 : predecessorIndex;
|
|
46088
|
+
}
|
|
46089
|
+
return searchLeft ? predecessorIndex : predecessorIndex - 1;
|
|
46090
|
+
}
|
|
46091
|
+
/**
|
|
46092
|
+
* Suppose the following Suppose we want to add We want to have the
|
|
46093
|
+
* profile: the following zone: following profile:
|
|
46094
|
+
*
|
|
46095
|
+
* A B C D E F A B C D E F A B C D E F
|
|
46096
|
+
* 1 '___' 1 ' ' 1 '___'
|
|
46097
|
+
* 2 |___| 2 '___' 2 | |
|
|
46098
|
+
* 3 ' ' 3 | | 3 | |
|
|
46099
|
+
* 4 '___' --> 4 | | --> 4 | |
|
|
46100
|
+
* 6 | | 6 |___| 6 | |
|
|
46101
|
+
* 7 |___| 7 7 |___|
|
|
46102
|
+
* 8 8 8
|
|
46103
|
+
*
|
|
46104
|
+
* the profile for 'C' the top zone correspond Here [2, 3, 5, 8] with [3, 7]
|
|
46105
|
+
* corresponds to: to 3 and the bottom zone would be merged into [2, 8]
|
|
46106
|
+
* ____ ____ correspond to 6
|
|
46107
|
+
* [2, 3, 5, 8] would be the profile: The difficulty of modify profile
|
|
46108
|
+
* ____ is to know what must be deleted
|
|
46109
|
+
* Note that the 'filled [3, 7] and what must be added to the
|
|
46110
|
+
* zone' are always between existing profile.
|
|
46111
|
+
* an even index and its
|
|
46112
|
+
* next index
|
|
46113
|
+
*
|
|
46114
|
+
*/
|
|
46115
|
+
function modifyProfile(profile, zone, toRemove = false) {
|
|
46116
|
+
const topValue = zone.top;
|
|
46117
|
+
const bottomValue = zone.bottom === undefined ? undefined : zone.bottom + 1;
|
|
46118
|
+
const newPoints = [];
|
|
46119
|
+
// Case we want to add a zone to the profile:
|
|
46120
|
+
// - If the top predecessor index `topPredIndex` is even, it means the top of the zone is already positioned on a filled zone
|
|
46121
|
+
// so we don't need to add it to the profile. we can keep in reference the index of the predecessor.
|
|
46122
|
+
// - If it is odd, it means the top of the zone must be the beginning of a filled zone.
|
|
46123
|
+
// so we can keep the index of the top position
|
|
46124
|
+
// Case we want to remove a zone from the profile: it's the opposite of the previous case
|
|
46125
|
+
const topPredIndex = binaryPredecessorSearch(profile, topValue, 0, false);
|
|
46126
|
+
if ((topPredIndex % 2 !== 0 && !toRemove) || (topPredIndex % 2 === 0 && toRemove)) {
|
|
46127
|
+
newPoints.push(topValue);
|
|
46128
|
+
}
|
|
46129
|
+
if (bottomValue === undefined) {
|
|
46130
|
+
// The following two code lines will not impact the final result,
|
|
46131
|
+
// but they will impact the intermediate profile.
|
|
46132
|
+
// We keep them for performance reason
|
|
46133
|
+
profile.splice(topPredIndex + 1);
|
|
46134
|
+
profile.push(...newPoints);
|
|
46135
|
+
return;
|
|
46136
|
+
}
|
|
46137
|
+
// Case we want to add a zone to the profile:
|
|
46138
|
+
// - If the bottom successor index `bottomSuccIndex` is even, it means the bottom of the zone must be the ending of a filled zone
|
|
46139
|
+
// so we can keep the index of the bottom position.
|
|
46140
|
+
// - If it is odd, it means the bottom of the zone is already positioned on a filled zone
|
|
46141
|
+
// so we don't need to add it to the profile. we can keep in reference the index of the successor
|
|
46142
|
+
// Case we want to remove a zone from the profile: it's the opposite of the previous case
|
|
46143
|
+
const bottomSuccIndex = binarySuccessorSearch(profile, bottomValue, 0, false);
|
|
46144
|
+
if ((bottomSuccIndex % 2 === 0 && !toRemove) || (bottomSuccIndex % 2 !== 0 && toRemove)) {
|
|
46145
|
+
newPoints.push(bottomValue);
|
|
46146
|
+
}
|
|
46147
|
+
// add the top and bottom value to the profile and
|
|
46148
|
+
// remove all information between the top and bottom index
|
|
46149
|
+
profile.splice(topPredIndex + 1, bottomSuccIndex - topPredIndex - 1, ...newPoints);
|
|
46150
|
+
}
|
|
46151
|
+
function removeContiguousProfiles(profilesStartingPosition, profiles, leftIndex, rightIndex) {
|
|
46152
|
+
const start = leftIndex - 1 === -1 ? 0 : leftIndex - 1;
|
|
46153
|
+
const end = rightIndex === profilesStartingPosition.length - 1 ? rightIndex : rightIndex + 1;
|
|
46154
|
+
for (let i = end; i > start; i--) {
|
|
46155
|
+
if (deepEqualsArray(profiles.get(profilesStartingPosition[i]), profiles.get(profilesStartingPosition[i - 1]))) {
|
|
46156
|
+
profiles.delete(profilesStartingPosition[i]);
|
|
46157
|
+
profilesStartingPosition.splice(i, 1);
|
|
46158
|
+
}
|
|
46159
|
+
}
|
|
46160
|
+
}
|
|
46161
|
+
function constructZonesFromProfiles(profilesStartingPosition, profiles) {
|
|
46162
|
+
const mergedZone = [];
|
|
46163
|
+
let pendingZones = [];
|
|
46164
|
+
for (let colIndex = 0; colIndex < profilesStartingPosition.length; colIndex++) {
|
|
46165
|
+
const left = profilesStartingPosition[colIndex];
|
|
46166
|
+
const profile = profiles.get(left);
|
|
46167
|
+
if (!profile || profile.length === 0) {
|
|
46168
|
+
mergedZone.push(...pendingZones);
|
|
46169
|
+
pendingZones = [];
|
|
46170
|
+
continue;
|
|
46171
|
+
}
|
|
46172
|
+
let right = profilesStartingPosition[colIndex + 1];
|
|
46173
|
+
if (right !== undefined) {
|
|
46174
|
+
right--;
|
|
46175
|
+
}
|
|
46176
|
+
const nextPendingZones = [];
|
|
46177
|
+
for (let i = 0; i < profile.length; i += 2) {
|
|
46178
|
+
const top = profile[i];
|
|
46179
|
+
let bottom = profile[i + 1];
|
|
46180
|
+
if (bottom !== undefined) {
|
|
46181
|
+
bottom--;
|
|
46182
|
+
}
|
|
46183
|
+
const profileZone = {
|
|
46184
|
+
top,
|
|
46185
|
+
left,
|
|
46186
|
+
bottom,
|
|
46187
|
+
right,
|
|
46188
|
+
hasHeader: (bottom === undefined && top !== 0) || (right === undefined && left !== 0),
|
|
46189
|
+
};
|
|
46190
|
+
let findCorrespondingZone = false;
|
|
46191
|
+
for (let j = pendingZones.length - 1; j >= 0; j--) {
|
|
46192
|
+
const pendingZone = pendingZones[j];
|
|
46193
|
+
if (pendingZone.top === profileZone.top && pendingZone.bottom === profileZone.bottom) {
|
|
46194
|
+
pendingZone.right = profileZone.right;
|
|
46195
|
+
pendingZones.splice(j, 1);
|
|
46196
|
+
nextPendingZones.push(pendingZone);
|
|
46197
|
+
findCorrespondingZone = true;
|
|
46198
|
+
break;
|
|
46199
|
+
}
|
|
46200
|
+
}
|
|
46201
|
+
if (!findCorrespondingZone) {
|
|
46202
|
+
nextPendingZones.push(profileZone);
|
|
46203
|
+
}
|
|
46204
|
+
}
|
|
46205
|
+
mergedZone.push(...pendingZones);
|
|
46206
|
+
pendingZones = nextPendingZones;
|
|
46207
|
+
}
|
|
46208
|
+
mergedZone.push(...pendingZones);
|
|
46209
|
+
return mergedZone;
|
|
46210
|
+
}
|
|
46211
|
+
function binaryPredecessorSearch(arr, val, start = 0, matchEqual = true) {
|
|
46212
|
+
let end = arr.length - 1;
|
|
46213
|
+
let result = -1;
|
|
46214
|
+
while (start <= end) {
|
|
46215
|
+
const mid = Math.floor((start + end) / 2);
|
|
46216
|
+
if (arr[mid] === val && matchEqual) {
|
|
46217
|
+
return mid;
|
|
46218
|
+
}
|
|
46219
|
+
else if (arr[mid] < val) {
|
|
46220
|
+
result = mid;
|
|
46221
|
+
start = mid + 1;
|
|
46222
|
+
}
|
|
46223
|
+
else {
|
|
46224
|
+
end = mid - 1;
|
|
46225
|
+
}
|
|
46226
|
+
}
|
|
46227
|
+
return result;
|
|
46228
|
+
}
|
|
46229
|
+
function binarySuccessorSearch(arr, val, start = 0, matchEqual = true) {
|
|
46230
|
+
let end = arr.length - 1;
|
|
46231
|
+
let result = arr.length;
|
|
46232
|
+
while (start <= end) {
|
|
46233
|
+
const mid = Math.floor((start + end) / 2);
|
|
46234
|
+
if (arr[mid] === val && matchEqual) {
|
|
46235
|
+
return mid;
|
|
46236
|
+
}
|
|
46237
|
+
else if (arr[mid] > val) {
|
|
46238
|
+
result = mid;
|
|
46239
|
+
end = mid - 1;
|
|
46240
|
+
}
|
|
46241
|
+
else {
|
|
46242
|
+
start = mid + 1;
|
|
46243
|
+
}
|
|
46244
|
+
}
|
|
46245
|
+
return result;
|
|
46246
|
+
}
|
|
46247
|
+
/**
|
|
46248
|
+
* Compares two arrays.
|
|
46249
|
+
* For performance reasons, this function is to be preferred
|
|
46250
|
+
* to 'deepEquals' in the case we know that the inputs are arrays.
|
|
46251
|
+
*/
|
|
46252
|
+
function deepEqualsArray(arr1, arr2) {
|
|
46253
|
+
if (arr1.length !== arr2.length) {
|
|
46254
|
+
return false;
|
|
46255
|
+
}
|
|
46256
|
+
for (let i = 0; i < arr1.length; i++) {
|
|
46257
|
+
if (!deepEquals(arr1[i], arr2[i])) {
|
|
46258
|
+
return false;
|
|
46259
|
+
}
|
|
46260
|
+
}
|
|
46261
|
+
return true;
|
|
46262
|
+
}
|
|
46263
|
+
|
|
45895
46264
|
class PositionMap {
|
|
45896
46265
|
map = {};
|
|
45897
46266
|
set({ sheetId, col, row }, value) {
|
|
@@ -46690,7 +47059,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
46690
47059
|
}
|
|
46691
47060
|
}
|
|
46692
47061
|
/**
|
|
46693
|
-
* Return the
|
|
47062
|
+
* Return all the cells that depend on the provided ranges,
|
|
46694
47063
|
* in the correct order they should be evaluated.
|
|
46695
47064
|
* This is called a topological ordering (excluding cycles)
|
|
46696
47065
|
*/
|
|
@@ -46701,11 +47070,19 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
46701
47070
|
const range = queue.pop();
|
|
46702
47071
|
visited.addMany(positions(range.zone).map((position) => ({ sheetId: range.sheetId, ...position })));
|
|
46703
47072
|
const impactedPositions = this.rTree.search(range).map((dep) => dep.data);
|
|
47073
|
+
const nextInQueue = {};
|
|
46704
47074
|
for (const position of impactedPositions) {
|
|
46705
47075
|
if (!visited.has(position)) {
|
|
46706
|
-
|
|
47076
|
+
if (!nextInQueue[position.sheetId]) {
|
|
47077
|
+
nextInQueue[position.sheetId] = [];
|
|
47078
|
+
}
|
|
47079
|
+
nextInQueue[position.sheetId].push(positionToZone(position));
|
|
46707
47080
|
}
|
|
46708
47081
|
}
|
|
47082
|
+
for (const sheetId in nextInQueue) {
|
|
47083
|
+
const zones = futureRecomputeZones(nextInQueue[sheetId]);
|
|
47084
|
+
queue.push(...zones.map((zone) => ({ sheetId, zone })));
|
|
47085
|
+
}
|
|
46709
47086
|
}
|
|
46710
47087
|
visited.deleteMany(ranges.flatMap((r) => positions(r.zone).map((position) => ({ sheetId: r.sheetId, ...position }))));
|
|
46711
47088
|
return visited;
|
|
@@ -47044,7 +47421,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
47044
47421
|
}
|
|
47045
47422
|
if (!content) {
|
|
47046
47423
|
// The previous content could have blocked some array formulas
|
|
47047
|
-
impactedPositions.addMany(this.
|
|
47424
|
+
impactedPositions.addMany(this.getArrayFormulasBlockedBy(position));
|
|
47048
47425
|
}
|
|
47049
47426
|
}
|
|
47050
47427
|
return impactedPositions;
|
|
@@ -47087,14 +47464,23 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
47087
47464
|
positions.fillAllPositions();
|
|
47088
47465
|
return positions;
|
|
47089
47466
|
}
|
|
47090
|
-
|
|
47467
|
+
/**
|
|
47468
|
+
* Return the position of formulas blocked by the given position
|
|
47469
|
+
* as well as all their dependencies.
|
|
47470
|
+
*/
|
|
47471
|
+
getArrayFormulasBlockedBy(position) {
|
|
47091
47472
|
if (!this.spreadingRelations.hasArrayFormulaResult(position)) {
|
|
47092
47473
|
return [];
|
|
47093
47474
|
}
|
|
47094
47475
|
const arrayFormulas = this.spreadingRelations.getFormulaPositionsSpreadingOn(position);
|
|
47095
47476
|
const positions = this.createEmptyPositionSet();
|
|
47096
47477
|
positions.addMany(arrayFormulas);
|
|
47097
|
-
|
|
47478
|
+
const arrayFormulaPosition = this.getArrayFormulaSpreadingOn(position);
|
|
47479
|
+
if (arrayFormulaPosition) {
|
|
47480
|
+
// ignore the formula spreading on the position. Keep only the blocked ones
|
|
47481
|
+
positions.delete(arrayFormulaPosition);
|
|
47482
|
+
}
|
|
47483
|
+
positions.addMany(this.getCellsDependingOn(positions));
|
|
47098
47484
|
return positions;
|
|
47099
47485
|
}
|
|
47100
47486
|
nextPositionsToUpdate = new PositionSet({});
|
|
@@ -47234,7 +47620,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
47234
47620
|
}
|
|
47235
47621
|
this.evaluatedCells.delete(child);
|
|
47236
47622
|
this.nextPositionsToUpdate.addMany(this.getCellsDependingOn([child]));
|
|
47237
|
-
this.nextPositionsToUpdate.addMany(this.
|
|
47623
|
+
this.nextPositionsToUpdate.addMany(this.getArrayFormulasBlockedBy(child));
|
|
47238
47624
|
}
|
|
47239
47625
|
this.spreadingRelations.removeNode(position);
|
|
47240
47626
|
}
|
|
@@ -52085,7 +52471,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
52085
52471
|
"isFilterActive",
|
|
52086
52472
|
];
|
|
52087
52473
|
filterValues = {};
|
|
52088
|
-
hiddenRows =
|
|
52474
|
+
hiddenRows = {};
|
|
52089
52475
|
isEvaluationDirty = false;
|
|
52090
52476
|
allowDispatch(cmd) {
|
|
52091
52477
|
switch (cmd.type) {
|
|
@@ -52129,11 +52515,11 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
52129
52515
|
case "UNFOLD_HEADER_GROUP":
|
|
52130
52516
|
case "FOLD_ALL_HEADER_GROUPS":
|
|
52131
52517
|
case "UNFOLD_ALL_HEADER_GROUPS":
|
|
52132
|
-
this.updateHiddenRows();
|
|
52518
|
+
this.updateHiddenRows(cmd.sheetId);
|
|
52133
52519
|
break;
|
|
52134
52520
|
case "UPDATE_FILTER":
|
|
52135
52521
|
this.updateFilter(cmd);
|
|
52136
|
-
this.updateHiddenRows();
|
|
52522
|
+
this.updateHiddenRows(cmd.sheetId);
|
|
52137
52523
|
break;
|
|
52138
52524
|
case "DUPLICATE_SHEET":
|
|
52139
52525
|
const filterValues = {};
|
|
@@ -52153,15 +52539,14 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
52153
52539
|
}
|
|
52154
52540
|
finalize() {
|
|
52155
52541
|
if (this.isEvaluationDirty) {
|
|
52156
|
-
this.
|
|
52542
|
+
for (const sheetId of this.getters.getSheetIds()) {
|
|
52543
|
+
this.updateHiddenRows(sheetId);
|
|
52544
|
+
}
|
|
52157
52545
|
this.isEvaluationDirty = false;
|
|
52158
52546
|
}
|
|
52159
52547
|
}
|
|
52160
52548
|
isRowFiltered(sheetId, row) {
|
|
52161
|
-
|
|
52162
|
-
return false;
|
|
52163
|
-
}
|
|
52164
|
-
return this.hiddenRows.has(row);
|
|
52549
|
+
return !!this.hiddenRows[sheetId]?.has(row);
|
|
52165
52550
|
}
|
|
52166
52551
|
getFilterHiddenValues(position) {
|
|
52167
52552
|
const id = this.getters.getFilterId(position);
|
|
@@ -52188,8 +52573,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
52188
52573
|
this.filterValues[sheetId] = {};
|
|
52189
52574
|
this.filterValues[sheetId][id] = hiddenValues;
|
|
52190
52575
|
}
|
|
52191
|
-
updateHiddenRows() {
|
|
52192
|
-
const sheetId = this.getters.getActiveSheetId();
|
|
52576
|
+
updateHiddenRows(sheetId) {
|
|
52193
52577
|
const filters = this.getters
|
|
52194
52578
|
.getFilters(sheetId)
|
|
52195
52579
|
.sort((filter1, filter2) => filter1.rangeWithHeaders.zone.top - filter2.rangeWithHeaders.zone.top);
|
|
@@ -52211,7 +52595,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
52211
52595
|
}
|
|
52212
52596
|
}
|
|
52213
52597
|
}
|
|
52214
|
-
this.hiddenRows = hiddenRows;
|
|
52598
|
+
this.hiddenRows[sheetId] = hiddenRows;
|
|
52215
52599
|
}
|
|
52216
52600
|
getCellValueAsString(sheetId, col, row) {
|
|
52217
52601
|
const value = this.getters.getEvaluatedCell({ sheetId, col, row }).formattedValue;
|
|
@@ -53330,13 +53714,14 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
53330
53714
|
}
|
|
53331
53715
|
}
|
|
53332
53716
|
handleEvent(event) {
|
|
53717
|
+
const sheetId = this.getters.getActiveSheetId();
|
|
53333
53718
|
if (event.options.scrollIntoView) {
|
|
53334
53719
|
let { col, row } = findCellInNewZone(event.previousAnchor.zone, event.anchor.zone);
|
|
53335
53720
|
if (event.mode === "updateAnchor") {
|
|
53336
53721
|
const oldZone = event.previousAnchor.zone;
|
|
53337
53722
|
const newZone = event.anchor.zone;
|
|
53338
53723
|
// altering a zone should not move the viewport in a dimension that wasn't changed
|
|
53339
|
-
const { top, bottom, left, right } = this.
|
|
53724
|
+
const { top, bottom, left, right } = this.getMainInternalViewport(sheetId);
|
|
53340
53725
|
if (oldZone.left === newZone.left && oldZone.right === newZone.right) {
|
|
53341
53726
|
col = left > col || col > right ? left : col;
|
|
53342
53727
|
}
|
|
@@ -53344,7 +53729,6 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
53344
53729
|
row = top > row || row > bottom ? top : row;
|
|
53345
53730
|
}
|
|
53346
53731
|
}
|
|
53347
|
-
const sheetId = this.getters.getActiveSheetId();
|
|
53348
53732
|
col = Math.min(col, this.getters.getNumberCols(sheetId) - 1);
|
|
53349
53733
|
row = Math.min(row, this.getters.getNumberRows(sheetId) - 1);
|
|
53350
53734
|
if (!this.sheetsWithDirtyViewports.has(sheetId)) {
|
|
@@ -53381,16 +53765,16 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
53381
53765
|
this.setSheetViewOffset(cmd.offsetX, cmd.offsetY);
|
|
53382
53766
|
break;
|
|
53383
53767
|
case "SHIFT_VIEWPORT_DOWN":
|
|
53384
|
-
const { top } = this.getActiveMainViewport();
|
|
53385
53768
|
const sheetId = this.getters.getActiveSheetId();
|
|
53386
|
-
const
|
|
53387
|
-
this.
|
|
53769
|
+
const { top, viewportHeight, offsetCorrectionY } = this.getMainInternalViewport(sheetId);
|
|
53770
|
+
const topRowDims = this.getters.getRowDimensions(sheetId, top);
|
|
53771
|
+
this.shiftVertically(topRowDims.start + viewportHeight - offsetCorrectionY);
|
|
53388
53772
|
break;
|
|
53389
53773
|
case "SHIFT_VIEWPORT_UP": {
|
|
53390
|
-
const { top } = this.getActiveMainViewport();
|
|
53391
53774
|
const sheetId = this.getters.getActiveSheetId();
|
|
53392
|
-
const
|
|
53393
|
-
this.
|
|
53775
|
+
const { top, viewportHeight, offsetCorrectionY } = this.getMainInternalViewport(sheetId);
|
|
53776
|
+
const topRowDims = this.getters.getRowDimensions(sheetId, top);
|
|
53777
|
+
this.shiftVertically(topRowDims.end - offsetCorrectionY - viewportHeight);
|
|
53394
53778
|
break;
|
|
53395
53779
|
}
|
|
53396
53780
|
case "REMOVE_TABLE":
|
|
@@ -53813,17 +54197,6 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
53813
54197
|
const { maxOffsetX, maxOffsetY } = this.getMaximumSheetOffset();
|
|
53814
54198
|
Object.values(this.getSubViewports(sheetId)).forEach((viewport) => viewport.setViewportOffset(clip(offsetX, 0, maxOffsetX), clip(offsetY, 0, maxOffsetY)));
|
|
53815
54199
|
}
|
|
53816
|
-
/**
|
|
53817
|
-
* Clip the vertical offset within the allowed range.
|
|
53818
|
-
* Not above the sheet, nor below the sheet.
|
|
53819
|
-
*/
|
|
53820
|
-
clipOffsetY(offsetY) {
|
|
53821
|
-
const { height } = this.getMainViewportRect();
|
|
53822
|
-
const maxOffset = height - this.sheetViewHeight;
|
|
53823
|
-
offsetY = Math.min(offsetY, maxOffset);
|
|
53824
|
-
offsetY = Math.max(offsetY, 0);
|
|
53825
|
-
return offsetY;
|
|
53826
|
-
}
|
|
53827
54200
|
getViewportOffset(sheetId) {
|
|
53828
54201
|
return {
|
|
53829
54202
|
x: this.viewports[sheetId]?.bottomRight.offsetScrollbarX || 0,
|
|
@@ -53879,12 +54252,15 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
53879
54252
|
* viewport top.
|
|
53880
54253
|
*/
|
|
53881
54254
|
shiftVertically(offset) {
|
|
53882
|
-
const
|
|
54255
|
+
const sheetId = this.getters.getActiveSheetId();
|
|
54256
|
+
const { top } = this.getMainInternalViewport(sheetId);
|
|
53883
54257
|
const { scrollX } = this.getActiveSheetScrollInfo();
|
|
53884
54258
|
this.setSheetViewOffset(scrollX, offset);
|
|
53885
54259
|
const { anchor } = this.getters.getSelection();
|
|
53886
|
-
|
|
53887
|
-
|
|
54260
|
+
if (anchor.cell.row >= this.getters.getPaneDivisions(sheetId).ySplit) {
|
|
54261
|
+
const deltaRow = this.getMainInternalViewport(sheetId).top - top;
|
|
54262
|
+
this.selection.selectCell(anchor.cell.col, anchor.cell.row + deltaRow);
|
|
54263
|
+
}
|
|
53888
54264
|
}
|
|
53889
54265
|
getVisibleFigures() {
|
|
53890
54266
|
const sheetId = this.getters.getActiveSheetId();
|
|
@@ -54803,7 +55179,6 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
54803
55179
|
cursor: pointer;
|
|
54804
55180
|
}
|
|
54805
55181
|
`;
|
|
54806
|
-
let tKey = 1;
|
|
54807
55182
|
class SpreadsheetDashboard extends owl.Component {
|
|
54808
55183
|
static template = "o-spreadsheet-SpreadsheetDashboard";
|
|
54809
55184
|
static props = {};
|
|
@@ -54882,13 +55257,9 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
54882
55257
|
coordinates: rect,
|
|
54883
55258
|
position: { col, row },
|
|
54884
55259
|
action,
|
|
54885
|
-
// we can't rely on position only because a row or a column could
|
|
54886
|
-
// be inserted at any time.
|
|
54887
|
-
tKey: `${tKey}-${col}-${row}`,
|
|
54888
55260
|
});
|
|
54889
55261
|
}
|
|
54890
55262
|
}
|
|
54891
|
-
tKey++;
|
|
54892
55263
|
return cells;
|
|
54893
55264
|
}
|
|
54894
55265
|
getClickableAction(position) {
|
|
@@ -60228,9 +60599,9 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
60228
60599
|
exports.tokenize = tokenize;
|
|
60229
60600
|
|
|
60230
60601
|
|
|
60231
|
-
__info__.version = "17.2.
|
|
60232
|
-
__info__.date = "2024-04-
|
|
60233
|
-
__info__.hash = "
|
|
60602
|
+
__info__.version = "17.2.5";
|
|
60603
|
+
__info__.date = "2024-04-26T07:41:13.193Z";
|
|
60604
|
+
__info__.hash = "a730f5c";
|
|
60234
60605
|
|
|
60235
60606
|
|
|
60236
60607
|
})(this.o_spreadsheet = this.o_spreadsheet || {}, owl);
|