@odoo/o-spreadsheet 17.0.55 → 17.0.57
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 +622 -478
- package/dist/o-spreadsheet.d.ts +19 -10
- package/dist/o-spreadsheet.esm.js +622 -478
- package/dist/o-spreadsheet.iife.js +622 -478
- package/dist/o-spreadsheet.iife.min.js +256 -256
- package/dist/o_spreadsheet.xml +3 -3
- 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 17.0.
|
|
6
|
-
* @date 2025-
|
|
7
|
-
* @hash
|
|
5
|
+
* @version 17.0.57
|
|
6
|
+
* @date 2025-04-14T17:13:56.677Z
|
|
7
|
+
* @hash 6ac8bb7
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
'use strict';
|
|
@@ -712,8 +712,7 @@ function removeFalsyAttributes(obj) {
|
|
|
712
712
|
*
|
|
713
713
|
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Character_Classes
|
|
714
714
|
*/
|
|
715
|
-
const
|
|
716
|
-
" ",
|
|
715
|
+
const specialWhiteSpaceSpecialCharacters = [
|
|
717
716
|
"\t",
|
|
718
717
|
"\f",
|
|
719
718
|
"\v",
|
|
@@ -728,7 +727,7 @@ const whiteSpaceSpecialCharacters = [
|
|
|
728
727
|
String.fromCharCode(parseInt("3000", 16)),
|
|
729
728
|
String.fromCharCode(parseInt("feff", 16)),
|
|
730
729
|
];
|
|
731
|
-
const
|
|
730
|
+
const specialWhiteSpaceRegexp = new RegExp(specialWhiteSpaceSpecialCharacters.join("|"), "g");
|
|
732
731
|
const newLineRegexp = /(\r\n|\r)/g;
|
|
733
732
|
/**
|
|
734
733
|
* Replace all different newlines characters by \n
|
|
@@ -2004,6 +2003,7 @@ const coreTypes = new Set([
|
|
|
2004
2003
|
"CLEAR_FORMATTING",
|
|
2005
2004
|
"SET_BORDER",
|
|
2006
2005
|
"SET_ZONE_BORDERS",
|
|
2006
|
+
"SET_BORDERS_ON_TARGET",
|
|
2007
2007
|
/** CHART */
|
|
2008
2008
|
"CREATE_CHART",
|
|
2009
2009
|
"UPDATE_CHART",
|
|
@@ -7533,8 +7533,12 @@ function tokenize(str, locale = DEFAULT_LOCALE) {
|
|
|
7533
7533
|
str = replaceNewLines(str);
|
|
7534
7534
|
const chars = new TokenizingChars(str);
|
|
7535
7535
|
const result = [];
|
|
7536
|
+
const tokenizeSpace = specialWhiteSpaceRegexp.test(str)
|
|
7537
|
+
? tokenizeSpecialCharacterSpace
|
|
7538
|
+
: tokenizeSimpleSpace;
|
|
7536
7539
|
while (!chars.isOver()) {
|
|
7537
|
-
let token =
|
|
7540
|
+
let token = tokenizeNewLine(chars) ||
|
|
7541
|
+
tokenizeSpace(chars) ||
|
|
7538
7542
|
tokenizeArgsSeparator(chars, locale) ||
|
|
7539
7543
|
tokenizeParenthesis(chars) ||
|
|
7540
7544
|
tokenizeOperator(chars) ||
|
|
@@ -7668,17 +7672,19 @@ function tokenizeSymbol(chars) {
|
|
|
7668
7672
|
}
|
|
7669
7673
|
return null;
|
|
7670
7674
|
}
|
|
7671
|
-
function
|
|
7672
|
-
let
|
|
7673
|
-
while (chars.current ===
|
|
7674
|
-
|
|
7675
|
-
chars.shift();
|
|
7675
|
+
function tokenizeSpecialCharacterSpace(chars) {
|
|
7676
|
+
let spaces = "";
|
|
7677
|
+
while (chars.current === " " || (chars.current && chars.current.match(specialWhiteSpaceRegexp))) {
|
|
7678
|
+
spaces += chars.shift();
|
|
7676
7679
|
}
|
|
7677
|
-
if (
|
|
7678
|
-
return { type: "SPACE", value:
|
|
7680
|
+
if (spaces) {
|
|
7681
|
+
return { type: "SPACE", value: spaces };
|
|
7679
7682
|
}
|
|
7683
|
+
return null;
|
|
7684
|
+
}
|
|
7685
|
+
function tokenizeSimpleSpace(chars) {
|
|
7680
7686
|
let spaces = "";
|
|
7681
|
-
while (chars.current
|
|
7687
|
+
while (chars.current === " ") {
|
|
7682
7688
|
spaces += chars.shift();
|
|
7683
7689
|
}
|
|
7684
7690
|
if (spaces) {
|
|
@@ -7686,6 +7692,17 @@ function tokenizeSpace(chars) {
|
|
|
7686
7692
|
}
|
|
7687
7693
|
return null;
|
|
7688
7694
|
}
|
|
7695
|
+
function tokenizeNewLine(chars) {
|
|
7696
|
+
let length = 0;
|
|
7697
|
+
while (chars.current === NEWLINE) {
|
|
7698
|
+
length++;
|
|
7699
|
+
chars.shift();
|
|
7700
|
+
}
|
|
7701
|
+
if (length) {
|
|
7702
|
+
return { type: "SPACE", value: NEWLINE.repeat(length) };
|
|
7703
|
+
}
|
|
7704
|
+
return null;
|
|
7705
|
+
}
|
|
7689
7706
|
function tokenizeInvalidRange(chars) {
|
|
7690
7707
|
if (chars.currentStartsWith(INCORRECT_RANGE_STRING)) {
|
|
7691
7708
|
chars.advanceBy(INCORRECT_RANGE_STRING.length);
|
|
@@ -34669,6 +34686,15 @@ class BordersPlugin extends CorePlugin {
|
|
|
34669
34686
|
case "SET_BORDER":
|
|
34670
34687
|
this.setBorder(cmd.sheetId, cmd.col, cmd.row, cmd.border);
|
|
34671
34688
|
break;
|
|
34689
|
+
case "SET_BORDERS_ON_TARGET":
|
|
34690
|
+
for (const zone of cmd.target) {
|
|
34691
|
+
for (let row = zone.top; row <= zone.bottom; row++) {
|
|
34692
|
+
for (let col = zone.left; col <= zone.right; col++) {
|
|
34693
|
+
this.setBorder(cmd.sheetId, col, row, cmd.border);
|
|
34694
|
+
}
|
|
34695
|
+
}
|
|
34696
|
+
}
|
|
34697
|
+
break;
|
|
34672
34698
|
case "SET_ZONE_BORDERS":
|
|
34673
34699
|
if (cmd.border) {
|
|
34674
34700
|
const target = cmd.target.map((zone) => this.getters.expandZone(cmd.sheetId, zone));
|
|
@@ -36675,6 +36701,353 @@ class ChartPlugin extends CorePlugin {
|
|
|
36675
36701
|
}
|
|
36676
36702
|
}
|
|
36677
36703
|
|
|
36704
|
+
/**
|
|
36705
|
+
* ####################################################
|
|
36706
|
+
* # INTRODUCTION
|
|
36707
|
+
* ####################################################
|
|
36708
|
+
*
|
|
36709
|
+
* This file contain the function recomputeZones.
|
|
36710
|
+
* This function try to recompute in a performant way
|
|
36711
|
+
* an ensemble of zones possibly overlapping to avoid
|
|
36712
|
+
* overlapping and to reduce the number of zones.
|
|
36713
|
+
*
|
|
36714
|
+
* It also allows to remove some zones from the ensemble.
|
|
36715
|
+
*
|
|
36716
|
+
* In the following example, 2 zones are overlapping.
|
|
36717
|
+
* Applying recomputeZones will return zones without
|
|
36718
|
+
* overlapping:
|
|
36719
|
+
*
|
|
36720
|
+
* ["B3:D4", "D2:E3"] ["B3:C4", "D2:D4", "E2:E3"]
|
|
36721
|
+
*
|
|
36722
|
+
* A B C D E A B C D E
|
|
36723
|
+
* 1 ___ 1 ___
|
|
36724
|
+
* 2 ___|_ | 2 ___| | |
|
|
36725
|
+
* 3 | |_|_| ---> 3 | | |_|
|
|
36726
|
+
* 4 |_____| 4 |___|_|
|
|
36727
|
+
* 6 6
|
|
36728
|
+
* 7 7
|
|
36729
|
+
*
|
|
36730
|
+
*
|
|
36731
|
+
* In the following example, 2 zones are contiguous.
|
|
36732
|
+
* Applying recomputeZones will return only one zone:
|
|
36733
|
+
*
|
|
36734
|
+
* ["B2:B3", "C2:D3"] ["B2:D3"]
|
|
36735
|
+
*
|
|
36736
|
+
* A B C D E A B C D E
|
|
36737
|
+
* 1 _ ___ 1 _____
|
|
36738
|
+
* 2 | | | ---> 2 | |
|
|
36739
|
+
* 3 |_|___| 3 |_____|
|
|
36740
|
+
* 4 4
|
|
36741
|
+
*
|
|
36742
|
+
*
|
|
36743
|
+
* In the following example, we want to remove a zone
|
|
36744
|
+
* from the ensemble. Applying recomputeZones will
|
|
36745
|
+
* return the ensemble without the zone to remove:
|
|
36746
|
+
*
|
|
36747
|
+
* remove ["C3:D3"] ["B2:B4", "C2:D2",
|
|
36748
|
+
* "C4:D4", "E2:E4"]
|
|
36749
|
+
*
|
|
36750
|
+
* A B C D E F A B C D E F
|
|
36751
|
+
* 1 _______ 1 _______
|
|
36752
|
+
* 2 | | ---> 2 | |___| |
|
|
36753
|
+
* 3 | xxx | 3 | |___| |
|
|
36754
|
+
* 4 |_______| 4 |_|___|_|
|
|
36755
|
+
* 5 5
|
|
36756
|
+
*
|
|
36757
|
+
*
|
|
36758
|
+
* The exercise seems simple when we have only 2 zones.
|
|
36759
|
+
* But with n zones and in a performant way, we want to
|
|
36760
|
+
* avoid comparing each zone with all the others.
|
|
36761
|
+
*
|
|
36762
|
+
*
|
|
36763
|
+
* ####################################################
|
|
36764
|
+
* # Methodological approach
|
|
36765
|
+
* ####################################################
|
|
36766
|
+
*
|
|
36767
|
+
* The methodological approach to avoid comparing each
|
|
36768
|
+
* zone with all the others is to use a data structure
|
|
36769
|
+
* that allow to quickly find which zones are
|
|
36770
|
+
* overlapping with any other given zone.
|
|
36771
|
+
*
|
|
36772
|
+
* Here the idea is to profile the zones at the columns level.
|
|
36773
|
+
*
|
|
36774
|
+
* To do that, we propose to use a data structure
|
|
36775
|
+
* composed of 2 parts:
|
|
36776
|
+
* - profilesStartingPosition: a sorted number array
|
|
36777
|
+
* indicating on which columns a new profile begins.
|
|
36778
|
+
* - profiles: a map where the key is a column
|
|
36779
|
+
* position (from profilesStartingPosition) and the
|
|
36780
|
+
* value is a sorted number array representing a
|
|
36781
|
+
* profile.
|
|
36782
|
+
*
|
|
36783
|
+
*
|
|
36784
|
+
* See the following example: here profileStartingPosition
|
|
36785
|
+
* corresponds to [A,C,E,G,K]
|
|
36786
|
+
* A B C D E F G H I J K so with number [0,2,4,6,10]
|
|
36787
|
+
* 1 ' ' ' '
|
|
36788
|
+
* 2 ' ' '_______' here profile correspond
|
|
36789
|
+
* 3 '___' |_______| for A to []
|
|
36790
|
+
* 4 | | for C to [3, 5]
|
|
36791
|
+
* 5 |___| for E to []
|
|
36792
|
+
* 6 for G to [2, 3]
|
|
36793
|
+
* 7 for K to []
|
|
36794
|
+
*
|
|
36795
|
+
*
|
|
36796
|
+
* Now we can easily find which zones are overlapping
|
|
36797
|
+
* with a given zone. Suppose we want to add a new zone
|
|
36798
|
+
* D5:H6 to the ensemble:
|
|
36799
|
+
*
|
|
36800
|
+
* With a binary search of left and right
|
|
36801
|
+
* A B C D E F G H I J K on profilesStartingPosition, we can
|
|
36802
|
+
* 1 ' ' ' ' find the indexes of the profiles on which
|
|
36803
|
+
* 2 ' ' '_______' to apply a modification.
|
|
36804
|
+
* 3 '___' |_______|
|
|
36805
|
+
* 4 | _|_______ Here we will:
|
|
36806
|
+
* 5 |_|_| | - add a new profile in D --> become [3, 6]
|
|
36807
|
+
* 6 |_________| - modify the profile in E --> become [4, 6]
|
|
36808
|
+
* 7 - modify the profile in G --> become [2, 3, 4, 6]
|
|
36809
|
+
* - add a new profile in I --> become [8, 10]
|
|
36810
|
+
*
|
|
36811
|
+
* See below the result:
|
|
36812
|
+
*
|
|
36813
|
+
* Note the particularity of the profile
|
|
36814
|
+
* A B C D E F G H I J K for G: it will correspond to [2, 3, 4, 6]
|
|
36815
|
+
* 1 ' ' ' ' ' '
|
|
36816
|
+
* 2 ' ' ' '___'___' To know how to modify the profile (add a
|
|
36817
|
+
* 3 '_'_' |___|___| zone or remove it) we do a binary
|
|
36818
|
+
* 4 | | |___ ___ search of the top and bottom value on the
|
|
36819
|
+
* 5 |_| | | | profile array. Depending on the result index
|
|
36820
|
+
* 6 |_|___|___| parity (odd or even), because zone boundaries
|
|
36821
|
+
* 7 go by pairs, we know if we are in a zone or
|
|
36822
|
+
* not and how operate.
|
|
36823
|
+
*/
|
|
36824
|
+
/**
|
|
36825
|
+
* Recompute the zone without the cells in toRemoveZones and avoid overlapping.
|
|
36826
|
+
* This compute is particularly useful because after this function:
|
|
36827
|
+
* - you will find coordinate of a cell only once among all the zones
|
|
36828
|
+
* - the number of zones will be reduced to the minimum
|
|
36829
|
+
*/
|
|
36830
|
+
function futureRecomputeZones(zones, zonesToRemove = []) {
|
|
36831
|
+
const profilesStartingPosition = [0];
|
|
36832
|
+
const profiles = new Map([[0, []]]);
|
|
36833
|
+
modifyProfiles(profilesStartingPosition, profiles, zones, false);
|
|
36834
|
+
modifyProfiles(profilesStartingPosition, profiles, zonesToRemove, true);
|
|
36835
|
+
return constructZonesFromProfiles(profilesStartingPosition, profiles);
|
|
36836
|
+
}
|
|
36837
|
+
function modifyProfiles(// export for testing only
|
|
36838
|
+
profilesStartingPosition, profiles, zones, toRemove = false) {
|
|
36839
|
+
for (const zone of zones) {
|
|
36840
|
+
const leftValue = zone.left;
|
|
36841
|
+
const rightValue = zone.right === undefined ? undefined : zone.right + 1;
|
|
36842
|
+
const leftIndex = findIndexAndCreateProfile(profilesStartingPosition, profiles, leftValue, true, 0);
|
|
36843
|
+
const rightIndex = findIndexAndCreateProfile(profilesStartingPosition, profiles, rightValue, false, leftIndex);
|
|
36844
|
+
for (let i = leftIndex; i <= rightIndex; i++) {
|
|
36845
|
+
const profile = profiles.get(profilesStartingPosition[i]);
|
|
36846
|
+
modifyProfile(profile, zone, toRemove);
|
|
36847
|
+
}
|
|
36848
|
+
// maybe this part cost in performance, and maybe it's not necessary (depending on the use case). To be checked
|
|
36849
|
+
removeContiguousProfiles(profilesStartingPosition, profiles, leftIndex, rightIndex);
|
|
36850
|
+
}
|
|
36851
|
+
}
|
|
36852
|
+
function findIndexAndCreateProfile(profilesStartingPosition, profiles, value, searchLeft, startIndex) {
|
|
36853
|
+
if (value === undefined) {
|
|
36854
|
+
// this is only the case when the value correspond to a bottom value that could be undefined
|
|
36855
|
+
return profilesStartingPosition.length - 1;
|
|
36856
|
+
}
|
|
36857
|
+
const predecessorIndex = binaryPredecessorSearch(profilesStartingPosition, value, startIndex);
|
|
36858
|
+
if (value != profilesStartingPosition[predecessorIndex]) {
|
|
36859
|
+
// mean that the value is not ending/starting at the same position as the previous/next profile
|
|
36860
|
+
// --> it's a new profile
|
|
36861
|
+
// --> we need to add it
|
|
36862
|
+
profilesStartingPosition.splice(predecessorIndex + 1, 0, value);
|
|
36863
|
+
// suppose the we want to add the for the left value
|
|
36864
|
+
// following profile following zone: 'C', the predecessor index
|
|
36865
|
+
// for B: [1, 3] "C3:D4" correspond to 'B'.
|
|
36866
|
+
// The next line code will
|
|
36867
|
+
// A B C D A B C D copy the profile of 'B'
|
|
36868
|
+
// 1 '___' 1 '___' to 'C'. In the rest of the
|
|
36869
|
+
// 2 | | ---> 2 | _|_ process the 'modifyProfile'
|
|
36870
|
+
// 3 |___| 3 |_|_| | function will adapt the waiting
|
|
36871
|
+
// 4 4 |___| 'C' profile [1, 3] to the
|
|
36872
|
+
// correct 'C' profile [1, 4]
|
|
36873
|
+
profiles.set(value, [...profiles.get(profilesStartingPosition[predecessorIndex])]);
|
|
36874
|
+
return searchLeft ? predecessorIndex + 1 : predecessorIndex;
|
|
36875
|
+
}
|
|
36876
|
+
return searchLeft ? predecessorIndex : predecessorIndex - 1;
|
|
36877
|
+
}
|
|
36878
|
+
/**
|
|
36879
|
+
* Suppose the following Suppose we want to add We want to have the
|
|
36880
|
+
* profile: the following zone: following profile:
|
|
36881
|
+
*
|
|
36882
|
+
* A B C D E F A B C D E F A B C D E F
|
|
36883
|
+
* 1 '___' 1 ' ' 1 '___'
|
|
36884
|
+
* 2 |___| 2 '___' 2 | |
|
|
36885
|
+
* 3 ' ' 3 | | 3 | |
|
|
36886
|
+
* 4 '___' --> 4 | | --> 4 | |
|
|
36887
|
+
* 6 | | 6 |___| 6 | |
|
|
36888
|
+
* 7 |___| 7 7 |___|
|
|
36889
|
+
* 8 8 8
|
|
36890
|
+
*
|
|
36891
|
+
* the profile for 'C' the top zone correspond Here [2, 3, 5, 8] with [3, 7]
|
|
36892
|
+
* corresponds to: to 3 and the bottom zone would be merged into [2, 8]
|
|
36893
|
+
* ____ ____ correspond to 6
|
|
36894
|
+
* [2, 3, 5, 8] would be the profile: The difficulty of modify profile
|
|
36895
|
+
* ____ is to know what must be deleted
|
|
36896
|
+
* Note that the 'filled [3, 7] and what must be added to the
|
|
36897
|
+
* zone' are always between existing profile.
|
|
36898
|
+
* an even index and its
|
|
36899
|
+
* next index
|
|
36900
|
+
*
|
|
36901
|
+
*/
|
|
36902
|
+
function modifyProfile(profile, zone, toRemove = false) {
|
|
36903
|
+
const topValue = zone.top;
|
|
36904
|
+
const bottomValue = zone.bottom === undefined ? undefined : zone.bottom + 1;
|
|
36905
|
+
const newPoints = [];
|
|
36906
|
+
// Case we want to add a zone to the profile:
|
|
36907
|
+
// - If the top predecessor index `topPredIndex` is even, it means the top of the zone is already positioned on a filled zone
|
|
36908
|
+
// so we don't need to add it to the profile. we can keep in reference the index of the predecessor.
|
|
36909
|
+
// - If it is odd, it means the top of the zone must be the beginning of a filled zone.
|
|
36910
|
+
// so we can keep the index of the top position
|
|
36911
|
+
// Case we want to remove a zone from the profile: it's the opposite of the previous case
|
|
36912
|
+
const topPredIndex = binaryPredecessorSearch(profile, topValue, 0, false);
|
|
36913
|
+
if ((topPredIndex % 2 !== 0 && !toRemove) || (topPredIndex % 2 === 0 && toRemove)) {
|
|
36914
|
+
newPoints.push(topValue);
|
|
36915
|
+
}
|
|
36916
|
+
if (bottomValue === undefined) {
|
|
36917
|
+
// The following two code lines will not impact the final result,
|
|
36918
|
+
// but they will impact the intermediate profile.
|
|
36919
|
+
// We keep them for performance reason
|
|
36920
|
+
profile.splice(topPredIndex + 1);
|
|
36921
|
+
profile.push(...newPoints);
|
|
36922
|
+
return;
|
|
36923
|
+
}
|
|
36924
|
+
// Case we want to add a zone to the profile:
|
|
36925
|
+
// - If the bottom successor index `bottomSuccIndex` is even, it means the bottom of the zone must be the ending of a filled zone
|
|
36926
|
+
// so we can keep the index of the bottom position.
|
|
36927
|
+
// - If it is odd, it means the bottom of the zone is already positioned on a filled zone
|
|
36928
|
+
// so we don't need to add it to the profile. we can keep in reference the index of the successor
|
|
36929
|
+
// Case we want to remove a zone from the profile: it's the opposite of the previous case
|
|
36930
|
+
const bottomSuccIndex = binarySuccessorSearch(profile, bottomValue, 0, false);
|
|
36931
|
+
if ((bottomSuccIndex % 2 === 0 && !toRemove) || (bottomSuccIndex % 2 !== 0 && toRemove)) {
|
|
36932
|
+
newPoints.push(bottomValue);
|
|
36933
|
+
}
|
|
36934
|
+
// add the top and bottom value to the profile and
|
|
36935
|
+
// remove all information between the top and bottom index
|
|
36936
|
+
profile.splice(topPredIndex + 1, bottomSuccIndex - topPredIndex - 1, ...newPoints);
|
|
36937
|
+
}
|
|
36938
|
+
function removeContiguousProfiles(profilesStartingPosition, profiles, leftIndex, rightIndex) {
|
|
36939
|
+
const start = leftIndex - 1 === -1 ? 0 : leftIndex - 1;
|
|
36940
|
+
const end = rightIndex === profilesStartingPosition.length - 1 ? rightIndex : rightIndex + 1;
|
|
36941
|
+
for (let i = end; i > start; i--) {
|
|
36942
|
+
if (deepEqualsArray(profiles.get(profilesStartingPosition[i]), profiles.get(profilesStartingPosition[i - 1]))) {
|
|
36943
|
+
profiles.delete(profilesStartingPosition[i]);
|
|
36944
|
+
profilesStartingPosition.splice(i, 1);
|
|
36945
|
+
}
|
|
36946
|
+
}
|
|
36947
|
+
}
|
|
36948
|
+
function constructZonesFromProfiles(profilesStartingPosition, profiles) {
|
|
36949
|
+
const mergedZone = [];
|
|
36950
|
+
let pendingZones = [];
|
|
36951
|
+
for (let colIndex = 0; colIndex < profilesStartingPosition.length; colIndex++) {
|
|
36952
|
+
const left = profilesStartingPosition[colIndex];
|
|
36953
|
+
const profile = profiles.get(left);
|
|
36954
|
+
if (!profile || profile.length === 0) {
|
|
36955
|
+
mergedZone.push(...pendingZones);
|
|
36956
|
+
pendingZones = [];
|
|
36957
|
+
continue;
|
|
36958
|
+
}
|
|
36959
|
+
let right = profilesStartingPosition[colIndex + 1];
|
|
36960
|
+
if (right !== undefined) {
|
|
36961
|
+
right--;
|
|
36962
|
+
}
|
|
36963
|
+
const nextPendingZones = [];
|
|
36964
|
+
for (let i = 0; i < profile.length; i += 2) {
|
|
36965
|
+
const top = profile[i];
|
|
36966
|
+
let bottom = profile[i + 1];
|
|
36967
|
+
if (bottom !== undefined) {
|
|
36968
|
+
bottom--;
|
|
36969
|
+
}
|
|
36970
|
+
const profileZone = {
|
|
36971
|
+
top,
|
|
36972
|
+
left,
|
|
36973
|
+
bottom,
|
|
36974
|
+
right,
|
|
36975
|
+
hasHeader: (bottom === undefined && top !== 0) || (right === undefined && left !== 0),
|
|
36976
|
+
};
|
|
36977
|
+
let findCorrespondingZone = false;
|
|
36978
|
+
for (let j = pendingZones.length - 1; j >= 0; j--) {
|
|
36979
|
+
const pendingZone = pendingZones[j];
|
|
36980
|
+
if (pendingZone.top === profileZone.top && pendingZone.bottom === profileZone.bottom) {
|
|
36981
|
+
pendingZone.right = profileZone.right;
|
|
36982
|
+
pendingZones.splice(j, 1);
|
|
36983
|
+
nextPendingZones.push(pendingZone);
|
|
36984
|
+
findCorrespondingZone = true;
|
|
36985
|
+
break;
|
|
36986
|
+
}
|
|
36987
|
+
}
|
|
36988
|
+
if (!findCorrespondingZone) {
|
|
36989
|
+
nextPendingZones.push(profileZone);
|
|
36990
|
+
}
|
|
36991
|
+
}
|
|
36992
|
+
mergedZone.push(...pendingZones);
|
|
36993
|
+
pendingZones = nextPendingZones;
|
|
36994
|
+
}
|
|
36995
|
+
mergedZone.push(...pendingZones);
|
|
36996
|
+
return mergedZone;
|
|
36997
|
+
}
|
|
36998
|
+
function binaryPredecessorSearch(arr, val, start = 0, matchEqual = true) {
|
|
36999
|
+
let end = arr.length - 1;
|
|
37000
|
+
let result = -1;
|
|
37001
|
+
while (start <= end) {
|
|
37002
|
+
const mid = Math.floor((start + end) / 2);
|
|
37003
|
+
if (arr[mid] === val && matchEqual) {
|
|
37004
|
+
return mid;
|
|
37005
|
+
}
|
|
37006
|
+
else if (arr[mid] < val) {
|
|
37007
|
+
result = mid;
|
|
37008
|
+
start = mid + 1;
|
|
37009
|
+
}
|
|
37010
|
+
else {
|
|
37011
|
+
end = mid - 1;
|
|
37012
|
+
}
|
|
37013
|
+
}
|
|
37014
|
+
return result;
|
|
37015
|
+
}
|
|
37016
|
+
function binarySuccessorSearch(arr, val, start = 0, matchEqual = true) {
|
|
37017
|
+
let end = arr.length - 1;
|
|
37018
|
+
let result = arr.length;
|
|
37019
|
+
while (start <= end) {
|
|
37020
|
+
const mid = Math.floor((start + end) / 2);
|
|
37021
|
+
if (arr[mid] === val && matchEqual) {
|
|
37022
|
+
return mid;
|
|
37023
|
+
}
|
|
37024
|
+
else if (arr[mid] > val) {
|
|
37025
|
+
result = mid;
|
|
37026
|
+
end = mid - 1;
|
|
37027
|
+
}
|
|
37028
|
+
else {
|
|
37029
|
+
start = mid + 1;
|
|
37030
|
+
}
|
|
37031
|
+
}
|
|
37032
|
+
return result;
|
|
37033
|
+
}
|
|
37034
|
+
/**
|
|
37035
|
+
* Compares two arrays.
|
|
37036
|
+
* For performance reasons, this function is to be preferred
|
|
37037
|
+
* to 'deepEquals' in the case we know that the inputs are arrays.
|
|
37038
|
+
*/
|
|
37039
|
+
function deepEqualsArray(arr1, arr2) {
|
|
37040
|
+
if (arr1.length !== arr2.length) {
|
|
37041
|
+
return false;
|
|
37042
|
+
}
|
|
37043
|
+
for (let i = 0; i < arr1.length; i++) {
|
|
37044
|
+
if (!deepEquals(arr1[i], arr2[i])) {
|
|
37045
|
+
return false;
|
|
37046
|
+
}
|
|
37047
|
+
}
|
|
37048
|
+
return true;
|
|
37049
|
+
}
|
|
37050
|
+
|
|
36678
37051
|
// -----------------------------------------------------------------------------
|
|
36679
37052
|
// Constants
|
|
36680
37053
|
// -----------------------------------------------------------------------------
|
|
@@ -36844,10 +37217,12 @@ class ConditionalFormatPlugin extends CorePlugin {
|
|
|
36844
37217
|
const replaceIndex = rules.findIndex((c) => c.id === cf.id);
|
|
36845
37218
|
let currentRanges = [];
|
|
36846
37219
|
if (replaceIndex > -1) {
|
|
36847
|
-
currentRanges = rules[replaceIndex].ranges;
|
|
37220
|
+
currentRanges = rules[replaceIndex].ranges.map(toUnboundedZone);
|
|
36848
37221
|
}
|
|
36849
37222
|
currentRanges = currentRanges.concat(toAdd);
|
|
36850
|
-
|
|
37223
|
+
// Remove the zones first in case the same position is in toAdd and toRemove
|
|
37224
|
+
const withRemovedZones = futureRecomputeZones(currentRanges, toRemove);
|
|
37225
|
+
return futureRecomputeZones([...toAdd, ...withRemovedZones], []);
|
|
36851
37226
|
}
|
|
36852
37227
|
// ---------------------------------------------------------------------------
|
|
36853
37228
|
// Private
|
|
@@ -40582,353 +40957,6 @@ class CompilationParametersBuilder {
|
|
|
40582
40957
|
}
|
|
40583
40958
|
}
|
|
40584
40959
|
|
|
40585
|
-
/**
|
|
40586
|
-
* ####################################################
|
|
40587
|
-
* # INTRODUCTION
|
|
40588
|
-
* ####################################################
|
|
40589
|
-
*
|
|
40590
|
-
* This file contain the function recomputeZones.
|
|
40591
|
-
* This function try to recompute in a performant way
|
|
40592
|
-
* an ensemble of zones possibly overlapping to avoid
|
|
40593
|
-
* overlapping and to reduce the number of zones.
|
|
40594
|
-
*
|
|
40595
|
-
* It also allows to remove some zones from the ensemble.
|
|
40596
|
-
*
|
|
40597
|
-
* In the following example, 2 zones are overlapping.
|
|
40598
|
-
* Applying recomputeZones will return zones without
|
|
40599
|
-
* overlapping:
|
|
40600
|
-
*
|
|
40601
|
-
* ["B3:D4", "D2:E3"] ["B3:C4", "D2:D4", "E2:E3"]
|
|
40602
|
-
*
|
|
40603
|
-
* A B C D E A B C D E
|
|
40604
|
-
* 1 ___ 1 ___
|
|
40605
|
-
* 2 ___|_ | 2 ___| | |
|
|
40606
|
-
* 3 | |_|_| ---> 3 | | |_|
|
|
40607
|
-
* 4 |_____| 4 |___|_|
|
|
40608
|
-
* 6 6
|
|
40609
|
-
* 7 7
|
|
40610
|
-
*
|
|
40611
|
-
*
|
|
40612
|
-
* In the following example, 2 zones are contiguous.
|
|
40613
|
-
* Applying recomputeZones will return only one zone:
|
|
40614
|
-
*
|
|
40615
|
-
* ["B2:B3", "C2:D3"] ["B2:D3"]
|
|
40616
|
-
*
|
|
40617
|
-
* A B C D E A B C D E
|
|
40618
|
-
* 1 _ ___ 1 _____
|
|
40619
|
-
* 2 | | | ---> 2 | |
|
|
40620
|
-
* 3 |_|___| 3 |_____|
|
|
40621
|
-
* 4 4
|
|
40622
|
-
*
|
|
40623
|
-
*
|
|
40624
|
-
* In the following example, we want to remove a zone
|
|
40625
|
-
* from the ensemble. Applying recomputeZones will
|
|
40626
|
-
* return the ensemble without the zone to remove:
|
|
40627
|
-
*
|
|
40628
|
-
* remove ["C3:D3"] ["B2:B4", "C2:D2",
|
|
40629
|
-
* "C4:D4", "E2:E4"]
|
|
40630
|
-
*
|
|
40631
|
-
* A B C D E F A B C D E F
|
|
40632
|
-
* 1 _______ 1 _______
|
|
40633
|
-
* 2 | | ---> 2 | |___| |
|
|
40634
|
-
* 3 | xxx | 3 | |___| |
|
|
40635
|
-
* 4 |_______| 4 |_|___|_|
|
|
40636
|
-
* 5 5
|
|
40637
|
-
*
|
|
40638
|
-
*
|
|
40639
|
-
* The exercise seems simple when we have only 2 zones.
|
|
40640
|
-
* But with n zones and in a performant way, we want to
|
|
40641
|
-
* avoid comparing each zone with all the others.
|
|
40642
|
-
*
|
|
40643
|
-
*
|
|
40644
|
-
* ####################################################
|
|
40645
|
-
* # Methodological approach
|
|
40646
|
-
* ####################################################
|
|
40647
|
-
*
|
|
40648
|
-
* The methodological approach to avoid comparing each
|
|
40649
|
-
* zone with all the others is to use a data structure
|
|
40650
|
-
* that allow to quickly find which zones are
|
|
40651
|
-
* overlapping with any other given zone.
|
|
40652
|
-
*
|
|
40653
|
-
* Here the idea is to profile the zones at the columns level.
|
|
40654
|
-
*
|
|
40655
|
-
* To do that, we propose to use a data structure
|
|
40656
|
-
* composed of 2 parts:
|
|
40657
|
-
* - profilesStartingPosition: a sorted number array
|
|
40658
|
-
* indicating on which columns a new profile begins.
|
|
40659
|
-
* - profiles: a map where the key is a column
|
|
40660
|
-
* position (from profilesStartingPosition) and the
|
|
40661
|
-
* value is a sorted number array representing a
|
|
40662
|
-
* profile.
|
|
40663
|
-
*
|
|
40664
|
-
*
|
|
40665
|
-
* See the following example: here profileStartingPosition
|
|
40666
|
-
* corresponds to [A,C,E,G,K]
|
|
40667
|
-
* A B C D E F G H I J K so with number [0,2,4,6,10]
|
|
40668
|
-
* 1 ' ' ' '
|
|
40669
|
-
* 2 ' ' '_______' here profile correspond
|
|
40670
|
-
* 3 '___' |_______| for A to []
|
|
40671
|
-
* 4 | | for C to [3, 5]
|
|
40672
|
-
* 5 |___| for E to []
|
|
40673
|
-
* 6 for G to [2, 3]
|
|
40674
|
-
* 7 for K to []
|
|
40675
|
-
*
|
|
40676
|
-
*
|
|
40677
|
-
* Now we can easily find which zones are overlapping
|
|
40678
|
-
* with a given zone. Suppose we want to add a new zone
|
|
40679
|
-
* D5:H6 to the ensemble:
|
|
40680
|
-
*
|
|
40681
|
-
* With a binary search of left and right
|
|
40682
|
-
* A B C D E F G H I J K on profilesStartingPosition, we can
|
|
40683
|
-
* 1 ' ' ' ' find the indexes of the profiles on which
|
|
40684
|
-
* 2 ' ' '_______' to apply a modification.
|
|
40685
|
-
* 3 '___' |_______|
|
|
40686
|
-
* 4 | _|_______ Here we will:
|
|
40687
|
-
* 5 |_|_| | - add a new profile in D --> become [3, 6]
|
|
40688
|
-
* 6 |_________| - modify the profile in E --> become [4, 6]
|
|
40689
|
-
* 7 - modify the profile in G --> become [2, 3, 4, 6]
|
|
40690
|
-
* - add a new profile in I --> become [8, 10]
|
|
40691
|
-
*
|
|
40692
|
-
* See below the result:
|
|
40693
|
-
*
|
|
40694
|
-
* Note the particularity of the profile
|
|
40695
|
-
* A B C D E F G H I J K for G: it will correspond to [2, 3, 4, 6]
|
|
40696
|
-
* 1 ' ' ' ' ' '
|
|
40697
|
-
* 2 ' ' ' '___'___' To know how to modify the profile (add a
|
|
40698
|
-
* 3 '_'_' |___|___| zone or remove it) we do a binary
|
|
40699
|
-
* 4 | | |___ ___ search of the top and bottom value on the
|
|
40700
|
-
* 5 |_| | | | profile array. Depending on the result index
|
|
40701
|
-
* 6 |_|___|___| parity (odd or even), because zone boundaries
|
|
40702
|
-
* 7 go by pairs, we know if we are in a zone or
|
|
40703
|
-
* not and how operate.
|
|
40704
|
-
*/
|
|
40705
|
-
/**
|
|
40706
|
-
* Recompute the zone without the cells in toRemoveZones and avoid overlapping.
|
|
40707
|
-
* This compute is particularly useful because after this function:
|
|
40708
|
-
* - you will find coordinate of a cell only once among all the zones
|
|
40709
|
-
* - the number of zones will be reduced to the minimum
|
|
40710
|
-
*/
|
|
40711
|
-
function futureRecomputeZones(zones, zonesToRemove = []) {
|
|
40712
|
-
const profilesStartingPosition = [0];
|
|
40713
|
-
const profiles = new Map([[0, []]]);
|
|
40714
|
-
modifyProfiles(profilesStartingPosition, profiles, zones, false);
|
|
40715
|
-
modifyProfiles(profilesStartingPosition, profiles, zonesToRemove, true);
|
|
40716
|
-
return constructZonesFromProfiles(profilesStartingPosition, profiles);
|
|
40717
|
-
}
|
|
40718
|
-
function modifyProfiles(// export for testing only
|
|
40719
|
-
profilesStartingPosition, profiles, zones, toRemove = false) {
|
|
40720
|
-
for (const zone of zones) {
|
|
40721
|
-
const leftValue = zone.left;
|
|
40722
|
-
const rightValue = zone.right === undefined ? undefined : zone.right + 1;
|
|
40723
|
-
const leftIndex = findIndexAndCreateProfile(profilesStartingPosition, profiles, leftValue, true, 0);
|
|
40724
|
-
const rightIndex = findIndexAndCreateProfile(profilesStartingPosition, profiles, rightValue, false, leftIndex);
|
|
40725
|
-
for (let i = leftIndex; i <= rightIndex; i++) {
|
|
40726
|
-
const profile = profiles.get(profilesStartingPosition[i]);
|
|
40727
|
-
modifyProfile(profile, zone, toRemove);
|
|
40728
|
-
}
|
|
40729
|
-
// maybe this part cost in performance, and maybe it's not necessary (depending on the use case). To be checked
|
|
40730
|
-
removeContiguousProfiles(profilesStartingPosition, profiles, leftIndex, rightIndex);
|
|
40731
|
-
}
|
|
40732
|
-
}
|
|
40733
|
-
function findIndexAndCreateProfile(profilesStartingPosition, profiles, value, searchLeft, startIndex) {
|
|
40734
|
-
if (value === undefined) {
|
|
40735
|
-
// this is only the case when the value correspond to a bottom value that could be undefined
|
|
40736
|
-
return profilesStartingPosition.length - 1;
|
|
40737
|
-
}
|
|
40738
|
-
const predecessorIndex = binaryPredecessorSearch(profilesStartingPosition, value, startIndex);
|
|
40739
|
-
if (value != profilesStartingPosition[predecessorIndex]) {
|
|
40740
|
-
// mean that the value is not ending/starting at the same position as the previous/next profile
|
|
40741
|
-
// --> it's a new profile
|
|
40742
|
-
// --> we need to add it
|
|
40743
|
-
profilesStartingPosition.splice(predecessorIndex + 1, 0, value);
|
|
40744
|
-
// suppose the we want to add the for the left value
|
|
40745
|
-
// following profile following zone: 'C', the predecessor index
|
|
40746
|
-
// for B: [1, 3] "C3:D4" correspond to 'B'.
|
|
40747
|
-
// The next line code will
|
|
40748
|
-
// A B C D A B C D copy the profile of 'B'
|
|
40749
|
-
// 1 '___' 1 '___' to 'C'. In the rest of the
|
|
40750
|
-
// 2 | | ---> 2 | _|_ process the 'modifyProfile'
|
|
40751
|
-
// 3 |___| 3 |_|_| | function will adapt the waiting
|
|
40752
|
-
// 4 4 |___| 'C' profile [1, 3] to the
|
|
40753
|
-
// correct 'C' profile [1, 4]
|
|
40754
|
-
profiles.set(value, [...profiles.get(profilesStartingPosition[predecessorIndex])]);
|
|
40755
|
-
return searchLeft ? predecessorIndex + 1 : predecessorIndex;
|
|
40756
|
-
}
|
|
40757
|
-
return searchLeft ? predecessorIndex : predecessorIndex - 1;
|
|
40758
|
-
}
|
|
40759
|
-
/**
|
|
40760
|
-
* Suppose the following Suppose we want to add We want to have the
|
|
40761
|
-
* profile: the following zone: following profile:
|
|
40762
|
-
*
|
|
40763
|
-
* A B C D E F A B C D E F A B C D E F
|
|
40764
|
-
* 1 '___' 1 ' ' 1 '___'
|
|
40765
|
-
* 2 |___| 2 '___' 2 | |
|
|
40766
|
-
* 3 ' ' 3 | | 3 | |
|
|
40767
|
-
* 4 '___' --> 4 | | --> 4 | |
|
|
40768
|
-
* 6 | | 6 |___| 6 | |
|
|
40769
|
-
* 7 |___| 7 7 |___|
|
|
40770
|
-
* 8 8 8
|
|
40771
|
-
*
|
|
40772
|
-
* the profile for 'C' the top zone correspond Here [2, 3, 5, 8] with [3, 7]
|
|
40773
|
-
* corresponds to: to 3 and the bottom zone would be merged into [2, 8]
|
|
40774
|
-
* ____ ____ correspond to 6
|
|
40775
|
-
* [2, 3, 5, 8] would be the profile: The difficulty of modify profile
|
|
40776
|
-
* ____ is to know what must be deleted
|
|
40777
|
-
* Note that the 'filled [3, 7] and what must be added to the
|
|
40778
|
-
* zone' are always between existing profile.
|
|
40779
|
-
* an even index and its
|
|
40780
|
-
* next index
|
|
40781
|
-
*
|
|
40782
|
-
*/
|
|
40783
|
-
function modifyProfile(profile, zone, toRemove = false) {
|
|
40784
|
-
const topValue = zone.top;
|
|
40785
|
-
const bottomValue = zone.bottom === undefined ? undefined : zone.bottom + 1;
|
|
40786
|
-
const newPoints = [];
|
|
40787
|
-
// Case we want to add a zone to the profile:
|
|
40788
|
-
// - If the top predecessor index `topPredIndex` is even, it means the top of the zone is already positioned on a filled zone
|
|
40789
|
-
// so we don't need to add it to the profile. we can keep in reference the index of the predecessor.
|
|
40790
|
-
// - If it is odd, it means the top of the zone must be the beginning of a filled zone.
|
|
40791
|
-
// so we can keep the index of the top position
|
|
40792
|
-
// Case we want to remove a zone from the profile: it's the opposite of the previous case
|
|
40793
|
-
const topPredIndex = binaryPredecessorSearch(profile, topValue, 0, false);
|
|
40794
|
-
if ((topPredIndex % 2 !== 0 && !toRemove) || (topPredIndex % 2 === 0 && toRemove)) {
|
|
40795
|
-
newPoints.push(topValue);
|
|
40796
|
-
}
|
|
40797
|
-
if (bottomValue === undefined) {
|
|
40798
|
-
// The following two code lines will not impact the final result,
|
|
40799
|
-
// but they will impact the intermediate profile.
|
|
40800
|
-
// We keep them for performance reason
|
|
40801
|
-
profile.splice(topPredIndex + 1);
|
|
40802
|
-
profile.push(...newPoints);
|
|
40803
|
-
return;
|
|
40804
|
-
}
|
|
40805
|
-
// Case we want to add a zone to the profile:
|
|
40806
|
-
// - If the bottom successor index `bottomSuccIndex` is even, it means the bottom of the zone must be the ending of a filled zone
|
|
40807
|
-
// so we can keep the index of the bottom position.
|
|
40808
|
-
// - If it is odd, it means the bottom of the zone is already positioned on a filled zone
|
|
40809
|
-
// so we don't need to add it to the profile. we can keep in reference the index of the successor
|
|
40810
|
-
// Case we want to remove a zone from the profile: it's the opposite of the previous case
|
|
40811
|
-
const bottomSuccIndex = binarySuccessorSearch(profile, bottomValue, 0, false);
|
|
40812
|
-
if ((bottomSuccIndex % 2 === 0 && !toRemove) || (bottomSuccIndex % 2 !== 0 && toRemove)) {
|
|
40813
|
-
newPoints.push(bottomValue);
|
|
40814
|
-
}
|
|
40815
|
-
// add the top and bottom value to the profile and
|
|
40816
|
-
// remove all information between the top and bottom index
|
|
40817
|
-
profile.splice(topPredIndex + 1, bottomSuccIndex - topPredIndex - 1, ...newPoints);
|
|
40818
|
-
}
|
|
40819
|
-
function removeContiguousProfiles(profilesStartingPosition, profiles, leftIndex, rightIndex) {
|
|
40820
|
-
const start = leftIndex - 1 === -1 ? 0 : leftIndex - 1;
|
|
40821
|
-
const end = rightIndex === profilesStartingPosition.length - 1 ? rightIndex : rightIndex + 1;
|
|
40822
|
-
for (let i = end; i > start; i--) {
|
|
40823
|
-
if (deepEqualsArray(profiles.get(profilesStartingPosition[i]), profiles.get(profilesStartingPosition[i - 1]))) {
|
|
40824
|
-
profiles.delete(profilesStartingPosition[i]);
|
|
40825
|
-
profilesStartingPosition.splice(i, 1);
|
|
40826
|
-
}
|
|
40827
|
-
}
|
|
40828
|
-
}
|
|
40829
|
-
function constructZonesFromProfiles(profilesStartingPosition, profiles) {
|
|
40830
|
-
const mergedZone = [];
|
|
40831
|
-
let pendingZones = [];
|
|
40832
|
-
for (let colIndex = 0; colIndex < profilesStartingPosition.length; colIndex++) {
|
|
40833
|
-
const left = profilesStartingPosition[colIndex];
|
|
40834
|
-
const profile = profiles.get(left);
|
|
40835
|
-
if (!profile || profile.length === 0) {
|
|
40836
|
-
mergedZone.push(...pendingZones);
|
|
40837
|
-
pendingZones = [];
|
|
40838
|
-
continue;
|
|
40839
|
-
}
|
|
40840
|
-
let right = profilesStartingPosition[colIndex + 1];
|
|
40841
|
-
if (right !== undefined) {
|
|
40842
|
-
right--;
|
|
40843
|
-
}
|
|
40844
|
-
const nextPendingZones = [];
|
|
40845
|
-
for (let i = 0; i < profile.length; i += 2) {
|
|
40846
|
-
const top = profile[i];
|
|
40847
|
-
let bottom = profile[i + 1];
|
|
40848
|
-
if (bottom !== undefined) {
|
|
40849
|
-
bottom--;
|
|
40850
|
-
}
|
|
40851
|
-
const profileZone = {
|
|
40852
|
-
top,
|
|
40853
|
-
left,
|
|
40854
|
-
bottom,
|
|
40855
|
-
right,
|
|
40856
|
-
hasHeader: (bottom === undefined && top !== 0) || (right === undefined && left !== 0),
|
|
40857
|
-
};
|
|
40858
|
-
let findCorrespondingZone = false;
|
|
40859
|
-
for (let j = pendingZones.length - 1; j >= 0; j--) {
|
|
40860
|
-
const pendingZone = pendingZones[j];
|
|
40861
|
-
if (pendingZone.top === profileZone.top && pendingZone.bottom === profileZone.bottom) {
|
|
40862
|
-
pendingZone.right = profileZone.right;
|
|
40863
|
-
pendingZones.splice(j, 1);
|
|
40864
|
-
nextPendingZones.push(pendingZone);
|
|
40865
|
-
findCorrespondingZone = true;
|
|
40866
|
-
break;
|
|
40867
|
-
}
|
|
40868
|
-
}
|
|
40869
|
-
if (!findCorrespondingZone) {
|
|
40870
|
-
nextPendingZones.push(profileZone);
|
|
40871
|
-
}
|
|
40872
|
-
}
|
|
40873
|
-
mergedZone.push(...pendingZones);
|
|
40874
|
-
pendingZones = nextPendingZones;
|
|
40875
|
-
}
|
|
40876
|
-
mergedZone.push(...pendingZones);
|
|
40877
|
-
return mergedZone;
|
|
40878
|
-
}
|
|
40879
|
-
function binaryPredecessorSearch(arr, val, start = 0, matchEqual = true) {
|
|
40880
|
-
let end = arr.length - 1;
|
|
40881
|
-
let result = -1;
|
|
40882
|
-
while (start <= end) {
|
|
40883
|
-
const mid = Math.floor((start + end) / 2);
|
|
40884
|
-
if (arr[mid] === val && matchEqual) {
|
|
40885
|
-
return mid;
|
|
40886
|
-
}
|
|
40887
|
-
else if (arr[mid] < val) {
|
|
40888
|
-
result = mid;
|
|
40889
|
-
start = mid + 1;
|
|
40890
|
-
}
|
|
40891
|
-
else {
|
|
40892
|
-
end = mid - 1;
|
|
40893
|
-
}
|
|
40894
|
-
}
|
|
40895
|
-
return result;
|
|
40896
|
-
}
|
|
40897
|
-
function binarySuccessorSearch(arr, val, start = 0, matchEqual = true) {
|
|
40898
|
-
let end = arr.length - 1;
|
|
40899
|
-
let result = arr.length;
|
|
40900
|
-
while (start <= end) {
|
|
40901
|
-
const mid = Math.floor((start + end) / 2);
|
|
40902
|
-
if (arr[mid] === val && matchEqual) {
|
|
40903
|
-
return mid;
|
|
40904
|
-
}
|
|
40905
|
-
else if (arr[mid] > val) {
|
|
40906
|
-
result = mid;
|
|
40907
|
-
end = mid - 1;
|
|
40908
|
-
}
|
|
40909
|
-
else {
|
|
40910
|
-
start = mid + 1;
|
|
40911
|
-
}
|
|
40912
|
-
}
|
|
40913
|
-
return result;
|
|
40914
|
-
}
|
|
40915
|
-
/**
|
|
40916
|
-
* Compares two arrays.
|
|
40917
|
-
* For performance reasons, this function is to be preferred
|
|
40918
|
-
* to 'deepEquals' in the case we know that the inputs are arrays.
|
|
40919
|
-
*/
|
|
40920
|
-
function deepEqualsArray(arr1, arr2) {
|
|
40921
|
-
if (arr1.length !== arr2.length) {
|
|
40922
|
-
return false;
|
|
40923
|
-
}
|
|
40924
|
-
for (let i = 0; i < arr1.length; i++) {
|
|
40925
|
-
if (!deepEquals(arr1[i], arr2[i])) {
|
|
40926
|
-
return false;
|
|
40927
|
-
}
|
|
40928
|
-
}
|
|
40929
|
-
return true;
|
|
40930
|
-
}
|
|
40931
|
-
|
|
40932
40960
|
function quickselect(arr, k, left, right, compare) {
|
|
40933
40961
|
quickselectStep(arr, k, left || 0, right || (arr.length - 1), compare || defaultCompare);
|
|
40934
40962
|
}
|
|
@@ -43525,25 +43553,6 @@ class AutofillPlugin extends UIPlugin {
|
|
|
43525
43553
|
case "AUTOFILL_AUTO":
|
|
43526
43554
|
this.autofillAuto();
|
|
43527
43555
|
break;
|
|
43528
|
-
case "AUTOFILL_CELL":
|
|
43529
|
-
this.autoFillMerge(cmd.originCol, cmd.originRow, cmd.col, cmd.row);
|
|
43530
|
-
const sheetId = this.getters.getActiveSheetId();
|
|
43531
|
-
this.dispatch("UPDATE_CELL", {
|
|
43532
|
-
sheetId,
|
|
43533
|
-
col: cmd.col,
|
|
43534
|
-
row: cmd.row,
|
|
43535
|
-
style: cmd.style || null,
|
|
43536
|
-
content: cmd.content || "",
|
|
43537
|
-
format: cmd.format || "",
|
|
43538
|
-
});
|
|
43539
|
-
this.dispatch("SET_BORDER", {
|
|
43540
|
-
sheetId,
|
|
43541
|
-
col: cmd.col,
|
|
43542
|
-
row: cmd.row,
|
|
43543
|
-
border: cmd.border,
|
|
43544
|
-
});
|
|
43545
|
-
this.autofillCF(cmd.originCol, cmd.originRow, cmd.col, cmd.row);
|
|
43546
|
-
this.autofillDV(cmd.originCol, cmd.originRow, cmd.col, cmd.row);
|
|
43547
43556
|
}
|
|
43548
43557
|
}
|
|
43549
43558
|
// ---------------------------------------------------------------------------
|
|
@@ -43567,6 +43576,7 @@ class AutofillPlugin extends UIPlugin {
|
|
|
43567
43576
|
}
|
|
43568
43577
|
const source = this.getters.getSelectedZone();
|
|
43569
43578
|
const target = this.autofillZone;
|
|
43579
|
+
const autofillCellsData = [];
|
|
43570
43580
|
switch (this.direction) {
|
|
43571
43581
|
case "down" /* DIRECTION.DOWN */:
|
|
43572
43582
|
for (let col = source.left; col <= source.right; col++) {
|
|
@@ -43576,7 +43586,7 @@ class AutofillPlugin extends UIPlugin {
|
|
|
43576
43586
|
}
|
|
43577
43587
|
const generator = this.createGenerator(xcs);
|
|
43578
43588
|
for (let row = target.top; row <= target.bottom; row++) {
|
|
43579
|
-
this.computeNewCell(generator, col, row
|
|
43589
|
+
autofillCellsData.push(this.computeNewCell(generator, col, row));
|
|
43580
43590
|
}
|
|
43581
43591
|
}
|
|
43582
43592
|
break;
|
|
@@ -43588,7 +43598,7 @@ class AutofillPlugin extends UIPlugin {
|
|
|
43588
43598
|
}
|
|
43589
43599
|
const generator = this.createGenerator(xcs);
|
|
43590
43600
|
for (let row = target.bottom; row >= target.top; row--) {
|
|
43591
|
-
this.computeNewCell(generator, col, row
|
|
43601
|
+
autofillCellsData.push(this.computeNewCell(generator, col, row));
|
|
43592
43602
|
}
|
|
43593
43603
|
}
|
|
43594
43604
|
break;
|
|
@@ -43600,7 +43610,7 @@ class AutofillPlugin extends UIPlugin {
|
|
|
43600
43610
|
}
|
|
43601
43611
|
const generator = this.createGenerator(xcs);
|
|
43602
43612
|
for (let col = target.right; col >= target.left; col--) {
|
|
43603
|
-
this.computeNewCell(generator, col, row
|
|
43613
|
+
autofillCellsData.push(this.computeNewCell(generator, col, row));
|
|
43604
43614
|
}
|
|
43605
43615
|
}
|
|
43606
43616
|
break;
|
|
@@ -43612,12 +43622,26 @@ class AutofillPlugin extends UIPlugin {
|
|
|
43612
43622
|
}
|
|
43613
43623
|
const generator = this.createGenerator(xcs);
|
|
43614
43624
|
for (let col = target.left; col <= target.right; col++) {
|
|
43615
|
-
this.computeNewCell(generator, col, row
|
|
43625
|
+
autofillCellsData.push(this.computeNewCell(generator, col, row));
|
|
43616
43626
|
}
|
|
43617
43627
|
}
|
|
43618
43628
|
break;
|
|
43619
43629
|
}
|
|
43620
43630
|
if (apply) {
|
|
43631
|
+
const bordersZones = {};
|
|
43632
|
+
const cfNewRanges = {};
|
|
43633
|
+
const dvNewZones = {};
|
|
43634
|
+
const sheetId = this.getters.getActiveSheetId();
|
|
43635
|
+
for (const data of autofillCellsData) {
|
|
43636
|
+
this.collectBordersData(data, bordersZones);
|
|
43637
|
+
this.autofillMerge(sheetId, data);
|
|
43638
|
+
this.autofillCell(sheetId, data);
|
|
43639
|
+
this.collectConditionalFormatsData(sheetId, data, cfNewRanges);
|
|
43640
|
+
this.collectDataValidationsData(sheetId, data, dvNewZones);
|
|
43641
|
+
}
|
|
43642
|
+
this.autofillBorders(sheetId, bordersZones);
|
|
43643
|
+
this.autofillConditionalFormats(sheetId, cfNewRanges);
|
|
43644
|
+
this.autofillDataValidations(sheetId, dvNewZones);
|
|
43621
43645
|
this.autofillZone = undefined;
|
|
43622
43646
|
this.selection.resizeAnchorZone(this.direction, this.steps);
|
|
43623
43647
|
this.lastCellSelected = {};
|
|
@@ -43626,6 +43650,95 @@ class AutofillPlugin extends UIPlugin {
|
|
|
43626
43650
|
this.tooltip = undefined;
|
|
43627
43651
|
}
|
|
43628
43652
|
}
|
|
43653
|
+
collectBordersData(data, bordersPositions) {
|
|
43654
|
+
const key = JSON.stringify(data.border);
|
|
43655
|
+
if (!(key in bordersPositions)) {
|
|
43656
|
+
bordersPositions[key] = [];
|
|
43657
|
+
}
|
|
43658
|
+
bordersPositions[key].push(positionToZone({ col: data.col, row: data.row }));
|
|
43659
|
+
}
|
|
43660
|
+
collectConditionalFormatsData(sheetId, data, cfNewRanges) {
|
|
43661
|
+
const { originCol, originRow, col, row } = data;
|
|
43662
|
+
const cfsAtOrigin = this.getters.getRulesByCell(sheetId, originCol, originRow);
|
|
43663
|
+
const xc = toXC(col, row);
|
|
43664
|
+
for (const cf of cfsAtOrigin) {
|
|
43665
|
+
if (!(cf.id in cfNewRanges)) {
|
|
43666
|
+
cfNewRanges[cf.id] = [];
|
|
43667
|
+
}
|
|
43668
|
+
cfNewRanges[cf.id].push(xc);
|
|
43669
|
+
}
|
|
43670
|
+
}
|
|
43671
|
+
collectDataValidationsData(sheetId, data, dvNewZones) {
|
|
43672
|
+
const { originCol, originRow, col, row } = data;
|
|
43673
|
+
const cellPosition = { sheetId, col: originCol, row: originRow };
|
|
43674
|
+
const dvsAtOrigin = this.getters.getValidationRuleForCell(cellPosition);
|
|
43675
|
+
if (!dvsAtOrigin) {
|
|
43676
|
+
return;
|
|
43677
|
+
}
|
|
43678
|
+
if (!(dvsAtOrigin.id in dvNewZones)) {
|
|
43679
|
+
dvNewZones[dvsAtOrigin.id] = [];
|
|
43680
|
+
}
|
|
43681
|
+
dvNewZones[dvsAtOrigin.id].push(positionToZone({ col, row }));
|
|
43682
|
+
}
|
|
43683
|
+
autofillCell(sheetId, data) {
|
|
43684
|
+
this.dispatch("UPDATE_CELL", {
|
|
43685
|
+
sheetId,
|
|
43686
|
+
col: data.col,
|
|
43687
|
+
row: data.row,
|
|
43688
|
+
content: data.content || "",
|
|
43689
|
+
style: data.style || null,
|
|
43690
|
+
format: data.format || "",
|
|
43691
|
+
});
|
|
43692
|
+
// Still usefull in odoo ATM to autofill field sync
|
|
43693
|
+
this.dispatch("AUTOFILL_CELL", data);
|
|
43694
|
+
}
|
|
43695
|
+
autofillBorders(sheetId, bordersPositions) {
|
|
43696
|
+
for (const stringifiedBorder in bordersPositions) {
|
|
43697
|
+
const border = stringifiedBorder === "undefined" ? undefined : JSON.parse(stringifiedBorder);
|
|
43698
|
+
this.dispatch("SET_BORDERS_ON_TARGET", {
|
|
43699
|
+
sheetId,
|
|
43700
|
+
border,
|
|
43701
|
+
target: futureRecomputeZones(bordersPositions[stringifiedBorder]),
|
|
43702
|
+
});
|
|
43703
|
+
}
|
|
43704
|
+
}
|
|
43705
|
+
autofillConditionalFormats(sheetId, cfNewRanges) {
|
|
43706
|
+
for (const cfId in cfNewRanges) {
|
|
43707
|
+
const changes = cfNewRanges[cfId];
|
|
43708
|
+
const cf = this.getters.getConditionalFormats(sheetId).find((cf) => cf.id === cfId);
|
|
43709
|
+
if (!cf) {
|
|
43710
|
+
continue;
|
|
43711
|
+
}
|
|
43712
|
+
const newCfZones = this.getters.getAdaptedCfRanges(sheetId, cf, changes.map(toZone), []);
|
|
43713
|
+
if (newCfZones) {
|
|
43714
|
+
this.dispatch("ADD_CONDITIONAL_FORMAT", {
|
|
43715
|
+
cf: {
|
|
43716
|
+
id: cf.id,
|
|
43717
|
+
rule: cf.rule,
|
|
43718
|
+
stopIfTrue: cf.stopIfTrue,
|
|
43719
|
+
},
|
|
43720
|
+
ranges: newCfZones.map((xc) => this.getters.getRangeDataFromZone(sheetId, xc)),
|
|
43721
|
+
sheetId,
|
|
43722
|
+
});
|
|
43723
|
+
}
|
|
43724
|
+
}
|
|
43725
|
+
}
|
|
43726
|
+
autofillDataValidations(sheetId, dvNewZones) {
|
|
43727
|
+
for (const dvId in dvNewZones) {
|
|
43728
|
+
const changes = dvNewZones[dvId];
|
|
43729
|
+
const dvOrigin = this.getters.getDataValidationRule(sheetId, dvId);
|
|
43730
|
+
if (!dvOrigin) {
|
|
43731
|
+
continue;
|
|
43732
|
+
}
|
|
43733
|
+
const dvRangesXcs = dvOrigin.ranges.map((range) => range.zone);
|
|
43734
|
+
const newDvRanges = futureRecomputeZones(dvRangesXcs.concat(changes), []);
|
|
43735
|
+
this.dispatch("ADD_DATA_VALIDATION_RULE", {
|
|
43736
|
+
rule: dvOrigin,
|
|
43737
|
+
ranges: newDvRanges.map((zone) => this.getters.getRangeDataFromZone(sheetId, zone)),
|
|
43738
|
+
sheetId,
|
|
43739
|
+
});
|
|
43740
|
+
}
|
|
43741
|
+
}
|
|
43629
43742
|
/**
|
|
43630
43743
|
* Select a cell which becomes the last cell of the autofillZone
|
|
43631
43744
|
*/
|
|
@@ -43692,22 +43805,20 @@ class AutofillPlugin extends UIPlugin {
|
|
|
43692
43805
|
/**
|
|
43693
43806
|
* Generate the next cell
|
|
43694
43807
|
*/
|
|
43695
|
-
computeNewCell(generator, col, row
|
|
43808
|
+
computeNewCell(generator, col, row) {
|
|
43696
43809
|
const { cellData, tooltip, origin } = generator.next();
|
|
43697
43810
|
const { content, style, border, format } = cellData;
|
|
43698
43811
|
this.tooltip = tooltip;
|
|
43699
|
-
|
|
43700
|
-
|
|
43701
|
-
|
|
43702
|
-
|
|
43703
|
-
|
|
43704
|
-
|
|
43705
|
-
|
|
43706
|
-
|
|
43707
|
-
|
|
43708
|
-
|
|
43709
|
-
});
|
|
43710
|
-
}
|
|
43812
|
+
return {
|
|
43813
|
+
originCol: origin.col,
|
|
43814
|
+
originRow: origin.row,
|
|
43815
|
+
col,
|
|
43816
|
+
row,
|
|
43817
|
+
content,
|
|
43818
|
+
style,
|
|
43819
|
+
border,
|
|
43820
|
+
format,
|
|
43821
|
+
};
|
|
43711
43822
|
}
|
|
43712
43823
|
/**
|
|
43713
43824
|
* Get the rule associated to the current cell
|
|
@@ -43775,8 +43886,8 @@ class AutofillPlugin extends UIPlugin {
|
|
|
43775
43886
|
? position[first].value
|
|
43776
43887
|
: position[second].value;
|
|
43777
43888
|
}
|
|
43778
|
-
|
|
43779
|
-
const
|
|
43889
|
+
autofillMerge(sheetId, data) {
|
|
43890
|
+
const { originCol, originRow, col, row } = data;
|
|
43780
43891
|
const position = { sheetId, col, row };
|
|
43781
43892
|
const originPosition = { sheetId, col: originCol, row: originRow };
|
|
43782
43893
|
if (this.getters.isInMerge(position) && !this.getters.isInMerge(originPosition)) {
|
|
@@ -43803,35 +43914,6 @@ class AutofillPlugin extends UIPlugin {
|
|
|
43803
43914
|
});
|
|
43804
43915
|
}
|
|
43805
43916
|
}
|
|
43806
|
-
autofillCF(originCol, originRow, col, row) {
|
|
43807
|
-
const sheetId = this.getters.getActiveSheetId();
|
|
43808
|
-
const cfOrigin = this.getters.getRulesByCell(sheetId, originCol, originRow);
|
|
43809
|
-
for (const cf of cfOrigin) {
|
|
43810
|
-
const newCfRanges = this.getters.getAdaptedCfRanges(sheetId, cf, [toXC(col, row)], []);
|
|
43811
|
-
if (newCfRanges) {
|
|
43812
|
-
this.dispatch("ADD_CONDITIONAL_FORMAT", {
|
|
43813
|
-
cf: deepCopy(cf),
|
|
43814
|
-
ranges: newCfRanges.map((xc) => this.getters.getRangeDataFromXc(sheetId, xc)),
|
|
43815
|
-
sheetId,
|
|
43816
|
-
});
|
|
43817
|
-
}
|
|
43818
|
-
}
|
|
43819
|
-
}
|
|
43820
|
-
autofillDV(originCol, originRow, col, row) {
|
|
43821
|
-
const sheetId = this.getters.getActiveSheetId();
|
|
43822
|
-
const cellPosition = { sheetId, col: originCol, row: originRow };
|
|
43823
|
-
const dvOrigin = this.getters.getValidationRuleForCell(cellPosition);
|
|
43824
|
-
if (!dvOrigin) {
|
|
43825
|
-
return;
|
|
43826
|
-
}
|
|
43827
|
-
const dvRangesXcs = dvOrigin.ranges.map((range) => this.getters.getRangeString(range, sheetId));
|
|
43828
|
-
const newDvRanges = recomputeZones(dvRangesXcs.concat(toXC(col, row)), []);
|
|
43829
|
-
this.dispatch("ADD_DATA_VALIDATION_RULE", {
|
|
43830
|
-
rule: dvOrigin,
|
|
43831
|
-
ranges: newDvRanges.map((xc) => this.getters.getRangeDataFromXc(sheetId, xc)),
|
|
43832
|
-
sheetId,
|
|
43833
|
-
});
|
|
43834
|
-
}
|
|
43835
43917
|
// ---------------------------------------------------------------------------
|
|
43836
43918
|
// Grid rendering
|
|
43837
43919
|
// ---------------------------------------------------------------------------
|
|
@@ -45218,6 +45300,9 @@ class ClipboardCellsState extends ClipboardCellsAbstractState {
|
|
|
45218
45300
|
copiedTables;
|
|
45219
45301
|
zones;
|
|
45220
45302
|
uuidGenerator = new UuidGenerator();
|
|
45303
|
+
queuedBordersToAdd = {};
|
|
45304
|
+
queuedCFChanges = {};
|
|
45305
|
+
queuedDVChanges = {};
|
|
45221
45306
|
constructor(zones, operation, getters, dispatch, selection) {
|
|
45222
45307
|
super(operation, getters, dispatch, selection);
|
|
45223
45308
|
if (!zones.length) {
|
|
@@ -45327,6 +45412,8 @@ class ClipboardCellsState extends ClipboardCellsAbstractState {
|
|
|
45327
45412
|
* Paste the clipboard content in the given target
|
|
45328
45413
|
*/
|
|
45329
45414
|
paste(target, options) {
|
|
45415
|
+
this.queuedCFChanges = {};
|
|
45416
|
+
this.queuedDVChanges = {};
|
|
45330
45417
|
if (this.operation === "COPY") {
|
|
45331
45418
|
this.pasteFromCopy(target, options);
|
|
45332
45419
|
}
|
|
@@ -45339,6 +45426,7 @@ class ClipboardCellsState extends ClipboardCellsAbstractState {
|
|
|
45339
45426
|
if (options?.selectTarget) {
|
|
45340
45427
|
this.selectPastedZone(width, height, isCutOperation, target);
|
|
45341
45428
|
}
|
|
45429
|
+
this.executeQueuedChanges(this.getters.getActiveSheetId());
|
|
45342
45430
|
}
|
|
45343
45431
|
pasteFromCopy(target, options) {
|
|
45344
45432
|
if (target.length === 1) {
|
|
@@ -45513,7 +45601,11 @@ class ClipboardCellsState extends ClipboardCellsAbstractState {
|
|
|
45513
45601
|
left: targetBorders?.left || originBorders?.left,
|
|
45514
45602
|
right: targetBorders?.right || originBorders?.right,
|
|
45515
45603
|
};
|
|
45516
|
-
|
|
45604
|
+
const borderKey = JSON.stringify(border);
|
|
45605
|
+
if (!this.queuedBordersToAdd[borderKey]) {
|
|
45606
|
+
this.queuedBordersToAdd[borderKey] = [];
|
|
45607
|
+
}
|
|
45608
|
+
this.queuedBordersToAdd[borderKey].push(positionToZone(target));
|
|
45517
45609
|
if (clipboardOption?.pasteOption === "onlyFormat") {
|
|
45518
45610
|
this.dispatch("UPDATE_CELL", {
|
|
45519
45611
|
...target,
|
|
@@ -45659,7 +45751,8 @@ class ClipboardCellsState extends ClipboardCellsAbstractState {
|
|
|
45659
45751
|
}
|
|
45660
45752
|
}
|
|
45661
45753
|
pasteCf(origin, target) {
|
|
45662
|
-
const
|
|
45754
|
+
const zone = positionToZone(target);
|
|
45755
|
+
const originZone = positionToZone(origin);
|
|
45663
45756
|
for (let rule of this.getters.getConditionalFormats(origin.sheetId)) {
|
|
45664
45757
|
for (let range of rule.ranges) {
|
|
45665
45758
|
if (isInside(origin.col, origin.row, this.getters.getRangeFromSheetXC(origin.sheetId, range).zone)) {
|
|
@@ -45667,15 +45760,15 @@ class ClipboardCellsState extends ClipboardCellsAbstractState {
|
|
|
45667
45760
|
const toRemoveRange = [];
|
|
45668
45761
|
if (this.operation === "CUT") {
|
|
45669
45762
|
//remove from current rule
|
|
45670
|
-
toRemoveRange.push(
|
|
45763
|
+
toRemoveRange.push(originZone);
|
|
45671
45764
|
}
|
|
45672
45765
|
if (origin.sheetId === target.sheetId) {
|
|
45673
|
-
this.adaptCFRules(origin.sheetId, cf, [
|
|
45766
|
+
this.adaptCFRules(origin.sheetId, cf, [zone], toRemoveRange);
|
|
45674
45767
|
}
|
|
45675
45768
|
else {
|
|
45676
45769
|
this.adaptCFRules(origin.sheetId, cf, [], toRemoveRange);
|
|
45677
45770
|
const cfToCopyTo = this.getCFToCopyTo(target.sheetId, cf);
|
|
45678
|
-
this.adaptCFRules(target.sheetId, cfToCopyTo, [
|
|
45771
|
+
this.adaptCFRules(target.sheetId, cfToCopyTo, [zone], []);
|
|
45679
45772
|
}
|
|
45680
45773
|
}
|
|
45681
45774
|
}
|
|
@@ -45685,79 +45778,130 @@ class ClipboardCellsState extends ClipboardCellsAbstractState {
|
|
|
45685
45778
|
* Add or remove cells to a given conditional formatting rule.
|
|
45686
45779
|
*/
|
|
45687
45780
|
adaptCFRules(sheetId, cf, toAdd, toRemove) {
|
|
45688
|
-
|
|
45689
|
-
|
|
45690
|
-
return;
|
|
45781
|
+
if (!this.queuedCFChanges[sheetId]) {
|
|
45782
|
+
this.queuedCFChanges[sheetId] = [];
|
|
45691
45783
|
}
|
|
45692
|
-
|
|
45693
|
-
|
|
45694
|
-
|
|
45784
|
+
const queuedChange = this.queuedCFChanges[sheetId].find((queued) => queued.cf.id === cf.id);
|
|
45785
|
+
if (!queuedChange) {
|
|
45786
|
+
this.queuedCFChanges[sheetId].push({ toAdd, toRemove, cf });
|
|
45787
|
+
}
|
|
45788
|
+
else {
|
|
45789
|
+
queuedChange.toAdd.push(...toAdd);
|
|
45790
|
+
queuedChange.toRemove.push(...toRemove);
|
|
45695
45791
|
}
|
|
45696
|
-
this.dispatch("ADD_CONDITIONAL_FORMAT", {
|
|
45697
|
-
cf: {
|
|
45698
|
-
id: cf.id,
|
|
45699
|
-
rule: cf.rule,
|
|
45700
|
-
stopIfTrue: cf.stopIfTrue,
|
|
45701
|
-
},
|
|
45702
|
-
ranges: newRangesXC.map((xc) => this.getters.getRangeDataFromXc(sheetId, xc)),
|
|
45703
|
-
sheetId,
|
|
45704
|
-
});
|
|
45705
45792
|
}
|
|
45706
45793
|
getCFToCopyTo(targetSheetId, originCF) {
|
|
45707
|
-
|
|
45794
|
+
let targetCF = this.getters
|
|
45708
45795
|
.getConditionalFormats(targetSheetId)
|
|
45709
45796
|
.find((cf) => cf.stopIfTrue === originCF.stopIfTrue && deepEquals(cf.rule, originCF.rule));
|
|
45710
|
-
|
|
45711
|
-
|
|
45712
|
-
|
|
45797
|
+
const queuedCfs = this.queuedCFChanges[targetSheetId];
|
|
45798
|
+
if (!targetCF && queuedCfs) {
|
|
45799
|
+
targetCF = queuedCfs.find((queued) => queued.cf.stopIfTrue === originCF.stopIfTrue && deepEquals(queued.cf.rule, originCF.rule))?.cf;
|
|
45800
|
+
}
|
|
45801
|
+
return targetCF ? targetCF : { ...originCF, id: this.uuidGenerator.smallUuid(), ranges: [] };
|
|
45713
45802
|
}
|
|
45714
45803
|
pasteDataValidation(origin, target) {
|
|
45715
45804
|
const rule = this.getters.getValidationRuleForCell(origin);
|
|
45805
|
+
const originZone = positionToZone(origin);
|
|
45716
45806
|
if (!rule) {
|
|
45717
45807
|
return;
|
|
45718
45808
|
}
|
|
45719
|
-
const
|
|
45809
|
+
const zone = positionToZone(target);
|
|
45720
45810
|
for (const range of rule.ranges) {
|
|
45721
45811
|
if (isInside(origin.col, origin.row, range.zone)) {
|
|
45722
45812
|
const toRemoveRange = [];
|
|
45723
45813
|
if (this.operation === "CUT") {
|
|
45724
|
-
toRemoveRange.push(
|
|
45814
|
+
toRemoveRange.push(originZone);
|
|
45725
45815
|
}
|
|
45726
45816
|
if (origin.sheetId === target.sheetId) {
|
|
45727
|
-
this.
|
|
45817
|
+
const copyToRule = this.getDataValidationRuleToCopyTo(target.sheetId, rule);
|
|
45818
|
+
this.adaptDataValidationRule(origin.sheetId, copyToRule, [zone], toRemoveRange);
|
|
45728
45819
|
}
|
|
45729
45820
|
else {
|
|
45730
|
-
this.
|
|
45731
|
-
|
|
45732
|
-
|
|
45821
|
+
const originRule = this.getters.getValidationRuleForCell(origin);
|
|
45822
|
+
if (originRule) {
|
|
45823
|
+
this.adaptDataValidationRule(origin.sheetId, originRule, [], toRemoveRange);
|
|
45824
|
+
}
|
|
45825
|
+
const copyToRule = this.getDataValidationRuleToCopyTo(target.sheetId, rule);
|
|
45826
|
+
this.adaptDataValidationRule(target.sheetId, copyToRule, [zone], []);
|
|
45733
45827
|
}
|
|
45734
45828
|
}
|
|
45735
45829
|
}
|
|
45736
45830
|
}
|
|
45737
45831
|
getDataValidationRuleToCopyTo(targetSheetId, originRule) {
|
|
45738
|
-
|
|
45832
|
+
let targetRule = this.getters
|
|
45739
45833
|
.getDataValidationRules(targetSheetId)
|
|
45740
45834
|
.find((rule) => deepEquals(originRule.criterion, rule.criterion) &&
|
|
45741
45835
|
originRule.isBlocking === rule.isBlocking);
|
|
45742
|
-
|
|
45743
|
-
|
|
45744
|
-
|
|
45836
|
+
const queuedRules = this.queuedDVChanges[targetSheetId];
|
|
45837
|
+
if (!targetRule && queuedRules) {
|
|
45838
|
+
targetRule = queuedRules.find((queued) => deepEquals(originRule.criterion, queued.rule.criterion) &&
|
|
45839
|
+
originRule.isBlocking === queued.rule.isBlocking)?.rule;
|
|
45840
|
+
}
|
|
45841
|
+
return targetRule || { ...originRule, id: this.uuidGenerator.smallUuid(), ranges: [] };
|
|
45745
45842
|
}
|
|
45746
45843
|
/**
|
|
45747
45844
|
* Add or remove XCs to a given data validation rule.
|
|
45748
45845
|
*/
|
|
45749
45846
|
adaptDataValidationRule(sheetId, rule, toAdd, toRemove) {
|
|
45750
|
-
|
|
45751
|
-
|
|
45752
|
-
if (newRangesXC.length === 0) {
|
|
45753
|
-
this.dispatch("REMOVE_DATA_VALIDATION_RULE", { sheetId, id: rule.id });
|
|
45754
|
-
return;
|
|
45847
|
+
if (!this.queuedDVChanges[sheetId]) {
|
|
45848
|
+
this.queuedDVChanges[sheetId] = [];
|
|
45755
45849
|
}
|
|
45756
|
-
this.
|
|
45757
|
-
|
|
45758
|
-
|
|
45759
|
-
|
|
45760
|
-
|
|
45850
|
+
const queuedChange = this.queuedDVChanges[sheetId].find((queued) => queued.rule.id === rule.id);
|
|
45851
|
+
if (!queuedChange) {
|
|
45852
|
+
this.queuedDVChanges[sheetId].push({ toAdd, toRemove, rule: deepCopy(rule) });
|
|
45853
|
+
}
|
|
45854
|
+
else {
|
|
45855
|
+
queuedChange.toAdd.push(...toAdd);
|
|
45856
|
+
queuedChange.toRemove.push(...toRemove);
|
|
45857
|
+
}
|
|
45858
|
+
}
|
|
45859
|
+
executeQueuedChanges(pasteSheetTarget) {
|
|
45860
|
+
for (const sheetId in this.queuedCFChanges) {
|
|
45861
|
+
for (const { toAdd, toRemove, cf } of this.queuedCFChanges[sheetId]) {
|
|
45862
|
+
const newRangesXc = this.getters.getAdaptedCfRanges(sheetId, cf, toAdd, toRemove);
|
|
45863
|
+
if (!newRangesXc) {
|
|
45864
|
+
continue;
|
|
45865
|
+
}
|
|
45866
|
+
if (newRangesXc.length === 0) {
|
|
45867
|
+
this.dispatch("REMOVE_CONDITIONAL_FORMAT", { id: cf.id, sheetId });
|
|
45868
|
+
continue;
|
|
45869
|
+
}
|
|
45870
|
+
this.dispatch("ADD_CONDITIONAL_FORMAT", {
|
|
45871
|
+
cf: {
|
|
45872
|
+
id: cf.id,
|
|
45873
|
+
rule: cf.rule,
|
|
45874
|
+
stopIfTrue: cf.stopIfTrue,
|
|
45875
|
+
},
|
|
45876
|
+
ranges: newRangesXc.map((zone) => this.getters.getRangeDataFromZone(sheetId, zone)),
|
|
45877
|
+
sheetId,
|
|
45878
|
+
});
|
|
45879
|
+
}
|
|
45880
|
+
}
|
|
45881
|
+
for (const sheetId in this.queuedDVChanges) {
|
|
45882
|
+
for (const { toAdd, toRemove, rule: dv } of this.queuedDVChanges[sheetId]) {
|
|
45883
|
+
// Remove the zones first in case the same position is in toAdd and toRemove
|
|
45884
|
+
const dvZones = dv.ranges.map((range) => range.zone);
|
|
45885
|
+
const withRemovedZones = futureRecomputeZones(dvZones, toRemove);
|
|
45886
|
+
const newDvZones = futureRecomputeZones([...withRemovedZones, ...toAdd], []);
|
|
45887
|
+
if (newDvZones.length === 0) {
|
|
45888
|
+
this.dispatch("REMOVE_DATA_VALIDATION_RULE", { sheetId, id: dv.id });
|
|
45889
|
+
continue;
|
|
45890
|
+
}
|
|
45891
|
+
this.dispatch("ADD_DATA_VALIDATION_RULE", {
|
|
45892
|
+
rule: { id: dv.id, criterion: dv.criterion, isBlocking: dv.isBlocking },
|
|
45893
|
+
ranges: newDvZones.map((zone) => this.getters.getRangeDataFromZone(sheetId, zone)),
|
|
45894
|
+
sheetId,
|
|
45895
|
+
});
|
|
45896
|
+
}
|
|
45897
|
+
}
|
|
45898
|
+
for (const borderKey in this.queuedBordersToAdd) {
|
|
45899
|
+
const zones = this.queuedBordersToAdd[borderKey];
|
|
45900
|
+
const border = JSON.parse(borderKey);
|
|
45901
|
+
const target = futureRecomputeZones(zones, []);
|
|
45902
|
+
this.dispatch("SET_BORDERS_ON_TARGET", { sheetId: pasteSheetTarget, target, border });
|
|
45903
|
+
}
|
|
45904
|
+
this.queuedBordersToAdd = {};
|
|
45761
45905
|
}
|
|
45762
45906
|
}
|
|
45763
45907
|
|
|
@@ -58105,6 +58249,6 @@ exports.setTranslationMethod = setTranslationMethod;
|
|
|
58105
58249
|
exports.tokenize = tokenize;
|
|
58106
58250
|
|
|
58107
58251
|
|
|
58108
|
-
__info__.version = "17.0.
|
|
58109
|
-
__info__.date = "2025-
|
|
58110
|
-
__info__.hash = "
|
|
58252
|
+
__info__.version = "17.0.57";
|
|
58253
|
+
__info__.date = "2025-04-14T17:13:56.677Z";
|
|
58254
|
+
__info__.hash = "6ac8bb7";
|