@odoo/o-spreadsheet 17.0.5 → 17.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/o-spreadsheet.cjs.js +998 -218
- package/dist/o-spreadsheet.d.ts +47 -18
- package/dist/o-spreadsheet.esm.js +998 -218
- package/dist/o-spreadsheet.iife.js +998 -218
- package/dist/o-spreadsheet.iife.min.js +245 -245
- package/dist/o_spreadsheet.xml +3 -3
- package/package.json +6 -2
|
@@ -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
|
|
7
|
-
* @hash
|
|
5
|
+
* @version 17.0.7
|
|
6
|
+
* @date 2024-01-12T13:42:04.654Z
|
|
7
|
+
* @hash cadb817
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
(function (exports, owl) {
|
|
@@ -330,13 +330,6 @@
|
|
|
330
330
|
//------------------------------------------------------------------------------
|
|
331
331
|
// Miscellaneous
|
|
332
332
|
//------------------------------------------------------------------------------
|
|
333
|
-
/**
|
|
334
|
-
* Stringify an object, like JSON.stringify, except that the first level of keys
|
|
335
|
-
* is ordered.
|
|
336
|
-
*/
|
|
337
|
-
function stringify(obj) {
|
|
338
|
-
return JSON.stringify(obj, Object.keys(obj).sort());
|
|
339
|
-
}
|
|
340
333
|
/**
|
|
341
334
|
* Remove quotes from a quoted string
|
|
342
335
|
* ```js
|
|
@@ -559,7 +552,7 @@
|
|
|
559
552
|
*/
|
|
560
553
|
function getItemId(item, itemsDic) {
|
|
561
554
|
for (let [key, value] of Object.entries(itemsDic)) {
|
|
562
|
-
if (
|
|
555
|
+
if (deepEquals(value, item)) {
|
|
563
556
|
return parseInt(key, 10);
|
|
564
557
|
}
|
|
565
558
|
}
|
|
@@ -1209,16 +1202,84 @@
|
|
|
1209
1202
|
// -----------------------------------------------------------------------------
|
|
1210
1203
|
// Date Type
|
|
1211
1204
|
// -----------------------------------------------------------------------------
|
|
1205
|
+
/**
|
|
1206
|
+
* A DateTime object that can be used to manipulate spreadsheet dates.
|
|
1207
|
+
* Conceptually, a spreadsheet date is simply a number with a date format,
|
|
1208
|
+
* and it is timezone-agnostic.
|
|
1209
|
+
* This DateTime object consistently uses UTC time to represent a naive date and time.
|
|
1210
|
+
*/
|
|
1211
|
+
class DateTime {
|
|
1212
|
+
jsDate;
|
|
1213
|
+
constructor(year, month, day, hours = 0, minutes = 0, seconds = 0) {
|
|
1214
|
+
this.jsDate = new Date(Date.UTC(year, month, day, hours, minutes, seconds, 0));
|
|
1215
|
+
}
|
|
1216
|
+
static fromTimestamp(timestamp) {
|
|
1217
|
+
const date = new Date(timestamp);
|
|
1218
|
+
return new DateTime(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds());
|
|
1219
|
+
}
|
|
1220
|
+
static now() {
|
|
1221
|
+
const now = new Date();
|
|
1222
|
+
return new DateTime(now.getFullYear(), now.getMonth(), now.getDate(), now.getHours(), now.getMinutes(), now.getSeconds());
|
|
1223
|
+
}
|
|
1224
|
+
toString() {
|
|
1225
|
+
return this.jsDate.toString();
|
|
1226
|
+
}
|
|
1227
|
+
toLocaleDateString() {
|
|
1228
|
+
return this.jsDate.toLocaleDateString();
|
|
1229
|
+
}
|
|
1230
|
+
getTime() {
|
|
1231
|
+
return this.jsDate.getTime();
|
|
1232
|
+
}
|
|
1233
|
+
getFullYear() {
|
|
1234
|
+
return this.jsDate.getUTCFullYear();
|
|
1235
|
+
}
|
|
1236
|
+
getMonth() {
|
|
1237
|
+
return this.jsDate.getUTCMonth();
|
|
1238
|
+
}
|
|
1239
|
+
getDate() {
|
|
1240
|
+
return this.jsDate.getUTCDate();
|
|
1241
|
+
}
|
|
1242
|
+
getDay() {
|
|
1243
|
+
return this.jsDate.getUTCDay();
|
|
1244
|
+
}
|
|
1245
|
+
getHours() {
|
|
1246
|
+
return this.jsDate.getUTCHours();
|
|
1247
|
+
}
|
|
1248
|
+
getMinutes() {
|
|
1249
|
+
return this.jsDate.getUTCMinutes();
|
|
1250
|
+
}
|
|
1251
|
+
getSeconds() {
|
|
1252
|
+
return this.jsDate.getUTCSeconds();
|
|
1253
|
+
}
|
|
1254
|
+
setFullYear(year) {
|
|
1255
|
+
return this.jsDate.setUTCFullYear(year);
|
|
1256
|
+
}
|
|
1257
|
+
setMonth(month) {
|
|
1258
|
+
return this.jsDate.setUTCMonth(month);
|
|
1259
|
+
}
|
|
1260
|
+
setDate(date) {
|
|
1261
|
+
return this.jsDate.setUTCDate(date);
|
|
1262
|
+
}
|
|
1263
|
+
setHours(hours) {
|
|
1264
|
+
return this.jsDate.setUTCHours(hours);
|
|
1265
|
+
}
|
|
1266
|
+
setMinutes(minutes) {
|
|
1267
|
+
return this.jsDate.setUTCMinutes(minutes);
|
|
1268
|
+
}
|
|
1269
|
+
setSeconds(seconds) {
|
|
1270
|
+
return this.jsDate.setUTCSeconds(seconds);
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1212
1273
|
// -----------------------------------------------------------------------------
|
|
1213
1274
|
// Parsing
|
|
1214
1275
|
// -----------------------------------------------------------------------------
|
|
1215
|
-
const INITIAL_1900_DAY = new
|
|
1276
|
+
const INITIAL_1900_DAY = new DateTime(1899, 11, 30);
|
|
1216
1277
|
const MS_PER_DAY = 24 * 60 * 60 * 1000;
|
|
1217
1278
|
const CURRENT_MILLENIAL = 2000; // note: don't forget to update this in 2999
|
|
1218
|
-
const CURRENT_YEAR =
|
|
1219
|
-
const CURRENT_MONTH =
|
|
1220
|
-
const INITIAL_JS_DAY =
|
|
1221
|
-
const DATE_JS_1900_OFFSET = INITIAL_JS_DAY - INITIAL_1900_DAY;
|
|
1279
|
+
const CURRENT_YEAR = DateTime.now().getFullYear();
|
|
1280
|
+
const CURRENT_MONTH = DateTime.now().getMonth();
|
|
1281
|
+
const INITIAL_JS_DAY = DateTime.fromTimestamp(0);
|
|
1282
|
+
const DATE_JS_1900_OFFSET = INITIAL_JS_DAY.getTime() - INITIAL_1900_DAY.getTime();
|
|
1222
1283
|
const mdyDateRegexp = /^\d{1,2}(\/|-|\s)\d{1,2}((\/|-|\s)\d{1,4})?$/;
|
|
1223
1284
|
const ymdDateRegexp = /^\d{3,4}(\/|-|\s)\d{1,2}(\/|-|\s)\d{1,2}$/;
|
|
1224
1285
|
const dateSeparatorsRegex = /\/|-|\s/;
|
|
@@ -1279,7 +1340,7 @@
|
|
|
1279
1340
|
return {
|
|
1280
1341
|
value: date.value + time.value,
|
|
1281
1342
|
format: date.format + " " + (time.format === "hhhh:mm:ss" ? "hh:mm:ss" : time.format),
|
|
1282
|
-
jsDate: new
|
|
1343
|
+
jsDate: new DateTime(date.jsDate.getFullYear() + time.jsDate.getFullYear() - 1899, date.jsDate.getMonth() + time.jsDate.getMonth() - 11, date.jsDate.getDate() + time.jsDate.getDate() - 30, date.jsDate.getHours() + time.jsDate.getHours(), date.jsDate.getMinutes() + time.jsDate.getMinutes(), date.jsDate.getSeconds() + time.jsDate.getSeconds()),
|
|
1283
1344
|
};
|
|
1284
1345
|
}
|
|
1285
1346
|
return date || time;
|
|
@@ -1355,12 +1416,12 @@
|
|
|
1355
1416
|
// month + 1: months are 0-indexed in JS
|
|
1356
1417
|
const leadingZero = (monthStr?.length === 2 && month + 1 < 10) || (dayStr?.length === 2 && day < 10);
|
|
1357
1418
|
const fullYear = yearStr?.length !== 2;
|
|
1358
|
-
const jsDate = new
|
|
1419
|
+
const jsDate = new DateTime(year, month, day);
|
|
1359
1420
|
if (jsDate.getMonth() !== month || jsDate.getDate() !== day) {
|
|
1360
1421
|
// invalid date
|
|
1361
1422
|
return null;
|
|
1362
1423
|
}
|
|
1363
|
-
const delta = jsDate - INITIAL_1900_DAY;
|
|
1424
|
+
const delta = jsDate.getTime() - INITIAL_1900_DAY.getTime();
|
|
1364
1425
|
const format = getFormatFromDateParts(parts, separator, leadingZero, fullYear);
|
|
1365
1426
|
return {
|
|
1366
1427
|
value: Math.round(delta / MS_PER_DAY),
|
|
@@ -1451,7 +1512,7 @@
|
|
|
1451
1512
|
if (hours >= 24) {
|
|
1452
1513
|
format = "hhhh:mm:ss";
|
|
1453
1514
|
}
|
|
1454
|
-
const jsDate = new
|
|
1515
|
+
const jsDate = new DateTime(1899, 11, 30, hours, minutes, seconds);
|
|
1455
1516
|
return {
|
|
1456
1517
|
value: hours / 24 + minutes / 1440 + seconds / 86400,
|
|
1457
1518
|
format: format,
|
|
@@ -1465,7 +1526,7 @@
|
|
|
1465
1526
|
// -----------------------------------------------------------------------------
|
|
1466
1527
|
function numberToJsDate(value) {
|
|
1467
1528
|
const truncValue = Math.trunc(value);
|
|
1468
|
-
let date =
|
|
1529
|
+
let date = DateTime.fromTimestamp(truncValue * MS_PER_DAY - DATE_JS_1900_OFFSET);
|
|
1469
1530
|
let time = value - truncValue;
|
|
1470
1531
|
time = time < 0 ? 1 + time : time;
|
|
1471
1532
|
const hours = Math.round(time * 24);
|
|
@@ -1485,7 +1546,7 @@
|
|
|
1485
1546
|
}
|
|
1486
1547
|
/** Return the number of days in the current month of the given date */
|
|
1487
1548
|
function getDaysInMonth(date) {
|
|
1488
|
-
return new
|
|
1549
|
+
return new DateTime(date.getFullYear(), date.getMonth() + 1, 0).getDate();
|
|
1489
1550
|
}
|
|
1490
1551
|
function isLastDayOfMonth(date) {
|
|
1491
1552
|
return getDaysInMonth(date) === date.getDate();
|
|
@@ -1503,7 +1564,7 @@
|
|
|
1503
1564
|
const yStart = date.getFullYear();
|
|
1504
1565
|
const mStart = date.getMonth();
|
|
1505
1566
|
const dStart = date.getDate();
|
|
1506
|
-
const jsDate = new
|
|
1567
|
+
const jsDate = new DateTime(yStart, mStart + months, 1);
|
|
1507
1568
|
if (keepEndOfMonth && dStart === getDaysInMonth(date)) {
|
|
1508
1569
|
jsDate.setDate(getDaysInMonth(jsDate));
|
|
1509
1570
|
}
|
|
@@ -2726,20 +2787,20 @@
|
|
|
2726
2787
|
}
|
|
2727
2788
|
|
|
2728
2789
|
function toCriterionDateNumber(dateValue) {
|
|
2729
|
-
const today =
|
|
2790
|
+
const today = DateTime.now();
|
|
2730
2791
|
switch (dateValue) {
|
|
2731
2792
|
case "today":
|
|
2732
2793
|
return jsDateToNumber(today);
|
|
2733
2794
|
case "yesterday":
|
|
2734
|
-
return jsDateToNumber(
|
|
2795
|
+
return jsDateToNumber(DateTime.fromTimestamp(today.setDate(today.getDate() - 1)));
|
|
2735
2796
|
case "tomorrow":
|
|
2736
|
-
return jsDateToNumber(
|
|
2797
|
+
return jsDateToNumber(DateTime.fromTimestamp(today.setDate(today.getDate() + 1)));
|
|
2737
2798
|
case "lastWeek":
|
|
2738
|
-
return jsDateToNumber(
|
|
2799
|
+
return jsDateToNumber(DateTime.fromTimestamp(today.setDate(today.getDate() - 7)));
|
|
2739
2800
|
case "lastMonth":
|
|
2740
|
-
return jsDateToNumber(
|
|
2801
|
+
return jsDateToNumber(DateTime.fromTimestamp(today.setMonth(today.getMonth() - 1)));
|
|
2741
2802
|
case "lastYear":
|
|
2742
|
-
return jsDateToNumber(
|
|
2803
|
+
return jsDateToNumber(DateTime.fromTimestamp(today.setFullYear(today.getFullYear() - 1)));
|
|
2743
2804
|
}
|
|
2744
2805
|
}
|
|
2745
2806
|
/** Get all the dates values of a criterion converted to numbers, converting date values such as "today" to actual dates */
|
|
@@ -3097,7 +3158,7 @@
|
|
|
3097
3158
|
.map((p) => {
|
|
3098
3159
|
switch (p) {
|
|
3099
3160
|
case "hhhh":
|
|
3100
|
-
const helapsedHours = Math.floor((jsDate.getTime() - INITIAL_1900_DAY) / (60 * 60 * 1000));
|
|
3161
|
+
const helapsedHours = Math.floor((jsDate.getTime() - INITIAL_1900_DAY.getTime()) / (60 * 60 * 1000));
|
|
3101
3162
|
return helapsedHours.toString();
|
|
3102
3163
|
case "hh":
|
|
3103
3164
|
return hours.toString().padStart(2, "0");
|
|
@@ -5287,7 +5348,7 @@
|
|
|
5287
5348
|
};
|
|
5288
5349
|
baselineCell = getters.getEvaluatedCell(baselinePosition);
|
|
5289
5350
|
}
|
|
5290
|
-
const background = getters.
|
|
5351
|
+
const { background, fontColor } = getters.getStyleOfSingleCellChart(chart.background, chart.keyValue);
|
|
5291
5352
|
const locale = getters.getLocale();
|
|
5292
5353
|
return {
|
|
5293
5354
|
title: _t(chart.title),
|
|
@@ -5296,7 +5357,7 @@
|
|
|
5296
5357
|
baselineArrow: getBaselineArrowDirection(baselineCell, keyValueCell, chart.baselineMode),
|
|
5297
5358
|
baselineColor: getBaselineColor(baselineCell, chart.baselineMode, keyValueCell, chart.baselineColorUp, chart.baselineColorDown),
|
|
5298
5359
|
baselineDescr: chart.baselineDescr ? _t(chart.baselineDescr) : "",
|
|
5299
|
-
fontColor
|
|
5360
|
+
fontColor,
|
|
5300
5361
|
background,
|
|
5301
5362
|
baselineStyle: chart.baselineMode !== "percentage" && baseline
|
|
5302
5363
|
? getters.getCellStyle({
|
|
@@ -6591,6 +6652,7 @@
|
|
|
6591
6652
|
},
|
|
6592
6653
|
};
|
|
6593
6654
|
|
|
6655
|
+
const macRegex = /Mac/i;
|
|
6594
6656
|
/**
|
|
6595
6657
|
* Return true if the event was triggered from
|
|
6596
6658
|
* a child element.
|
|
@@ -6630,7 +6692,15 @@
|
|
|
6630
6692
|
return Array.from(document.querySelectorAll(".o-spreadsheet .o-menu"));
|
|
6631
6693
|
}
|
|
6632
6694
|
function isMacOS() {
|
|
6633
|
-
return navigator.userAgent
|
|
6695
|
+
return Boolean(macRegex.test(navigator.userAgent));
|
|
6696
|
+
}
|
|
6697
|
+
/**
|
|
6698
|
+
* @param {KeyboardEvent | MouseEvent} ev
|
|
6699
|
+
* @returns Returns true if the event was triggered with the "ctrl" modifier pressed.
|
|
6700
|
+
* On Mac, this is the "meta" or "command" key.
|
|
6701
|
+
*/
|
|
6702
|
+
function isCtrlKey(ev) {
|
|
6703
|
+
return isMacOS() ? ev.metaKey : ev.ctrlKey;
|
|
6634
6704
|
}
|
|
6635
6705
|
|
|
6636
6706
|
/**
|
|
@@ -8879,7 +8949,7 @@
|
|
|
8879
8949
|
});
|
|
8880
8950
|
return {
|
|
8881
8951
|
chartJsConfig: config,
|
|
8882
|
-
background: getters.
|
|
8952
|
+
background: getters.getStyleOfSingleCellChart(chart.background, dataRange).background,
|
|
8883
8953
|
};
|
|
8884
8954
|
}
|
|
8885
8955
|
|
|
@@ -8960,7 +9030,7 @@
|
|
|
8960
9030
|
else if (format.includes("h") || format.includes("H")) {
|
|
8961
9031
|
return "hour";
|
|
8962
9032
|
}
|
|
8963
|
-
else if (format.includes("
|
|
9033
|
+
else if (format.includes("d")) {
|
|
8964
9034
|
return "day";
|
|
8965
9035
|
}
|
|
8966
9036
|
else if (format.includes("M")) {
|
|
@@ -10908,6 +10978,9 @@
|
|
|
10908
10978
|
function makeArg(str, description) {
|
|
10909
10979
|
let parts = str.match(ARG_REGEXP);
|
|
10910
10980
|
let name = parts[1].trim();
|
|
10981
|
+
if (!name) {
|
|
10982
|
+
throw new Error(`Function argument definition is missing a name: '${str}'.`);
|
|
10983
|
+
}
|
|
10911
10984
|
let types = [];
|
|
10912
10985
|
let isOptional = false;
|
|
10913
10986
|
let isRepeating = false;
|
|
@@ -14613,7 +14686,7 @@
|
|
|
14613
14686
|
if (_year < 1900) {
|
|
14614
14687
|
_year += 1900;
|
|
14615
14688
|
}
|
|
14616
|
-
const jsDate = new
|
|
14689
|
+
const jsDate = new DateTime(_year, _month - 1, _day);
|
|
14617
14690
|
const result = jsDateToRoundNumber(jsDate);
|
|
14618
14691
|
assert(() => result >= 0, _t("The function [[FUNCTION_NAME]] result must be greater than or equal 01/01/1900."));
|
|
14619
14692
|
return result;
|
|
@@ -14656,7 +14729,7 @@
|
|
|
14656
14729
|
// See: https://support.microsoft.com/en-us/office/datedif-function-25dba1a4-2812-480b-84dd-8b32a451b35c
|
|
14657
14730
|
let days = jsEndDate.getDate() - jsStartDate.getDate();
|
|
14658
14731
|
if (days < 0) {
|
|
14659
|
-
const monthBeforeEndMonth = new
|
|
14732
|
+
const monthBeforeEndMonth = new DateTime(jsEndDate.getFullYear(), jsEndDate.getMonth() - 1, 1);
|
|
14660
14733
|
const daysInMonthBeforeEndMonth = getDaysInMonth(monthBeforeEndMonth);
|
|
14661
14734
|
days = daysInMonthBeforeEndMonth - Math.abs(days);
|
|
14662
14735
|
}
|
|
@@ -14665,7 +14738,7 @@
|
|
|
14665
14738
|
if (areTwoDatesWithinOneYear(_startDate, _endDate)) {
|
|
14666
14739
|
return getTimeDifferenceInWholeDays(jsStartDate, jsEndDate);
|
|
14667
14740
|
}
|
|
14668
|
-
const endDateWithinOneYear = new
|
|
14741
|
+
const endDateWithinOneYear = new DateTime(jsStartDate.getFullYear(), jsEndDate.getMonth(), jsEndDate.getDate());
|
|
14669
14742
|
let days = getTimeDifferenceInWholeDays(jsStartDate, endDateWithinOneYear);
|
|
14670
14743
|
if (days < 0) {
|
|
14671
14744
|
endDateWithinOneYear.setFullYear(jsStartDate.getFullYear() + 1);
|
|
@@ -14782,7 +14855,7 @@
|
|
|
14782
14855
|
const _months = Math.trunc(toNumber(months, this.locale));
|
|
14783
14856
|
const yStart = _startDate.getFullYear();
|
|
14784
14857
|
const mStart = _startDate.getMonth();
|
|
14785
|
-
const jsDate = new
|
|
14858
|
+
const jsDate = new DateTime(yStart, mStart + _months + 1, 0);
|
|
14786
14859
|
return jsDateToRoundNumber(jsDate);
|
|
14787
14860
|
},
|
|
14788
14861
|
isExported: true,
|
|
@@ -14819,17 +14892,17 @@
|
|
|
14819
14892
|
// The first week of the year is the week that contains the first
|
|
14820
14893
|
// Thursday of the year.
|
|
14821
14894
|
let firstThursday = 1;
|
|
14822
|
-
while (new
|
|
14895
|
+
while (new DateTime(y, 0, firstThursday).getDay() !== 4) {
|
|
14823
14896
|
firstThursday += 1;
|
|
14824
14897
|
}
|
|
14825
|
-
const firstDayOfFirstWeek = new
|
|
14898
|
+
const firstDayOfFirstWeek = new DateTime(y, 0, firstThursday - 3);
|
|
14826
14899
|
// The last week of the year is the week that contains the last Thursday of
|
|
14827
14900
|
// the year.
|
|
14828
14901
|
let lastThursday = 31;
|
|
14829
|
-
while (new
|
|
14902
|
+
while (new DateTime(y, 11, lastThursday).getDay() !== 4) {
|
|
14830
14903
|
lastThursday -= 1;
|
|
14831
14904
|
}
|
|
14832
|
-
const lastDayOfLastWeek = new
|
|
14905
|
+
const lastDayOfLastWeek = new DateTime(y, 11, lastThursday + 3);
|
|
14833
14906
|
// B - If our date > lastDayOfLastWeek then it's in the weeks of the year after
|
|
14834
14907
|
// If our date < firstDayOfFirstWeek then it's in the weeks of the year before
|
|
14835
14908
|
let offsetYear;
|
|
@@ -14855,17 +14928,17 @@
|
|
|
14855
14928
|
case 1:
|
|
14856
14929
|
// firstDay is the 1st day of the 1st week of the year after
|
|
14857
14930
|
// firstDay = lastDayOfLastWeek + 1 Day
|
|
14858
|
-
firstDay = new
|
|
14931
|
+
firstDay = new DateTime(y, 11, lastThursday + 3 + 1);
|
|
14859
14932
|
break;
|
|
14860
14933
|
case -1:
|
|
14861
14934
|
// firstDay is the 1st day of the 1st week of the previous year.
|
|
14862
14935
|
// The first week of the previous year is the week that contains the
|
|
14863
14936
|
// first Thursday of the previous year.
|
|
14864
14937
|
let firstThursdayPreviousYear = 1;
|
|
14865
|
-
while (new
|
|
14938
|
+
while (new DateTime(y - 1, 0, firstThursdayPreviousYear).getDay() !== 4) {
|
|
14866
14939
|
firstThursdayPreviousYear += 1;
|
|
14867
14940
|
}
|
|
14868
|
-
firstDay = new
|
|
14941
|
+
firstDay = new DateTime(y - 1, 0, firstThursdayPreviousYear - 3);
|
|
14869
14942
|
break;
|
|
14870
14943
|
}
|
|
14871
14944
|
const diff = (_date.getTime() - firstDay.getTime()) / MS_PER_DAY;
|
|
@@ -15000,8 +15073,8 @@
|
|
|
15000
15073
|
});
|
|
15001
15074
|
}
|
|
15002
15075
|
const invertDate = _startDate.getTime() > _endDate.getTime();
|
|
15003
|
-
const stopDate =
|
|
15004
|
-
let stepDate =
|
|
15076
|
+
const stopDate = DateTime.fromTimestamp((invertDate ? _startDate : _endDate).getTime());
|
|
15077
|
+
let stepDate = DateTime.fromTimestamp((invertDate ? _endDate : _startDate).getTime());
|
|
15005
15078
|
const timeStopDate = stopDate.getTime();
|
|
15006
15079
|
let timeStepDate = stepDate.getTime();
|
|
15007
15080
|
let netWorkingDay = 0;
|
|
@@ -15027,8 +15100,7 @@
|
|
|
15027
15100
|
return getDateTimeFormat(this.locale);
|
|
15028
15101
|
},
|
|
15029
15102
|
compute: function () {
|
|
15030
|
-
let today =
|
|
15031
|
-
today.setMilliseconds(0);
|
|
15103
|
+
let today = DateTime.now();
|
|
15032
15104
|
const delta = today.getTime() - INITIAL_1900_DAY.getTime();
|
|
15033
15105
|
const time = today.getHours() / 24 + today.getMinutes() / 1440 + today.getSeconds() / 86400;
|
|
15034
15106
|
return Math.floor(delta / MS_PER_DAY) + time;
|
|
@@ -15102,8 +15174,8 @@
|
|
|
15102
15174
|
return this.locale.dateFormat;
|
|
15103
15175
|
},
|
|
15104
15176
|
compute: function () {
|
|
15105
|
-
const today =
|
|
15106
|
-
const jsDate = new
|
|
15177
|
+
const today = DateTime.now();
|
|
15178
|
+
const jsDate = new DateTime(today.getFullYear(), today.getMonth(), today.getDate());
|
|
15107
15179
|
return jsDateToRoundNumber(jsDate);
|
|
15108
15180
|
},
|
|
15109
15181
|
isExported: true,
|
|
@@ -15158,10 +15230,10 @@
|
|
|
15158
15230
|
}
|
|
15159
15231
|
const y = _date.getFullYear();
|
|
15160
15232
|
let dayStart = 1;
|
|
15161
|
-
let startDayOfFirstWeek = new
|
|
15233
|
+
let startDayOfFirstWeek = new DateTime(y, 0, dayStart);
|
|
15162
15234
|
while (startDayOfFirstWeek.getDay() !== startDayOfWeek) {
|
|
15163
15235
|
dayStart += 1;
|
|
15164
|
-
startDayOfFirstWeek = new
|
|
15236
|
+
startDayOfFirstWeek = new DateTime(y, 0, dayStart);
|
|
15165
15237
|
}
|
|
15166
15238
|
const dif = (_date.getTime() - startDayOfFirstWeek.getTime()) / MS_PER_DAY;
|
|
15167
15239
|
if (dif < 0) {
|
|
@@ -15219,7 +15291,7 @@
|
|
|
15219
15291
|
timesHoliday.add(holiday.getTime());
|
|
15220
15292
|
});
|
|
15221
15293
|
}
|
|
15222
|
-
let stepDate =
|
|
15294
|
+
let stepDate = DateTime.fromTimestamp(_startDate.getTime());
|
|
15223
15295
|
let timeStepDate = stepDate.getTime();
|
|
15224
15296
|
const unitDay = Math.sign(_numDays);
|
|
15225
15297
|
let stepDay = Math.abs(_numDays);
|
|
@@ -15283,7 +15355,7 @@
|
|
|
15283
15355
|
const _startDate = toJsDate(date, this.locale);
|
|
15284
15356
|
const yStart = _startDate.getFullYear();
|
|
15285
15357
|
const mStart = _startDate.getMonth();
|
|
15286
|
-
const jsDate = new
|
|
15358
|
+
const jsDate = new DateTime(yStart, mStart, 1);
|
|
15287
15359
|
return jsDateToRoundNumber(jsDate);
|
|
15288
15360
|
},
|
|
15289
15361
|
};
|
|
@@ -15325,7 +15397,7 @@
|
|
|
15325
15397
|
compute: function (date) {
|
|
15326
15398
|
const quarter = QUARTER.compute.bind(this)(date);
|
|
15327
15399
|
const year = YEAR.compute.bind(this)(date);
|
|
15328
|
-
const jsDate = new
|
|
15400
|
+
const jsDate = new DateTime(year, (quarter - 1) * 3, 1);
|
|
15329
15401
|
return jsDateToRoundNumber(jsDate);
|
|
15330
15402
|
},
|
|
15331
15403
|
};
|
|
@@ -15342,7 +15414,7 @@
|
|
|
15342
15414
|
compute: function (date) {
|
|
15343
15415
|
const quarter = QUARTER.compute.bind(this)(date);
|
|
15344
15416
|
const year = YEAR.compute.bind(this)(date);
|
|
15345
|
-
const jsDate = new
|
|
15417
|
+
const jsDate = new DateTime(year, quarter * 3, 0);
|
|
15346
15418
|
return jsDateToRoundNumber(jsDate);
|
|
15347
15419
|
},
|
|
15348
15420
|
};
|
|
@@ -15358,7 +15430,7 @@
|
|
|
15358
15430
|
},
|
|
15359
15431
|
compute: function (date) {
|
|
15360
15432
|
const year = YEAR.compute.bind(this)(date);
|
|
15361
|
-
const jsDate = new
|
|
15433
|
+
const jsDate = new DateTime(year, 0, 1);
|
|
15362
15434
|
return jsDateToRoundNumber(jsDate);
|
|
15363
15435
|
},
|
|
15364
15436
|
};
|
|
@@ -15374,7 +15446,7 @@
|
|
|
15374
15446
|
},
|
|
15375
15447
|
compute: function (date) {
|
|
15376
15448
|
const year = YEAR.compute.bind(this)(date);
|
|
15377
|
-
const jsDate = new
|
|
15449
|
+
const jsDate = new DateTime(year + 1, 0, 0);
|
|
15378
15450
|
return jsDateToRoundNumber(jsDate);
|
|
15379
15451
|
},
|
|
15380
15452
|
};
|
|
@@ -15422,8 +15494,8 @@
|
|
|
15422
15494
|
const DELTA = {
|
|
15423
15495
|
description: _t("Compare two numeric values, returning 1 if they're equal."),
|
|
15424
15496
|
args: [
|
|
15425
|
-
arg(" (number)", _t("The first number to compare.")),
|
|
15426
|
-
arg(` (number, default=${DEFAULT_DELTA_ARG})`, _t("The second number to compare.")),
|
|
15497
|
+
arg("number1 (number)", _t("The first number to compare.")),
|
|
15498
|
+
arg(`number2 (number, default=${DEFAULT_DELTA_ARG})`, _t("The second number to compare.")),
|
|
15427
15499
|
],
|
|
15428
15500
|
returns: ["NUMBER"],
|
|
15429
15501
|
compute: function (number1, number2 = DEFAULT_DELTA_ARG) {
|
|
@@ -15850,7 +15922,7 @@
|
|
|
15850
15922
|
function assertSettlementLessThanOneYearBeforeMaturity(settlement, maturity, locale) {
|
|
15851
15923
|
const startDate = toJsDate(settlement, locale);
|
|
15852
15924
|
const endDate = toJsDate(maturity, locale);
|
|
15853
|
-
const startDatePlusOneYear =
|
|
15925
|
+
const startDatePlusOneYear = toJsDate(settlement, locale);
|
|
15854
15926
|
startDatePlusOneYear.setFullYear(startDate.getFullYear() + 1);
|
|
15855
15927
|
assert(() => endDate.getTime() <= startDatePlusOneYear.getTime(), _t("The settlement date (%s) must at most one year after the maturity date (%s).", settlement.toString(), maturity.toString()));
|
|
15856
15928
|
}
|
|
@@ -15985,7 +16057,7 @@
|
|
|
15985
16057
|
arg("salvage (number)", _t("The value of the asset at the end of depreciation.")),
|
|
15986
16058
|
arg("period (number)", _t("The single period within life for which to calculate depreciation.")),
|
|
15987
16059
|
arg("rate (number)", _t("The deprecation rate.")),
|
|
15988
|
-
arg(" (number, optional)", _t("An indicator of what day count method to use.")),
|
|
16060
|
+
arg("day_count_convention (number, optional)", _t("An indicator of what day count method to use.")),
|
|
15989
16061
|
],
|
|
15990
16062
|
returns: ["NUMBER"],
|
|
15991
16063
|
compute: function (cost, purchaseDate, firstPeriodEnd, salvage, period, rate, dayCountConvention = DEFAULT_DAY_COUNT_CONVENTION) {
|
|
@@ -19009,8 +19081,8 @@
|
|
|
19009
19081
|
description: _t("A segment of a string."),
|
|
19010
19082
|
args: [
|
|
19011
19083
|
arg("text (string)", _t("The string to extract a segment from.")),
|
|
19012
|
-
arg(" (number)", _t("The index from the left of string from which to begin extracting. The first character in string has the index 1.")),
|
|
19013
|
-
arg(" (number)", _t("The length of the segment to extract.")),
|
|
19084
|
+
arg("starting_at (number)", _t("The index from the left of string from which to begin extracting. The first character in string has the index 1.")),
|
|
19085
|
+
arg("extract_length (number)", _t("The length of the segment to extract.")),
|
|
19014
19086
|
],
|
|
19015
19087
|
returns: ["STRING"],
|
|
19016
19088
|
compute: function (text, starting_at, extract_length) {
|
|
@@ -21213,10 +21285,10 @@
|
|
|
21213
21285
|
function updateSelectionWithArrowKeys(ev, selection) {
|
|
21214
21286
|
const direction = arrowMap[ev.key];
|
|
21215
21287
|
if (ev.shiftKey) {
|
|
21216
|
-
selection.resizeAnchorZone(direction, ev
|
|
21288
|
+
selection.resizeAnchorZone(direction, isCtrlKey(ev) ? "end" : 1);
|
|
21217
21289
|
}
|
|
21218
21290
|
else {
|
|
21219
|
-
selection.moveAnchorCell(direction, ev
|
|
21291
|
+
selection.moveAnchorCell(direction, isCtrlKey(ev) ? "end" : 1);
|
|
21220
21292
|
}
|
|
21221
21293
|
}
|
|
21222
21294
|
|
|
@@ -24204,7 +24276,7 @@
|
|
|
24204
24276
|
return false;
|
|
24205
24277
|
}
|
|
24206
24278
|
if (["lastWeek", "lastMonth", "lastYear"].includes(criterion.dateValue)) {
|
|
24207
|
-
const today = jsDateToRoundNumber(
|
|
24279
|
+
const today = jsDateToRoundNumber(DateTime.now());
|
|
24208
24280
|
return isDateBetween(dateValue, today, criterionValue);
|
|
24209
24281
|
}
|
|
24210
24282
|
return areDatesSameDay(dateValue, criterionValue);
|
|
@@ -28201,7 +28273,10 @@
|
|
|
28201
28273
|
return;
|
|
28202
28274
|
}
|
|
28203
28275
|
const [col, row] = this.getCartesianCoordinates(ev);
|
|
28204
|
-
this.props.onCellClicked(col, row, {
|
|
28276
|
+
this.props.onCellClicked(col, row, {
|
|
28277
|
+
expandZone: ev.shiftKey,
|
|
28278
|
+
addZone: isCtrlKey(ev),
|
|
28279
|
+
});
|
|
28205
28280
|
}
|
|
28206
28281
|
onDoubleClick(ev) {
|
|
28207
28282
|
const [col, row] = this.getCartesianCoordinates(ev);
|
|
@@ -28431,7 +28506,7 @@
|
|
|
28431
28506
|
this._increaseSelection(index);
|
|
28432
28507
|
}
|
|
28433
28508
|
else {
|
|
28434
|
-
this._selectElement(index, ev
|
|
28509
|
+
this._selectElement(index, isCtrlKey(ev));
|
|
28435
28510
|
}
|
|
28436
28511
|
this.lastSelectedElementIndex = index;
|
|
28437
28512
|
const mouseMoveSelect = (col, row) => {
|
|
@@ -28595,8 +28670,8 @@
|
|
|
28595
28670
|
this.env.raiseError(MergeErrorMessage);
|
|
28596
28671
|
}
|
|
28597
28672
|
}
|
|
28598
|
-
_selectElement(index,
|
|
28599
|
-
this.env.model.selection.selectColumn(index,
|
|
28673
|
+
_selectElement(index, addDistinctHeader) {
|
|
28674
|
+
this.env.model.selection.selectColumn(index, addDistinctHeader ? "newAnchor" : "overrideSelection");
|
|
28600
28675
|
}
|
|
28601
28676
|
_increaseSelection(index) {
|
|
28602
28677
|
this.env.model.selection.selectColumn(index, "updateAnchor");
|
|
@@ -28769,8 +28844,8 @@
|
|
|
28769
28844
|
this.env.raiseError(MergeErrorMessage);
|
|
28770
28845
|
}
|
|
28771
28846
|
}
|
|
28772
|
-
_selectElement(index,
|
|
28773
|
-
this.env.model.selection.selectRow(index,
|
|
28847
|
+
_selectElement(index, addDistinctHeader) {
|
|
28848
|
+
this.env.model.selection.selectRow(index, addDistinctHeader ? "newAnchor" : "overrideSelection");
|
|
28774
28849
|
}
|
|
28775
28850
|
_increaseSelection(index) {
|
|
28776
28851
|
this.env.model.selection.selectRow(index, "updateAnchor");
|
|
@@ -29572,17 +29647,17 @@
|
|
|
29572
29647
|
// ---------------------------------------------------------------------------
|
|
29573
29648
|
// Zone selection with mouse
|
|
29574
29649
|
// ---------------------------------------------------------------------------
|
|
29575
|
-
onCellClicked(col, row, {
|
|
29650
|
+
onCellClicked(col, row, { addZone, expandZone }) {
|
|
29576
29651
|
if (this.env.model.getters.hasOpenedPopover()) {
|
|
29577
29652
|
this.closeOpenedPopover();
|
|
29578
29653
|
}
|
|
29579
29654
|
if (this.env.model.getters.getEditionMode() === "editing") {
|
|
29580
29655
|
interactiveStopEdition(this.env);
|
|
29581
29656
|
}
|
|
29582
|
-
if (
|
|
29657
|
+
if (expandZone) {
|
|
29583
29658
|
this.env.model.selection.setAnchorCorner(col, row);
|
|
29584
29659
|
}
|
|
29585
|
-
else if (
|
|
29660
|
+
else if (addZone) {
|
|
29586
29661
|
this.env.model.selection.addCellToSelection(col, row);
|
|
29587
29662
|
}
|
|
29588
29663
|
else {
|
|
@@ -29643,7 +29718,7 @@
|
|
|
29643
29718
|
onKeydown(ev) {
|
|
29644
29719
|
const eventKey = ev.key.toUpperCase();
|
|
29645
29720
|
let keyDownString = "";
|
|
29646
|
-
if (ev
|
|
29721
|
+
if (isCtrlKey(ev) && eventKey !== "CTRL")
|
|
29647
29722
|
keyDownString += "CTRL+";
|
|
29648
29723
|
if (ev.metaKey)
|
|
29649
29724
|
keyDownString += "CTRL+";
|
|
@@ -34534,7 +34609,7 @@
|
|
|
34534
34609
|
*/
|
|
34535
34610
|
function getBorderId(border) {
|
|
34536
34611
|
for (let [key, value] of Object.entries(borders)) {
|
|
34537
|
-
if (
|
|
34612
|
+
if (deepEquals(value, border)) {
|
|
34538
34613
|
return parseInt(key, 10);
|
|
34539
34614
|
}
|
|
34540
34615
|
}
|
|
@@ -39853,56 +39928,758 @@
|
|
|
39853
39928
|
}
|
|
39854
39929
|
}
|
|
39855
39930
|
|
|
39931
|
+
function quickselect(arr, k, left, right, compare) {
|
|
39932
|
+
quickselectStep(arr, k, left || 0, right || (arr.length - 1), compare || defaultCompare);
|
|
39933
|
+
}
|
|
39934
|
+
|
|
39935
|
+
function quickselectStep(arr, k, left, right, compare) {
|
|
39936
|
+
|
|
39937
|
+
while (right > left) {
|
|
39938
|
+
if (right - left > 600) {
|
|
39939
|
+
var n = right - left + 1;
|
|
39940
|
+
var m = k - left + 1;
|
|
39941
|
+
var z = Math.log(n);
|
|
39942
|
+
var s = 0.5 * Math.exp(2 * z / 3);
|
|
39943
|
+
var sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1);
|
|
39944
|
+
var newLeft = Math.max(left, Math.floor(k - m * s / n + sd));
|
|
39945
|
+
var newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd));
|
|
39946
|
+
quickselectStep(arr, k, newLeft, newRight, compare);
|
|
39947
|
+
}
|
|
39948
|
+
|
|
39949
|
+
var t = arr[k];
|
|
39950
|
+
var i = left;
|
|
39951
|
+
var j = right;
|
|
39952
|
+
|
|
39953
|
+
swap(arr, left, k);
|
|
39954
|
+
if (compare(arr[right], t) > 0) swap(arr, left, right);
|
|
39955
|
+
|
|
39956
|
+
while (i < j) {
|
|
39957
|
+
swap(arr, i, j);
|
|
39958
|
+
i++;
|
|
39959
|
+
j--;
|
|
39960
|
+
while (compare(arr[i], t) < 0) i++;
|
|
39961
|
+
while (compare(arr[j], t) > 0) j--;
|
|
39962
|
+
}
|
|
39963
|
+
|
|
39964
|
+
if (compare(arr[left], t) === 0) swap(arr, left, j);
|
|
39965
|
+
else {
|
|
39966
|
+
j++;
|
|
39967
|
+
swap(arr, j, right);
|
|
39968
|
+
}
|
|
39969
|
+
|
|
39970
|
+
if (j <= k) left = j + 1;
|
|
39971
|
+
if (k <= j) right = j - 1;
|
|
39972
|
+
}
|
|
39973
|
+
}
|
|
39974
|
+
|
|
39975
|
+
function swap(arr, i, j) {
|
|
39976
|
+
var tmp = arr[i];
|
|
39977
|
+
arr[i] = arr[j];
|
|
39978
|
+
arr[j] = tmp;
|
|
39979
|
+
}
|
|
39980
|
+
|
|
39981
|
+
function defaultCompare(a, b) {
|
|
39982
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
39983
|
+
}
|
|
39984
|
+
|
|
39985
|
+
class RBush {
|
|
39986
|
+
constructor(maxEntries = 9) {
|
|
39987
|
+
// max entries in a node is 9 by default; min node fill is 40% for best performance
|
|
39988
|
+
this._maxEntries = Math.max(4, maxEntries);
|
|
39989
|
+
this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4));
|
|
39990
|
+
this.clear();
|
|
39991
|
+
}
|
|
39992
|
+
|
|
39993
|
+
all() {
|
|
39994
|
+
return this._all(this.data, []);
|
|
39995
|
+
}
|
|
39996
|
+
|
|
39997
|
+
search(bbox) {
|
|
39998
|
+
let node = this.data;
|
|
39999
|
+
const result = [];
|
|
40000
|
+
|
|
40001
|
+
if (!intersects(bbox, node)) return result;
|
|
40002
|
+
|
|
40003
|
+
const toBBox = this.toBBox;
|
|
40004
|
+
const nodesToSearch = [];
|
|
40005
|
+
|
|
40006
|
+
while (node) {
|
|
40007
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
40008
|
+
const child = node.children[i];
|
|
40009
|
+
const childBBox = node.leaf ? toBBox(child) : child;
|
|
40010
|
+
|
|
40011
|
+
if (intersects(bbox, childBBox)) {
|
|
40012
|
+
if (node.leaf) result.push(child);
|
|
40013
|
+
else if (contains(bbox, childBBox)) this._all(child, result);
|
|
40014
|
+
else nodesToSearch.push(child);
|
|
40015
|
+
}
|
|
40016
|
+
}
|
|
40017
|
+
node = nodesToSearch.pop();
|
|
40018
|
+
}
|
|
40019
|
+
|
|
40020
|
+
return result;
|
|
40021
|
+
}
|
|
40022
|
+
|
|
40023
|
+
collides(bbox) {
|
|
40024
|
+
let node = this.data;
|
|
40025
|
+
|
|
40026
|
+
if (!intersects(bbox, node)) return false;
|
|
40027
|
+
|
|
40028
|
+
const nodesToSearch = [];
|
|
40029
|
+
while (node) {
|
|
40030
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
40031
|
+
const child = node.children[i];
|
|
40032
|
+
const childBBox = node.leaf ? this.toBBox(child) : child;
|
|
40033
|
+
|
|
40034
|
+
if (intersects(bbox, childBBox)) {
|
|
40035
|
+
if (node.leaf || contains(bbox, childBBox)) return true;
|
|
40036
|
+
nodesToSearch.push(child);
|
|
40037
|
+
}
|
|
40038
|
+
}
|
|
40039
|
+
node = nodesToSearch.pop();
|
|
40040
|
+
}
|
|
40041
|
+
|
|
40042
|
+
return false;
|
|
40043
|
+
}
|
|
40044
|
+
|
|
40045
|
+
load(data) {
|
|
40046
|
+
if (!(data && data.length)) return this;
|
|
40047
|
+
|
|
40048
|
+
if (data.length < this._minEntries) {
|
|
40049
|
+
for (let i = 0; i < data.length; i++) {
|
|
40050
|
+
this.insert(data[i]);
|
|
40051
|
+
}
|
|
40052
|
+
return this;
|
|
40053
|
+
}
|
|
40054
|
+
|
|
40055
|
+
// recursively build the tree with the given data from scratch using OMT algorithm
|
|
40056
|
+
let node = this._build(data.slice(), 0, data.length - 1, 0);
|
|
40057
|
+
|
|
40058
|
+
if (!this.data.children.length) {
|
|
40059
|
+
// save as is if tree is empty
|
|
40060
|
+
this.data = node;
|
|
40061
|
+
|
|
40062
|
+
} else if (this.data.height === node.height) {
|
|
40063
|
+
// split root if trees have the same height
|
|
40064
|
+
this._splitRoot(this.data, node);
|
|
40065
|
+
|
|
40066
|
+
} else {
|
|
40067
|
+
if (this.data.height < node.height) {
|
|
40068
|
+
// swap trees if inserted one is bigger
|
|
40069
|
+
const tmpNode = this.data;
|
|
40070
|
+
this.data = node;
|
|
40071
|
+
node = tmpNode;
|
|
40072
|
+
}
|
|
40073
|
+
|
|
40074
|
+
// insert the small tree into the large tree at appropriate level
|
|
40075
|
+
this._insert(node, this.data.height - node.height - 1, true);
|
|
40076
|
+
}
|
|
40077
|
+
|
|
40078
|
+
return this;
|
|
40079
|
+
}
|
|
40080
|
+
|
|
40081
|
+
insert(item) {
|
|
40082
|
+
if (item) this._insert(item, this.data.height - 1);
|
|
40083
|
+
return this;
|
|
40084
|
+
}
|
|
40085
|
+
|
|
40086
|
+
clear() {
|
|
40087
|
+
this.data = createNode([]);
|
|
40088
|
+
return this;
|
|
40089
|
+
}
|
|
40090
|
+
|
|
40091
|
+
remove(item, equalsFn) {
|
|
40092
|
+
if (!item) return this;
|
|
40093
|
+
|
|
40094
|
+
let node = this.data;
|
|
40095
|
+
const bbox = this.toBBox(item);
|
|
40096
|
+
const path = [];
|
|
40097
|
+
const indexes = [];
|
|
40098
|
+
let i, parent, goingUp;
|
|
40099
|
+
|
|
40100
|
+
// depth-first iterative tree traversal
|
|
40101
|
+
while (node || path.length) {
|
|
40102
|
+
|
|
40103
|
+
if (!node) { // go up
|
|
40104
|
+
node = path.pop();
|
|
40105
|
+
parent = path[path.length - 1];
|
|
40106
|
+
i = indexes.pop();
|
|
40107
|
+
goingUp = true;
|
|
40108
|
+
}
|
|
40109
|
+
|
|
40110
|
+
if (node.leaf) { // check current node
|
|
40111
|
+
const index = findItem(item, node.children, equalsFn);
|
|
40112
|
+
|
|
40113
|
+
if (index !== -1) {
|
|
40114
|
+
// item found, remove the item and condense tree upwards
|
|
40115
|
+
node.children.splice(index, 1);
|
|
40116
|
+
path.push(node);
|
|
40117
|
+
this._condense(path);
|
|
40118
|
+
return this;
|
|
40119
|
+
}
|
|
40120
|
+
}
|
|
40121
|
+
|
|
40122
|
+
if (!goingUp && !node.leaf && contains(node, bbox)) { // go down
|
|
40123
|
+
path.push(node);
|
|
40124
|
+
indexes.push(i);
|
|
40125
|
+
i = 0;
|
|
40126
|
+
parent = node;
|
|
40127
|
+
node = node.children[0];
|
|
40128
|
+
|
|
40129
|
+
} else if (parent) { // go right
|
|
40130
|
+
i++;
|
|
40131
|
+
node = parent.children[i];
|
|
40132
|
+
goingUp = false;
|
|
40133
|
+
|
|
40134
|
+
} else node = null; // nothing found
|
|
40135
|
+
}
|
|
40136
|
+
|
|
40137
|
+
return this;
|
|
40138
|
+
}
|
|
40139
|
+
|
|
40140
|
+
toBBox(item) { return item; }
|
|
40141
|
+
|
|
40142
|
+
compareMinX(a, b) { return a.minX - b.minX; }
|
|
40143
|
+
compareMinY(a, b) { return a.minY - b.minY; }
|
|
40144
|
+
|
|
40145
|
+
toJSON() { return this.data; }
|
|
40146
|
+
|
|
40147
|
+
fromJSON(data) {
|
|
40148
|
+
this.data = data;
|
|
40149
|
+
return this;
|
|
40150
|
+
}
|
|
40151
|
+
|
|
40152
|
+
_all(node, result) {
|
|
40153
|
+
const nodesToSearch = [];
|
|
40154
|
+
while (node) {
|
|
40155
|
+
if (node.leaf) result.push(...node.children);
|
|
40156
|
+
else nodesToSearch.push(...node.children);
|
|
40157
|
+
|
|
40158
|
+
node = nodesToSearch.pop();
|
|
40159
|
+
}
|
|
40160
|
+
return result;
|
|
40161
|
+
}
|
|
40162
|
+
|
|
40163
|
+
_build(items, left, right, height) {
|
|
40164
|
+
|
|
40165
|
+
const N = right - left + 1;
|
|
40166
|
+
let M = this._maxEntries;
|
|
40167
|
+
let node;
|
|
40168
|
+
|
|
40169
|
+
if (N <= M) {
|
|
40170
|
+
// reached leaf level; return leaf
|
|
40171
|
+
node = createNode(items.slice(left, right + 1));
|
|
40172
|
+
calcBBox(node, this.toBBox);
|
|
40173
|
+
return node;
|
|
40174
|
+
}
|
|
40175
|
+
|
|
40176
|
+
if (!height) {
|
|
40177
|
+
// target height of the bulk-loaded tree
|
|
40178
|
+
height = Math.ceil(Math.log(N) / Math.log(M));
|
|
40179
|
+
|
|
40180
|
+
// target number of root entries to maximize storage utilization
|
|
40181
|
+
M = Math.ceil(N / Math.pow(M, height - 1));
|
|
40182
|
+
}
|
|
40183
|
+
|
|
40184
|
+
node = createNode([]);
|
|
40185
|
+
node.leaf = false;
|
|
40186
|
+
node.height = height;
|
|
40187
|
+
|
|
40188
|
+
// split the items into M mostly square tiles
|
|
40189
|
+
|
|
40190
|
+
const N2 = Math.ceil(N / M);
|
|
40191
|
+
const N1 = N2 * Math.ceil(Math.sqrt(M));
|
|
40192
|
+
|
|
40193
|
+
multiSelect(items, left, right, N1, this.compareMinX);
|
|
40194
|
+
|
|
40195
|
+
for (let i = left; i <= right; i += N1) {
|
|
40196
|
+
|
|
40197
|
+
const right2 = Math.min(i + N1 - 1, right);
|
|
40198
|
+
|
|
40199
|
+
multiSelect(items, i, right2, N2, this.compareMinY);
|
|
40200
|
+
|
|
40201
|
+
for (let j = i; j <= right2; j += N2) {
|
|
40202
|
+
|
|
40203
|
+
const right3 = Math.min(j + N2 - 1, right2);
|
|
40204
|
+
|
|
40205
|
+
// pack each entry recursively
|
|
40206
|
+
node.children.push(this._build(items, j, right3, height - 1));
|
|
40207
|
+
}
|
|
40208
|
+
}
|
|
40209
|
+
|
|
40210
|
+
calcBBox(node, this.toBBox);
|
|
40211
|
+
|
|
40212
|
+
return node;
|
|
40213
|
+
}
|
|
40214
|
+
|
|
40215
|
+
_chooseSubtree(bbox, node, level, path) {
|
|
40216
|
+
while (true) {
|
|
40217
|
+
path.push(node);
|
|
40218
|
+
|
|
40219
|
+
if (node.leaf || path.length - 1 === level) break;
|
|
40220
|
+
|
|
40221
|
+
let minArea = Infinity;
|
|
40222
|
+
let minEnlargement = Infinity;
|
|
40223
|
+
let targetNode;
|
|
40224
|
+
|
|
40225
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
40226
|
+
const child = node.children[i];
|
|
40227
|
+
const area = bboxArea(child);
|
|
40228
|
+
const enlargement = enlargedArea(bbox, child) - area;
|
|
40229
|
+
|
|
40230
|
+
// choose entry with the least area enlargement
|
|
40231
|
+
if (enlargement < minEnlargement) {
|
|
40232
|
+
minEnlargement = enlargement;
|
|
40233
|
+
minArea = area < minArea ? area : minArea;
|
|
40234
|
+
targetNode = child;
|
|
40235
|
+
|
|
40236
|
+
} else if (enlargement === minEnlargement) {
|
|
40237
|
+
// otherwise choose one with the smallest area
|
|
40238
|
+
if (area < minArea) {
|
|
40239
|
+
minArea = area;
|
|
40240
|
+
targetNode = child;
|
|
40241
|
+
}
|
|
40242
|
+
}
|
|
40243
|
+
}
|
|
40244
|
+
|
|
40245
|
+
node = targetNode || node.children[0];
|
|
40246
|
+
}
|
|
40247
|
+
|
|
40248
|
+
return node;
|
|
40249
|
+
}
|
|
40250
|
+
|
|
40251
|
+
_insert(item, level, isNode) {
|
|
40252
|
+
const bbox = isNode ? item : this.toBBox(item);
|
|
40253
|
+
const insertPath = [];
|
|
40254
|
+
|
|
40255
|
+
// find the best node for accommodating the item, saving all nodes along the path too
|
|
40256
|
+
const node = this._chooseSubtree(bbox, this.data, level, insertPath);
|
|
40257
|
+
|
|
40258
|
+
// put the item into the node
|
|
40259
|
+
node.children.push(item);
|
|
40260
|
+
extend(node, bbox);
|
|
40261
|
+
|
|
40262
|
+
// split on node overflow; propagate upwards if necessary
|
|
40263
|
+
while (level >= 0) {
|
|
40264
|
+
if (insertPath[level].children.length > this._maxEntries) {
|
|
40265
|
+
this._split(insertPath, level);
|
|
40266
|
+
level--;
|
|
40267
|
+
} else break;
|
|
40268
|
+
}
|
|
40269
|
+
|
|
40270
|
+
// adjust bboxes along the insertion path
|
|
40271
|
+
this._adjustParentBBoxes(bbox, insertPath, level);
|
|
40272
|
+
}
|
|
40273
|
+
|
|
40274
|
+
// split overflowed node into two
|
|
40275
|
+
_split(insertPath, level) {
|
|
40276
|
+
const node = insertPath[level];
|
|
40277
|
+
const M = node.children.length;
|
|
40278
|
+
const m = this._minEntries;
|
|
40279
|
+
|
|
40280
|
+
this._chooseSplitAxis(node, m, M);
|
|
40281
|
+
|
|
40282
|
+
const splitIndex = this._chooseSplitIndex(node, m, M);
|
|
40283
|
+
|
|
40284
|
+
const newNode = createNode(node.children.splice(splitIndex, node.children.length - splitIndex));
|
|
40285
|
+
newNode.height = node.height;
|
|
40286
|
+
newNode.leaf = node.leaf;
|
|
40287
|
+
|
|
40288
|
+
calcBBox(node, this.toBBox);
|
|
40289
|
+
calcBBox(newNode, this.toBBox);
|
|
40290
|
+
|
|
40291
|
+
if (level) insertPath[level - 1].children.push(newNode);
|
|
40292
|
+
else this._splitRoot(node, newNode);
|
|
40293
|
+
}
|
|
40294
|
+
|
|
40295
|
+
_splitRoot(node, newNode) {
|
|
40296
|
+
// split root node
|
|
40297
|
+
this.data = createNode([node, newNode]);
|
|
40298
|
+
this.data.height = node.height + 1;
|
|
40299
|
+
this.data.leaf = false;
|
|
40300
|
+
calcBBox(this.data, this.toBBox);
|
|
40301
|
+
}
|
|
40302
|
+
|
|
40303
|
+
_chooseSplitIndex(node, m, M) {
|
|
40304
|
+
let index;
|
|
40305
|
+
let minOverlap = Infinity;
|
|
40306
|
+
let minArea = Infinity;
|
|
40307
|
+
|
|
40308
|
+
for (let i = m; i <= M - m; i++) {
|
|
40309
|
+
const bbox1 = distBBox(node, 0, i, this.toBBox);
|
|
40310
|
+
const bbox2 = distBBox(node, i, M, this.toBBox);
|
|
40311
|
+
|
|
40312
|
+
const overlap = intersectionArea(bbox1, bbox2);
|
|
40313
|
+
const area = bboxArea(bbox1) + bboxArea(bbox2);
|
|
40314
|
+
|
|
40315
|
+
// choose distribution with minimum overlap
|
|
40316
|
+
if (overlap < minOverlap) {
|
|
40317
|
+
minOverlap = overlap;
|
|
40318
|
+
index = i;
|
|
40319
|
+
|
|
40320
|
+
minArea = area < minArea ? area : minArea;
|
|
40321
|
+
|
|
40322
|
+
} else if (overlap === minOverlap) {
|
|
40323
|
+
// otherwise choose distribution with minimum area
|
|
40324
|
+
if (area < minArea) {
|
|
40325
|
+
minArea = area;
|
|
40326
|
+
index = i;
|
|
40327
|
+
}
|
|
40328
|
+
}
|
|
40329
|
+
}
|
|
40330
|
+
|
|
40331
|
+
return index || M - m;
|
|
40332
|
+
}
|
|
40333
|
+
|
|
40334
|
+
// sorts node children by the best axis for split
|
|
40335
|
+
_chooseSplitAxis(node, m, M) {
|
|
40336
|
+
const compareMinX = node.leaf ? this.compareMinX : compareNodeMinX;
|
|
40337
|
+
const compareMinY = node.leaf ? this.compareMinY : compareNodeMinY;
|
|
40338
|
+
const xMargin = this._allDistMargin(node, m, M, compareMinX);
|
|
40339
|
+
const yMargin = this._allDistMargin(node, m, M, compareMinY);
|
|
40340
|
+
|
|
40341
|
+
// if total distributions margin value is minimal for x, sort by minX,
|
|
40342
|
+
// otherwise it's already sorted by minY
|
|
40343
|
+
if (xMargin < yMargin) node.children.sort(compareMinX);
|
|
40344
|
+
}
|
|
40345
|
+
|
|
40346
|
+
// total margin of all possible split distributions where each node is at least m full
|
|
40347
|
+
_allDistMargin(node, m, M, compare) {
|
|
40348
|
+
node.children.sort(compare);
|
|
40349
|
+
|
|
40350
|
+
const toBBox = this.toBBox;
|
|
40351
|
+
const leftBBox = distBBox(node, 0, m, toBBox);
|
|
40352
|
+
const rightBBox = distBBox(node, M - m, M, toBBox);
|
|
40353
|
+
let margin = bboxMargin(leftBBox) + bboxMargin(rightBBox);
|
|
40354
|
+
|
|
40355
|
+
for (let i = m; i < M - m; i++) {
|
|
40356
|
+
const child = node.children[i];
|
|
40357
|
+
extend(leftBBox, node.leaf ? toBBox(child) : child);
|
|
40358
|
+
margin += bboxMargin(leftBBox);
|
|
40359
|
+
}
|
|
40360
|
+
|
|
40361
|
+
for (let i = M - m - 1; i >= m; i--) {
|
|
40362
|
+
const child = node.children[i];
|
|
40363
|
+
extend(rightBBox, node.leaf ? toBBox(child) : child);
|
|
40364
|
+
margin += bboxMargin(rightBBox);
|
|
40365
|
+
}
|
|
40366
|
+
|
|
40367
|
+
return margin;
|
|
40368
|
+
}
|
|
40369
|
+
|
|
40370
|
+
_adjustParentBBoxes(bbox, path, level) {
|
|
40371
|
+
// adjust bboxes along the given tree path
|
|
40372
|
+
for (let i = level; i >= 0; i--) {
|
|
40373
|
+
extend(path[i], bbox);
|
|
40374
|
+
}
|
|
40375
|
+
}
|
|
40376
|
+
|
|
40377
|
+
_condense(path) {
|
|
40378
|
+
// go through the path, removing empty nodes and updating bboxes
|
|
40379
|
+
for (let i = path.length - 1, siblings; i >= 0; i--) {
|
|
40380
|
+
if (path[i].children.length === 0) {
|
|
40381
|
+
if (i > 0) {
|
|
40382
|
+
siblings = path[i - 1].children;
|
|
40383
|
+
siblings.splice(siblings.indexOf(path[i]), 1);
|
|
40384
|
+
|
|
40385
|
+
} else this.clear();
|
|
40386
|
+
|
|
40387
|
+
} else calcBBox(path[i], this.toBBox);
|
|
40388
|
+
}
|
|
40389
|
+
}
|
|
40390
|
+
}
|
|
40391
|
+
|
|
40392
|
+
function findItem(item, items, equalsFn) {
|
|
40393
|
+
if (!equalsFn) return items.indexOf(item);
|
|
40394
|
+
|
|
40395
|
+
for (let i = 0; i < items.length; i++) {
|
|
40396
|
+
if (equalsFn(item, items[i])) return i;
|
|
40397
|
+
}
|
|
40398
|
+
return -1;
|
|
40399
|
+
}
|
|
40400
|
+
|
|
40401
|
+
// calculate node's bbox from bboxes of its children
|
|
40402
|
+
function calcBBox(node, toBBox) {
|
|
40403
|
+
distBBox(node, 0, node.children.length, toBBox, node);
|
|
40404
|
+
}
|
|
40405
|
+
|
|
40406
|
+
// min bounding rectangle of node children from k to p-1
|
|
40407
|
+
function distBBox(node, k, p, toBBox, destNode) {
|
|
40408
|
+
if (!destNode) destNode = createNode(null);
|
|
40409
|
+
destNode.minX = Infinity;
|
|
40410
|
+
destNode.minY = Infinity;
|
|
40411
|
+
destNode.maxX = -Infinity;
|
|
40412
|
+
destNode.maxY = -Infinity;
|
|
40413
|
+
|
|
40414
|
+
for (let i = k; i < p; i++) {
|
|
40415
|
+
const child = node.children[i];
|
|
40416
|
+
extend(destNode, node.leaf ? toBBox(child) : child);
|
|
40417
|
+
}
|
|
40418
|
+
|
|
40419
|
+
return destNode;
|
|
40420
|
+
}
|
|
40421
|
+
|
|
40422
|
+
function extend(a, b) {
|
|
40423
|
+
a.minX = Math.min(a.minX, b.minX);
|
|
40424
|
+
a.minY = Math.min(a.minY, b.minY);
|
|
40425
|
+
a.maxX = Math.max(a.maxX, b.maxX);
|
|
40426
|
+
a.maxY = Math.max(a.maxY, b.maxY);
|
|
40427
|
+
return a;
|
|
40428
|
+
}
|
|
40429
|
+
|
|
40430
|
+
function compareNodeMinX(a, b) { return a.minX - b.minX; }
|
|
40431
|
+
function compareNodeMinY(a, b) { return a.minY - b.minY; }
|
|
40432
|
+
|
|
40433
|
+
function bboxArea(a) { return (a.maxX - a.minX) * (a.maxY - a.minY); }
|
|
40434
|
+
function bboxMargin(a) { return (a.maxX - a.minX) + (a.maxY - a.minY); }
|
|
40435
|
+
|
|
40436
|
+
function enlargedArea(a, b) {
|
|
40437
|
+
return (Math.max(b.maxX, a.maxX) - Math.min(b.minX, a.minX)) *
|
|
40438
|
+
(Math.max(b.maxY, a.maxY) - Math.min(b.minY, a.minY));
|
|
40439
|
+
}
|
|
40440
|
+
|
|
40441
|
+
function intersectionArea(a, b) {
|
|
40442
|
+
const minX = Math.max(a.minX, b.minX);
|
|
40443
|
+
const minY = Math.max(a.minY, b.minY);
|
|
40444
|
+
const maxX = Math.min(a.maxX, b.maxX);
|
|
40445
|
+
const maxY = Math.min(a.maxY, b.maxY);
|
|
40446
|
+
|
|
40447
|
+
return Math.max(0, maxX - minX) *
|
|
40448
|
+
Math.max(0, maxY - minY);
|
|
40449
|
+
}
|
|
40450
|
+
|
|
40451
|
+
function contains(a, b) {
|
|
40452
|
+
return a.minX <= b.minX &&
|
|
40453
|
+
a.minY <= b.minY &&
|
|
40454
|
+
b.maxX <= a.maxX &&
|
|
40455
|
+
b.maxY <= a.maxY;
|
|
40456
|
+
}
|
|
40457
|
+
|
|
40458
|
+
function intersects(a, b) {
|
|
40459
|
+
return b.minX <= a.maxX &&
|
|
40460
|
+
b.minY <= a.maxY &&
|
|
40461
|
+
b.maxX >= a.minX &&
|
|
40462
|
+
b.maxY >= a.minY;
|
|
40463
|
+
}
|
|
40464
|
+
|
|
40465
|
+
function createNode(children) {
|
|
40466
|
+
return {
|
|
40467
|
+
children,
|
|
40468
|
+
height: 1,
|
|
40469
|
+
leaf: true,
|
|
40470
|
+
minX: Infinity,
|
|
40471
|
+
minY: Infinity,
|
|
40472
|
+
maxX: -Infinity,
|
|
40473
|
+
maxY: -Infinity
|
|
40474
|
+
};
|
|
40475
|
+
}
|
|
40476
|
+
|
|
40477
|
+
// sort an array so that items come in groups of n unsorted items, with groups sorted between each other;
|
|
40478
|
+
// combines selection algorithm with binary divide & conquer approach
|
|
40479
|
+
|
|
40480
|
+
function multiSelect(arr, left, right, n, compare) {
|
|
40481
|
+
const stack = [left, right];
|
|
40482
|
+
|
|
40483
|
+
while (stack.length) {
|
|
40484
|
+
right = stack.pop();
|
|
40485
|
+
left = stack.pop();
|
|
40486
|
+
|
|
40487
|
+
if (right - left <= n) continue;
|
|
40488
|
+
|
|
40489
|
+
const mid = left + Math.ceil((right - left) / n / 2) * n;
|
|
40490
|
+
quickselect(arr, mid, left, right, compare);
|
|
40491
|
+
|
|
40492
|
+
stack.push(left, mid, mid, right);
|
|
40493
|
+
}
|
|
40494
|
+
}
|
|
40495
|
+
|
|
39856
40496
|
/**
|
|
39857
|
-
*
|
|
40497
|
+
* R-Tree Data Structure
|
|
40498
|
+
*
|
|
40499
|
+
* R-Tree is a spatial data structure used for efficient indexing and querying
|
|
40500
|
+
* of multi-dimensional objects, particularly in geometric and spatial applications.
|
|
40501
|
+
*
|
|
40502
|
+
* It organizes objects into a tree hierarchy, grouping nearby objects together
|
|
40503
|
+
* in bounding boxes. Each node in the tree represents a bounding box that
|
|
40504
|
+
* contains its child nodes or leaf objects. This hierarchical structure allows
|
|
40505
|
+
* for faster spatial queries.
|
|
40506
|
+
*
|
|
40507
|
+
* @see https://en.wikipedia.org/wiki/R-tree
|
|
40508
|
+
*
|
|
40509
|
+
* Consider a 2D Space with four zones: A, B, C, D
|
|
40510
|
+
* +--------------------------+
|
|
40511
|
+
* | |
|
|
40512
|
+
* | +---+ +-------+ |
|
|
40513
|
+
* | | A | | B | |
|
|
40514
|
+
* | +---+ +-------+ |
|
|
40515
|
+
* | |
|
|
40516
|
+
* | |
|
|
40517
|
+
* | +---+ |
|
|
40518
|
+
* | | C | |
|
|
40519
|
+
* | +---+ |
|
|
40520
|
+
* | +-----------+ |
|
|
40521
|
+
* | | D | |
|
|
40522
|
+
* | +-----------+ |
|
|
40523
|
+
* | |
|
|
40524
|
+
* +--------------------------+
|
|
40525
|
+
*
|
|
40526
|
+
* It groups together zones that are spatially close into a minimum bounding box.
|
|
40527
|
+
* For example, A and B are grouped together in rectangle R1, and C and D are grouped
|
|
40528
|
+
* in R2.
|
|
40529
|
+
*
|
|
40530
|
+
* R0
|
|
40531
|
+
* +--------------------------+
|
|
40532
|
+
* | R1 |
|
|
40533
|
+
* | +-----------------+ |
|
|
40534
|
+
* | | A | | B | |
|
|
40535
|
+
* | +-----------------+ |
|
|
40536
|
+
* | |
|
|
40537
|
+
* | R2 |
|
|
40538
|
+
* | +---+---+---+ |
|
|
40539
|
+
* | | | C | | |
|
|
40540
|
+
* | | +---+ | |
|
|
40541
|
+
* | +-----------+ |
|
|
40542
|
+
* | | D | |
|
|
40543
|
+
* | +-----------+ |
|
|
40544
|
+
* | |
|
|
40545
|
+
* +--------------------------+
|
|
40546
|
+
*
|
|
40547
|
+
* The tree would look like this:
|
|
40548
|
+
* R0
|
|
40549
|
+
* / \
|
|
40550
|
+
* / \
|
|
40551
|
+
* R1 R2
|
|
40552
|
+
* | |
|
|
40553
|
+
* A,B C,D
|
|
40554
|
+
|
|
40555
|
+
* Choosing how to group the zones is crucial for the performance of the tree.
|
|
40556
|
+
* Key considerations include avoiding excessive empty space coverage and minimizing overlap
|
|
40557
|
+
* to reduce the number of subtrees processed during searches.
|
|
40558
|
+
*
|
|
40559
|
+
* Various heuristics exist for determining the optimal grouping strategy, such as "least enlargement"
|
|
40560
|
+
* which prioritizes grouping nodes resulting in the smallest increase in bounding box size. In cases where
|
|
40561
|
+
* the choice cannot be made based on this criterion due to the same enlargement for different groupings,
|
|
40562
|
+
* we then evaluate "least area," aiming to minimize the overall area of bounding boxes.
|
|
40563
|
+
*
|
|
40564
|
+
* This implementation is tailored for spreadsheet use, indexing objects associated
|
|
40565
|
+
* with a zone and a sheet.
|
|
40566
|
+
*
|
|
40567
|
+
* It uses the RBush library under the hood. One 2D RBush R-tree per sheet.
|
|
40568
|
+
* @see https://github.com/mourner/rbush
|
|
40569
|
+
*/
|
|
40570
|
+
class SpreadsheetRTree {
|
|
40571
|
+
/**
|
|
40572
|
+
* One 2D R-tree per sheet
|
|
40573
|
+
*/
|
|
40574
|
+
rTrees = {};
|
|
40575
|
+
/**
|
|
40576
|
+
* Bulk-inserts the given items into the tree. Bulk insertion is usually ~2-3 times
|
|
40577
|
+
* faster than inserting items one by one. After bulk loading (bulk insertion into
|
|
40578
|
+
* an empty tree), subsequent query performance is also ~20-30% better.
|
|
40579
|
+
*/
|
|
40580
|
+
constructor(items = []) {
|
|
40581
|
+
const rangesPerSheet = {};
|
|
40582
|
+
for (const item of items) {
|
|
40583
|
+
const sheetId = item.boundingBox.sheetId;
|
|
40584
|
+
if (!rangesPerSheet[sheetId]) {
|
|
40585
|
+
rangesPerSheet[sheetId] = [];
|
|
40586
|
+
}
|
|
40587
|
+
rangesPerSheet[sheetId].push(item);
|
|
40588
|
+
}
|
|
40589
|
+
for (const sheetId in rangesPerSheet) {
|
|
40590
|
+
this.rTrees[sheetId] = new ZoneRBush();
|
|
40591
|
+
this.rTrees[sheetId].load(rangesPerSheet[sheetId]); // bulk-insert
|
|
40592
|
+
}
|
|
40593
|
+
}
|
|
40594
|
+
insert(item) {
|
|
40595
|
+
const sheetId = item.boundingBox.sheetId;
|
|
40596
|
+
if (!this.rTrees[sheetId]) {
|
|
40597
|
+
this.rTrees[sheetId] = new ZoneRBush();
|
|
40598
|
+
}
|
|
40599
|
+
this.rTrees[sheetId].insert(item);
|
|
40600
|
+
}
|
|
40601
|
+
search({ zone, sheetId }) {
|
|
40602
|
+
if (!this.rTrees[sheetId]) {
|
|
40603
|
+
return [];
|
|
40604
|
+
}
|
|
40605
|
+
return this.rTrees[sheetId].search({
|
|
40606
|
+
minX: zone.left,
|
|
40607
|
+
minY: zone.top,
|
|
40608
|
+
maxX: zone.right,
|
|
40609
|
+
maxY: zone.bottom,
|
|
40610
|
+
});
|
|
40611
|
+
}
|
|
40612
|
+
remove(item) {
|
|
40613
|
+
const sheetId = item.boundingBox.sheetId;
|
|
40614
|
+
if (!this.rTrees[sheetId]) {
|
|
40615
|
+
return;
|
|
40616
|
+
}
|
|
40617
|
+
this.rTrees[sheetId].remove(item, deepEquals);
|
|
40618
|
+
}
|
|
40619
|
+
}
|
|
40620
|
+
/**
|
|
40621
|
+
* RBush extension to use zones as bounding boxes
|
|
40622
|
+
*/
|
|
40623
|
+
class ZoneRBush extends RBush {
|
|
40624
|
+
toBBox({ boundingBox }) {
|
|
40625
|
+
const zone = boundingBox.zone;
|
|
40626
|
+
return {
|
|
40627
|
+
minX: zone.left,
|
|
40628
|
+
minY: zone.top,
|
|
40629
|
+
maxX: zone.right,
|
|
40630
|
+
maxY: zone.bottom,
|
|
40631
|
+
};
|
|
40632
|
+
}
|
|
40633
|
+
compareMinX(a, b) {
|
|
40634
|
+
return a.boundingBox.zone.left - b.boundingBox.zone.left;
|
|
40635
|
+
}
|
|
40636
|
+
compareMinY(a, b) {
|
|
40637
|
+
return a.boundingBox.zone.top - b.boundingBox.zone.top;
|
|
40638
|
+
}
|
|
40639
|
+
}
|
|
40640
|
+
|
|
40641
|
+
/**
|
|
40642
|
+
* Implementation of a dependency Graph.
|
|
39858
40643
|
* The graph is used to evaluate the cells in the correct
|
|
39859
40644
|
* order, and should be updated each time a cell's content is modified
|
|
39860
40645
|
*
|
|
40646
|
+
* It uses an R-Tree data structure to efficiently find dependent cells.
|
|
39861
40647
|
*/
|
|
39862
40648
|
class FormulaDependencyGraph {
|
|
39863
|
-
|
|
39864
|
-
* Internal structure:
|
|
39865
|
-
* - key: a cell position (encoded as an integer)
|
|
39866
|
-
* - value: a set of cell positions that depends on the key
|
|
39867
|
-
*
|
|
39868
|
-
* Given
|
|
39869
|
-
* - A1:"= B1 + SQRT(B2)"
|
|
39870
|
-
* - C1:"= B1";
|
|
39871
|
-
* - C2:"= C1"
|
|
39872
|
-
*
|
|
39873
|
-
* we will have something like:
|
|
39874
|
-
* - B1 ---> (A1, C1) meaning A1 and C1 depends on B1
|
|
39875
|
-
* - B2 ---> (A1) meaning A1 depends on B2
|
|
39876
|
-
* - C1 ---> (C2) meaning C2 depends on C1
|
|
39877
|
-
*/
|
|
39878
|
-
inverseDependencies = new Map();
|
|
40649
|
+
encoder;
|
|
39879
40650
|
dependencies = new Map();
|
|
40651
|
+
rTree;
|
|
40652
|
+
constructor(encoder, data = []) {
|
|
40653
|
+
this.encoder = encoder;
|
|
40654
|
+
this.rTree = new SpreadsheetRTree(data);
|
|
40655
|
+
}
|
|
39880
40656
|
removeAllDependencies(formulaPositionId) {
|
|
39881
|
-
const
|
|
39882
|
-
if (!
|
|
40657
|
+
const ranges = this.dependencies.get(formulaPositionId);
|
|
40658
|
+
if (!ranges) {
|
|
39883
40659
|
return;
|
|
39884
40660
|
}
|
|
39885
|
-
for (const
|
|
39886
|
-
this.
|
|
40661
|
+
for (const range of ranges) {
|
|
40662
|
+
this.rTree.remove(range);
|
|
39887
40663
|
}
|
|
39888
40664
|
this.dependencies.delete(formulaPositionId);
|
|
39889
40665
|
}
|
|
39890
40666
|
addDependencies(formulaPositionId, dependencies) {
|
|
39891
|
-
|
|
39892
|
-
|
|
39893
|
-
|
|
39894
|
-
|
|
39895
|
-
|
|
39896
|
-
|
|
39897
|
-
|
|
39898
|
-
|
|
40667
|
+
const rTreeItems = dependencies.map(({ sheetId, zone }) => ({
|
|
40668
|
+
data: formulaPositionId,
|
|
40669
|
+
boundingBox: {
|
|
40670
|
+
zone,
|
|
40671
|
+
sheetId,
|
|
40672
|
+
},
|
|
40673
|
+
}));
|
|
40674
|
+
for (const item of rTreeItems) {
|
|
40675
|
+
this.rTree.insert(item);
|
|
39899
40676
|
}
|
|
39900
40677
|
const existingDependencies = this.dependencies.get(formulaPositionId);
|
|
39901
40678
|
if (existingDependencies) {
|
|
39902
|
-
existingDependencies.push(...
|
|
40679
|
+
existingDependencies.push(...rTreeItems);
|
|
39903
40680
|
}
|
|
39904
40681
|
else {
|
|
39905
|
-
this.dependencies.set(formulaPositionId,
|
|
40682
|
+
this.dependencies.set(formulaPositionId, rTreeItems);
|
|
39906
40683
|
}
|
|
39907
40684
|
}
|
|
39908
40685
|
/**
|
|
@@ -39910,20 +40687,20 @@
|
|
|
39910
40687
|
* in the correct order they should be evaluated.
|
|
39911
40688
|
* This is called a topological ordering (excluding cycles)
|
|
39912
40689
|
*/
|
|
39913
|
-
getCellsDependingOn(
|
|
40690
|
+
getCellsDependingOn(ranges) {
|
|
39914
40691
|
const visited = new JetSet();
|
|
39915
|
-
const queue = Array.from(
|
|
40692
|
+
const queue = Array.from(ranges).reverse();
|
|
39916
40693
|
while (queue.length > 0) {
|
|
39917
|
-
const
|
|
39918
|
-
visited.add(
|
|
39919
|
-
const
|
|
39920
|
-
for (const
|
|
39921
|
-
if (!visited.has(
|
|
39922
|
-
queue.push(
|
|
40694
|
+
const range = queue.pop();
|
|
40695
|
+
visited.add(...this.encoder.encodeBoundingBox(range));
|
|
40696
|
+
const impactedPositionIds = this.rTree.search(range).map((dep) => dep.data);
|
|
40697
|
+
for (const positionId of impactedPositionIds) {
|
|
40698
|
+
if (!visited.has(positionId)) {
|
|
40699
|
+
queue.push(this.encoder.decodeToBoundingBox(positionId));
|
|
39923
40700
|
}
|
|
39924
40701
|
}
|
|
39925
40702
|
}
|
|
39926
|
-
visited.delete(...
|
|
40703
|
+
visited.delete(...ranges.flatMap((r) => this.encoder.encodeBoundingBox(r)));
|
|
39927
40704
|
return visited;
|
|
39928
40705
|
}
|
|
39929
40706
|
}
|
|
@@ -40011,9 +40788,9 @@
|
|
|
40011
40788
|
context;
|
|
40012
40789
|
getters;
|
|
40013
40790
|
compilationParams;
|
|
40014
|
-
|
|
40791
|
+
encoder = new PositionBitsEncoder();
|
|
40015
40792
|
evaluatedCells = new Map();
|
|
40016
|
-
formulaDependencies = lazy(new FormulaDependencyGraph());
|
|
40793
|
+
formulaDependencies = lazy(new FormulaDependencyGraph(this.encoder));
|
|
40017
40794
|
blockedArrayFormulas = new Set();
|
|
40018
40795
|
spreadingRelations = new SpreadingRelation();
|
|
40019
40796
|
constructor(context, getters) {
|
|
@@ -40022,23 +40799,23 @@
|
|
|
40022
40799
|
this.compilationParams = buildCompilationParameters(this.context, this.getters, this.computeAndSave.bind(this));
|
|
40023
40800
|
}
|
|
40024
40801
|
getEvaluatedCell(position) {
|
|
40025
|
-
return (this.evaluatedCells.get(this.
|
|
40802
|
+
return (this.evaluatedCells.get(this.encoder.encode(position)) ||
|
|
40026
40803
|
createEvaluatedCell("", { locale: this.getters.getLocale() }));
|
|
40027
40804
|
}
|
|
40028
40805
|
getSpreadPositionsOf(position) {
|
|
40029
|
-
const positionId = this.
|
|
40806
|
+
const positionId = this.encoder.encode(position);
|
|
40030
40807
|
if (!this.spreadingRelations.isArrayFormula(positionId)) {
|
|
40031
40808
|
return [];
|
|
40032
40809
|
}
|
|
40033
|
-
return Array.from(this.spreadingRelations.getArrayResultPositionIds(positionId)).map(this.
|
|
40810
|
+
return Array.from(this.spreadingRelations.getArrayResultPositionIds(positionId)).map((positionId) => this.encoder.decode(positionId));
|
|
40034
40811
|
}
|
|
40035
40812
|
getArrayFormulaSpreadingOn(position) {
|
|
40036
|
-
const positionId = this.
|
|
40813
|
+
const positionId = this.encoder.encode(position);
|
|
40037
40814
|
const formulaPosition = this.getArrayFormulaSpreadingOnId(positionId);
|
|
40038
|
-
return formulaPosition !== undefined ? this.
|
|
40815
|
+
return formulaPosition !== undefined ? this.encoder.decode(formulaPosition) : undefined;
|
|
40039
40816
|
}
|
|
40040
40817
|
getEvaluatedPositions() {
|
|
40041
|
-
return [...this.evaluatedCells.keys()].map(this.
|
|
40818
|
+
return [...this.evaluatedCells.keys()].map((p) => this.encoder.decode(p));
|
|
40042
40819
|
}
|
|
40043
40820
|
getArrayFormulaSpreadingOnId(positionId) {
|
|
40044
40821
|
if (!this.spreadingRelations.hasArrayFormulaResult(positionId)) {
|
|
@@ -40048,7 +40825,7 @@
|
|
|
40048
40825
|
return Array.from(arrayFormulas).find((positionId) => !this.blockedArrayFormulas.has(positionId));
|
|
40049
40826
|
}
|
|
40050
40827
|
updateDependencies(position) {
|
|
40051
|
-
const positionId = this.
|
|
40828
|
+
const positionId = this.encoder.encode(position);
|
|
40052
40829
|
this.formulaDependencies().removeAllDependencies(positionId);
|
|
40053
40830
|
const dependencies = this.getDirectDependencies(positionId);
|
|
40054
40831
|
this.formulaDependencies().addDependencies(positionId, dependencies);
|
|
@@ -40058,12 +40835,12 @@
|
|
|
40058
40835
|
this.compilationParams = buildCompilationParameters(this.context, this.getters, this.computeAndSave.bind(this));
|
|
40059
40836
|
}
|
|
40060
40837
|
evaluateCells(positions) {
|
|
40061
|
-
const cells = positions.map(this.
|
|
40838
|
+
const cells = positions.map((p) => this.encoder.encode(p));
|
|
40062
40839
|
const cellsToCompute = new JetSet(cells);
|
|
40063
|
-
const
|
|
40840
|
+
const arrayFormulasPositionIds = this.getArrayFormulasImpactedByChangesOf(cells);
|
|
40064
40841
|
cellsToCompute.add(...this.getCellsDependingOn(cells));
|
|
40065
|
-
cellsToCompute.add(...
|
|
40066
|
-
cellsToCompute.add(...this.getCellsDependingOn(
|
|
40842
|
+
cellsToCompute.add(...arrayFormulasPositionIds);
|
|
40843
|
+
cellsToCompute.add(...this.getCellsDependingOn(arrayFormulasPositionIds));
|
|
40067
40844
|
this.evaluate(cellsToCompute);
|
|
40068
40845
|
}
|
|
40069
40846
|
getArrayFormulasImpactedByChangesOf(positionIds) {
|
|
@@ -40086,12 +40863,14 @@
|
|
|
40086
40863
|
this.blockedArrayFormulas = new Set();
|
|
40087
40864
|
this.spreadingRelations = new SpreadingRelation();
|
|
40088
40865
|
this.formulaDependencies = lazy(() => {
|
|
40089
|
-
const
|
|
40090
|
-
|
|
40091
|
-
|
|
40092
|
-
|
|
40093
|
-
|
|
40094
|
-
|
|
40866
|
+
const dependencies = [...this.getAllCells()].flatMap((positionId) => this.getDirectDependencies(positionId).map((range) => ({
|
|
40867
|
+
data: positionId,
|
|
40868
|
+
boundingBox: {
|
|
40869
|
+
zone: range.zone,
|
|
40870
|
+
sheetId: range.sheetId,
|
|
40871
|
+
},
|
|
40872
|
+
})));
|
|
40873
|
+
return new FormulaDependencyGraph(this.encoder, dependencies);
|
|
40095
40874
|
});
|
|
40096
40875
|
}
|
|
40097
40876
|
evaluateAllCells() {
|
|
@@ -40113,7 +40892,7 @@
|
|
|
40113
40892
|
for (const sheetId of this.getters.getSheetIds()) {
|
|
40114
40893
|
const cellIds = this.getters.getCells(sheetId);
|
|
40115
40894
|
for (const cellId in cellIds) {
|
|
40116
|
-
positionIds.add(this.
|
|
40895
|
+
positionIds.add(this.encoder.encode(this.getters.getCellPosition(cellId)));
|
|
40117
40896
|
}
|
|
40118
40897
|
}
|
|
40119
40898
|
return positionIds;
|
|
@@ -40180,7 +40959,7 @@
|
|
|
40180
40959
|
}
|
|
40181
40960
|
}
|
|
40182
40961
|
computeAndSave(position) {
|
|
40183
|
-
const positionId = this.
|
|
40962
|
+
const positionId = this.encoder.encode(position);
|
|
40184
40963
|
const evaluatedCell = this.computeCell(positionId);
|
|
40185
40964
|
if (!this.evaluatedCells.has(positionId)) {
|
|
40186
40965
|
this.setEvaluatedCell(positionId, evaluatedCell);
|
|
@@ -40240,15 +41019,15 @@
|
|
|
40240
41019
|
throw new Error(_t("Result couldn't be automatically expanded. Please insert more columns and rows."));
|
|
40241
41020
|
}
|
|
40242
41021
|
updateSpreadRelation({ sheetId, col, row, }) {
|
|
40243
|
-
const arrayFormulaPositionId = this.
|
|
41022
|
+
const arrayFormulaPositionId = this.encoder.encode({ sheetId, col, row });
|
|
40244
41023
|
return (i, j) => {
|
|
40245
41024
|
const position = { sheetId, col: i + col, row: j + row };
|
|
40246
|
-
const resultPositionId = this.
|
|
41025
|
+
const resultPositionId = this.encoder.encode(position);
|
|
40247
41026
|
this.spreadingRelations.addRelation({ resultPositionId, arrayFormulaPositionId });
|
|
40248
41027
|
};
|
|
40249
41028
|
}
|
|
40250
41029
|
checkCollision({ sheetId, col, row }) {
|
|
40251
|
-
const formulaPositionId = this.
|
|
41030
|
+
const formulaPositionId = this.encoder.encode({ sheetId, col, row });
|
|
40252
41031
|
return (i, j) => {
|
|
40253
41032
|
const position = { sheetId: sheetId, col: i + col, row: j + row };
|
|
40254
41033
|
const rawCell = this.getters.getCell(position);
|
|
@@ -40269,7 +41048,7 @@
|
|
|
40269
41048
|
format: format || matrixResult[i][j]?.format,
|
|
40270
41049
|
locale: this.getters.getLocale(),
|
|
40271
41050
|
});
|
|
40272
|
-
const positionId = this.
|
|
41051
|
+
const positionId = this.encoder.encode(position);
|
|
40273
41052
|
this.setEvaluatedCell(positionId, evaluatedCell);
|
|
40274
41053
|
// check if formula dependencies present in the spread zone
|
|
40275
41054
|
// if so, they need to be recomputed
|
|
@@ -40301,29 +41080,17 @@
|
|
|
40301
41080
|
if (!cell?.isFormula) {
|
|
40302
41081
|
return [];
|
|
40303
41082
|
}
|
|
40304
|
-
|
|
40305
|
-
for (const range of cell.compiledFormula.dependencies) {
|
|
40306
|
-
if (range.invalidSheetName || range.invalidXc) {
|
|
40307
|
-
continue;
|
|
40308
|
-
}
|
|
40309
|
-
const sheetId = range.sheetId;
|
|
40310
|
-
forEachPositionsInZone(range.zone, (col, row) => {
|
|
40311
|
-
dependencies.push(this.encodePosition({ sheetId, col, row }));
|
|
40312
|
-
});
|
|
40313
|
-
}
|
|
40314
|
-
return dependencies;
|
|
41083
|
+
return cell.compiledFormula.dependencies;
|
|
40315
41084
|
}
|
|
40316
41085
|
getCellsDependingOn(positionIds) {
|
|
40317
|
-
|
|
41086
|
+
const ranges = [];
|
|
41087
|
+
for (const positionId of positionIds) {
|
|
41088
|
+
ranges.push(this.encoder.decodeToBoundingBox(positionId));
|
|
41089
|
+
}
|
|
41090
|
+
return this.formulaDependencies().getCellsDependingOn(ranges);
|
|
40318
41091
|
}
|
|
40319
41092
|
getCell(positionId) {
|
|
40320
|
-
return this.getters.getCell(this.
|
|
40321
|
-
}
|
|
40322
|
-
encodePosition(position) {
|
|
40323
|
-
return this.positionEncoder.encode(position);
|
|
40324
|
-
}
|
|
40325
|
-
decodePosition(positionId) {
|
|
40326
|
-
return this.positionEncoder.decode(positionId);
|
|
41093
|
+
return this.getters.getCell(this.encoder.decode(positionId));
|
|
40327
41094
|
}
|
|
40328
41095
|
}
|
|
40329
41096
|
function forEachSpreadPositionInMatrix(nbColumns, nbRows, callback) {
|
|
@@ -40379,6 +41146,13 @@
|
|
|
40379
41146
|
encode({ sheetId, col, row }) {
|
|
40380
41147
|
return (this.encodeSheet(sheetId) << 42n) | (BigInt(col) << 21n) | BigInt(row);
|
|
40381
41148
|
}
|
|
41149
|
+
encodeBoundingBox({ sheetId, zone }) {
|
|
41150
|
+
const positions = [];
|
|
41151
|
+
forEachPositionsInZone(zone, (col, row) => {
|
|
41152
|
+
positions.push(this.encode({ sheetId, col, row }));
|
|
41153
|
+
});
|
|
41154
|
+
return positions;
|
|
41155
|
+
}
|
|
40382
41156
|
decode(id) {
|
|
40383
41157
|
// keep only the last 21 bits by AND-ing the bit sequence with 21 ones
|
|
40384
41158
|
const row = Number(id & 2097151n);
|
|
@@ -40386,6 +41160,10 @@
|
|
|
40386
41160
|
const sheetId = this.decodeSheet(id >> 42n);
|
|
40387
41161
|
return { sheetId, col, row };
|
|
40388
41162
|
}
|
|
41163
|
+
decodeToBoundingBox(id) {
|
|
41164
|
+
const { sheetId, col, row } = this.decode(id);
|
|
41165
|
+
return { sheetId, zone: { left: col, top: row, right: col, bottom: row } };
|
|
41166
|
+
}
|
|
40389
41167
|
encodeSheet(sheetId) {
|
|
40390
41168
|
const sheetKey = this.sheetMapping[sheetId];
|
|
40391
41169
|
if (sheetKey === undefined) {
|
|
@@ -40556,7 +41334,12 @@
|
|
|
40556
41334
|
// Getters
|
|
40557
41335
|
// ---------------------------------------------------------------------------
|
|
40558
41336
|
evaluateFormula(sheetId, formulaString) {
|
|
40559
|
-
|
|
41337
|
+
try {
|
|
41338
|
+
return this.evaluator.evaluateFormula(sheetId, formulaString);
|
|
41339
|
+
}
|
|
41340
|
+
catch (error) {
|
|
41341
|
+
return error instanceof EvaluationError ? error.errorType : CellErrorType.GenericError;
|
|
41342
|
+
}
|
|
40560
41343
|
}
|
|
40561
41344
|
/**
|
|
40562
41345
|
* Return the value of each cell in the range as they are displayed in the grid.
|
|
@@ -40825,7 +41608,7 @@
|
|
|
40825
41608
|
}
|
|
40826
41609
|
|
|
40827
41610
|
class EvaluationChartPlugin extends UIPlugin {
|
|
40828
|
-
static getters = ["getChartRuntime", "
|
|
41611
|
+
static getters = ["getChartRuntime", "getStyleOfSingleCellChart"];
|
|
40829
41612
|
charts = {};
|
|
40830
41613
|
createRuntimeChart = chartRuntimeFactory(this.getters);
|
|
40831
41614
|
handle(cmd) {
|
|
@@ -40863,25 +41646,26 @@
|
|
|
40863
41646
|
return this.charts[figureId];
|
|
40864
41647
|
}
|
|
40865
41648
|
/**
|
|
40866
|
-
* Get the background
|
|
40867
|
-
* of the chart. In order of priority, it will return :
|
|
40868
|
-
*
|
|
40869
|
-
* - the chart background color if one is defined
|
|
40870
|
-
* - the fill color of the cell if one is defined
|
|
40871
|
-
* - the fill color of the cell from conditional formats if one is defined
|
|
40872
|
-
* - the default chart color if no other color is defined
|
|
41649
|
+
* Get the background and textColor of a chart based on the color of the first cell of the main range of the chart.
|
|
40873
41650
|
*/
|
|
40874
|
-
|
|
41651
|
+
getStyleOfSingleCellChart(chartBackground, mainRange) {
|
|
40875
41652
|
if (chartBackground)
|
|
40876
|
-
return chartBackground;
|
|
41653
|
+
return { background: chartBackground, fontColor: chartFontColor(chartBackground) };
|
|
40877
41654
|
if (!mainRange) {
|
|
40878
|
-
return
|
|
41655
|
+
return {
|
|
41656
|
+
background: BACKGROUND_CHART_COLOR,
|
|
41657
|
+
fontColor: chartFontColor(BACKGROUND_CHART_COLOR),
|
|
41658
|
+
};
|
|
40879
41659
|
}
|
|
40880
41660
|
const col = mainRange.zone.left;
|
|
40881
41661
|
const row = mainRange.zone.top;
|
|
40882
41662
|
const sheetId = mainRange.sheetId;
|
|
40883
41663
|
const style = this.getters.getCellComputedStyle({ sheetId, col, row });
|
|
40884
|
-
|
|
41664
|
+
const background = style.fillColor || BACKGROUND_CHART_COLOR;
|
|
41665
|
+
return {
|
|
41666
|
+
background,
|
|
41667
|
+
fontColor: style.textColor || chartFontColor(background),
|
|
41668
|
+
};
|
|
40885
41669
|
}
|
|
40886
41670
|
exportForExcel(data) {
|
|
40887
41671
|
for (const sheet of data.sheets) {
|
|
@@ -40992,45 +41776,40 @@
|
|
|
40992
41776
|
getComputedStyles(sheetId) {
|
|
40993
41777
|
const computedStyle = {};
|
|
40994
41778
|
for (let cf of this.getters.getConditionalFormats(sheetId).reverse()) {
|
|
40995
|
-
|
|
40996
|
-
|
|
40997
|
-
|
|
40998
|
-
|
|
40999
|
-
|
|
41000
|
-
|
|
41001
|
-
|
|
41002
|
-
|
|
41003
|
-
|
|
41004
|
-
|
|
41005
|
-
|
|
41006
|
-
for (let
|
|
41007
|
-
|
|
41008
|
-
|
|
41009
|
-
|
|
41010
|
-
const
|
|
41011
|
-
|
|
41012
|
-
|
|
41013
|
-
|
|
41014
|
-
|
|
41015
|
-
|
|
41016
|
-
});
|
|
41017
|
-
}
|
|
41018
|
-
return value;
|
|
41019
|
-
});
|
|
41020
|
-
if (predicate && predicate(target, { ...cf.rule, values })) {
|
|
41021
|
-
if (!computedStyle[col])
|
|
41022
|
-
computedStyle[col] = [];
|
|
41023
|
-
// we must combine all the properties of all the CF rules applied to the given cell
|
|
41024
|
-
computedStyle[col][row] = Object.assign(computedStyle[col]?.[row] || {}, cf.rule.style);
|
|
41779
|
+
switch (cf.rule.type) {
|
|
41780
|
+
case "ColorScaleRule":
|
|
41781
|
+
for (let range of cf.ranges) {
|
|
41782
|
+
this.applyColorScale(sheetId, range, cf.rule, computedStyle);
|
|
41783
|
+
}
|
|
41784
|
+
break;
|
|
41785
|
+
case "CellIsRule":
|
|
41786
|
+
const formulas = cf.rule.values.map((value) => value.startsWith("=") ? compile(value) : undefined);
|
|
41787
|
+
for (let ref of cf.ranges) {
|
|
41788
|
+
const zone = this.getters.getRangeFromSheetXC(sheetId, ref).zone;
|
|
41789
|
+
for (let row = zone.top; row <= zone.bottom; row++) {
|
|
41790
|
+
for (let col = zone.left; col <= zone.right; col++) {
|
|
41791
|
+
const predicate = this.rulePredicate[cf.rule.type];
|
|
41792
|
+
const target = { sheetId, col, row };
|
|
41793
|
+
const values = cf.rule.values.map((value, i) => {
|
|
41794
|
+
const compiledFormula = formulas[i];
|
|
41795
|
+
if (compiledFormula) {
|
|
41796
|
+
return this.getters.getTranslatedCellFormula(sheetId, col - zone.left, row - zone.top, {
|
|
41797
|
+
...compiledFormula,
|
|
41798
|
+
dependencies: compiledFormula.dependencies.map((d) => this.getters.getRangeFromSheetXC(sheetId, d)),
|
|
41799
|
+
});
|
|
41025
41800
|
}
|
|
41801
|
+
return value;
|
|
41802
|
+
});
|
|
41803
|
+
if (predicate && predicate(target, { ...cf.rule, values })) {
|
|
41804
|
+
if (!computedStyle[col])
|
|
41805
|
+
computedStyle[col] = [];
|
|
41806
|
+
// we must combine all the properties of all the CF rules applied to the given cell
|
|
41807
|
+
computedStyle[col][row] = Object.assign(computedStyle[col]?.[row] || {}, cf.rule.style);
|
|
41026
41808
|
}
|
|
41027
41809
|
}
|
|
41028
41810
|
}
|
|
41029
|
-
|
|
41030
|
-
|
|
41031
|
-
}
|
|
41032
|
-
catch (_) {
|
|
41033
|
-
// we don't care about the errors within the evaluation of a rule
|
|
41811
|
+
}
|
|
41812
|
+
break;
|
|
41034
41813
|
}
|
|
41035
41814
|
}
|
|
41036
41815
|
return computedStyle;
|
|
@@ -41802,7 +42581,7 @@
|
|
|
41802
42581
|
let row = zone.bottom;
|
|
41803
42582
|
if (col > 0) {
|
|
41804
42583
|
let leftPosition = { sheetId, col: col - 1, row };
|
|
41805
|
-
while (this.getters.
|
|
42584
|
+
while (this.getters.getCorrespondingFormulaCell(leftPosition) ||
|
|
41806
42585
|
this.getters.getCell(leftPosition)?.content) {
|
|
41807
42586
|
row += 1;
|
|
41808
42587
|
leftPosition = { sheetId, col: col - 1, row };
|
|
@@ -41812,7 +42591,7 @@
|
|
|
41812
42591
|
col = zone.right;
|
|
41813
42592
|
if (col <= this.getters.getNumberCols(sheetId)) {
|
|
41814
42593
|
let rightPosition = { sheetId, col: col + 1, row };
|
|
41815
|
-
while (this.getters.
|
|
42594
|
+
while (this.getters.getCorrespondingFormulaCell(rightPosition) ||
|
|
41816
42595
|
this.getters.getCell(rightPosition)?.content) {
|
|
41817
42596
|
row += 1;
|
|
41818
42597
|
rightPosition = { sheetId, col: col + 1, row };
|
|
@@ -52193,7 +52972,7 @@
|
|
|
52193
52972
|
}
|
|
52194
52973
|
onKeydown(ev) {
|
|
52195
52974
|
let keyDownString = "";
|
|
52196
|
-
if (ev
|
|
52975
|
+
if (isCtrlKey(ev)) {
|
|
52197
52976
|
keyDownString += "CTRL+";
|
|
52198
52977
|
}
|
|
52199
52978
|
keyDownString += ev.key.toUpperCase();
|
|
@@ -55905,6 +56684,7 @@
|
|
|
55905
56684
|
colorToRGBA,
|
|
55906
56685
|
positionToZone,
|
|
55907
56686
|
isDefined: isDefined$1,
|
|
56687
|
+
isMatrix,
|
|
55908
56688
|
lazy,
|
|
55909
56689
|
genericRepeat,
|
|
55910
56690
|
createAction,
|
|
@@ -55988,9 +56768,9 @@
|
|
|
55988
56768
|
exports.tokenize = tokenize;
|
|
55989
56769
|
|
|
55990
56770
|
|
|
55991
|
-
__info__.version = "17.0.
|
|
55992
|
-
__info__.date = "
|
|
55993
|
-
__info__.hash = "
|
|
56771
|
+
__info__.version = "17.0.7";
|
|
56772
|
+
__info__.date = "2024-01-12T13:42:04.654Z";
|
|
56773
|
+
__info__.hash = "cadb817";
|
|
55994
56774
|
|
|
55995
56775
|
|
|
55996
56776
|
})(this.o_spreadsheet = this.o_spreadsheet || {}, owl);
|