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