@odoo/o-spreadsheet 17.2.0-alpha.1 → 17.2.0-alpha.2
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 +2199 -1739
- package/dist/o-spreadsheet.d.ts +49 -29
- package/dist/o-spreadsheet.esm.js +2199 -1739
- package/dist/o-spreadsheet.iife.js +2203 -1743
- package/dist/o-spreadsheet.iife.min.js +370 -370
- package/dist/o_spreadsheet.xml +49 -5
- 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.2.0-alpha.
|
|
6
|
-
* @date 2024-01-
|
|
7
|
-
* @hash
|
|
5
|
+
* @version 17.2.0-alpha.2
|
|
6
|
+
* @date 2024-01-29T13:55:57.132Z
|
|
7
|
+
* @hash 03c1335
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { Component, useRef, onMounted, useEffect, onWillPatch, useState, onWillUpdateProps, onPatched, useComponent, useExternalListener, onWillUnmount, onWillStart, xml, useChildSubEnv, useSubEnv, markRaw } from '@odoo/owl';
|
|
@@ -2060,6 +2060,7 @@ var CommandResult;
|
|
|
2060
2060
|
CommandResult["BlockingValidationRule"] = "BlockingValidationRule";
|
|
2061
2061
|
CommandResult["InvalidCopyPasteSelection"] = "InvalidCopyPasteSelection";
|
|
2062
2062
|
CommandResult["NoChanges"] = "NoChanges";
|
|
2063
|
+
CommandResult["InvalidInputId"] = "InvalidInputId";
|
|
2063
2064
|
})(CommandResult || (CommandResult = {}));
|
|
2064
2065
|
|
|
2065
2066
|
const PLAIN_TEXT_FORMAT = "@"; // see OpenXML spec §18.8.31
|
|
@@ -2121,34 +2122,34 @@ const CellErrorType = {
|
|
|
2121
2122
|
const errorTypes = new Set(Object.values(CellErrorType));
|
|
2122
2123
|
class EvaluationError extends Error {
|
|
2123
2124
|
value;
|
|
2124
|
-
constructor(message, value = CellErrorType.GenericError) {
|
|
2125
|
-
super(message
|
|
2125
|
+
constructor(message = _t("Error"), value = CellErrorType.GenericError) {
|
|
2126
|
+
super(message);
|
|
2126
2127
|
this.value = value;
|
|
2127
2128
|
}
|
|
2128
2129
|
}
|
|
2129
2130
|
class BadExpressionError extends EvaluationError {
|
|
2130
|
-
constructor(message) {
|
|
2131
|
-
super(message
|
|
2131
|
+
constructor(message = _t("Invalid expression")) {
|
|
2132
|
+
super(message, CellErrorType.BadExpression);
|
|
2132
2133
|
}
|
|
2133
2134
|
}
|
|
2134
2135
|
class CircularDependencyError extends EvaluationError {
|
|
2135
|
-
constructor(message) {
|
|
2136
|
-
super(message
|
|
2136
|
+
constructor(message = _t("Circular reference")) {
|
|
2137
|
+
super(message, CellErrorType.CircularDependency);
|
|
2137
2138
|
}
|
|
2138
2139
|
}
|
|
2139
2140
|
class InvalidReferenceError extends EvaluationError {
|
|
2140
|
-
constructor(message) {
|
|
2141
|
-
super(message
|
|
2141
|
+
constructor(message = _t("Invalid reference")) {
|
|
2142
|
+
super(message, CellErrorType.InvalidReference);
|
|
2142
2143
|
}
|
|
2143
2144
|
}
|
|
2144
2145
|
class NotAvailableError extends EvaluationError {
|
|
2145
|
-
constructor(message) {
|
|
2146
|
-
super(message
|
|
2146
|
+
constructor(message = _t("Data not available")) {
|
|
2147
|
+
super(message, CellErrorType.NotAvailable);
|
|
2147
2148
|
}
|
|
2148
2149
|
}
|
|
2149
2150
|
class UnknownFunctionError extends EvaluationError {
|
|
2150
|
-
constructor(message) {
|
|
2151
|
-
super(message
|
|
2151
|
+
constructor(message = _t("Unknown function")) {
|
|
2152
|
+
super(message, CellErrorType.UnknownFunction);
|
|
2152
2153
|
}
|
|
2153
2154
|
}
|
|
2154
2155
|
|
|
@@ -3314,27 +3315,30 @@ function roundFormat(format) {
|
|
|
3314
3315
|
}
|
|
3315
3316
|
function createLargeNumberFormat(format, magnitude, postFix, locale) {
|
|
3316
3317
|
const internalFormat = parseFormat(format || "#,##0");
|
|
3317
|
-
const largeNumberFormat =
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
format: {
|
|
3324
|
-
...formatPart.format,
|
|
3325
|
-
magnitude,
|
|
3326
|
-
decimalPart: undefined,
|
|
3327
|
-
},
|
|
3328
|
-
},
|
|
3329
|
-
{
|
|
3330
|
-
type: "STRING",
|
|
3331
|
-
format: postFix,
|
|
3332
|
-
},
|
|
3333
|
-
];
|
|
3318
|
+
const largeNumberFormat = [];
|
|
3319
|
+
for (let i = 0; i < internalFormat.length; i++) {
|
|
3320
|
+
const formatPart = internalFormat[i];
|
|
3321
|
+
if (formatPart.type !== "NUMBER") {
|
|
3322
|
+
largeNumberFormat.push(formatPart);
|
|
3323
|
+
continue;
|
|
3334
3324
|
}
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3325
|
+
largeNumberFormat.push({
|
|
3326
|
+
...formatPart,
|
|
3327
|
+
format: {
|
|
3328
|
+
...formatPart.format,
|
|
3329
|
+
magnitude,
|
|
3330
|
+
decimalPart: undefined,
|
|
3331
|
+
},
|
|
3332
|
+
});
|
|
3333
|
+
largeNumberFormat.push({
|
|
3334
|
+
type: "STRING",
|
|
3335
|
+
format: postFix,
|
|
3336
|
+
});
|
|
3337
|
+
const nextFormatPart = internalFormat[i + 1];
|
|
3338
|
+
if (nextFormatPart?.type === "STRING" && ["k", "m", "b"].includes(nextFormatPart.format)) {
|
|
3339
|
+
i++;
|
|
3340
|
+
}
|
|
3341
|
+
}
|
|
3338
3342
|
return convertInternalFormatToFormat(largeNumberFormat);
|
|
3339
3343
|
}
|
|
3340
3344
|
function changeDecimalPlaces(format, step, locale) {
|
|
@@ -6691,166 +6695,848 @@ const FilterMenuPopoverBuilder = {
|
|
|
6691
6695
|
},
|
|
6692
6696
|
};
|
|
6693
6697
|
|
|
6694
|
-
const
|
|
6698
|
+
const LINK_TOOLTIP_HEIGHT = 32;
|
|
6699
|
+
const LINK_TOOLTIP_WIDTH = 220;
|
|
6700
|
+
css /* scss */ `
|
|
6701
|
+
.o-link-tool {
|
|
6702
|
+
font-size: 13px;
|
|
6703
|
+
background-color: white;
|
|
6704
|
+
box-shadow: 0 1px 4px 3px rgba(60, 64, 67, 0.15);
|
|
6705
|
+
padding: 6px 12px;
|
|
6706
|
+
border-radius: 4px;
|
|
6707
|
+
display: flex;
|
|
6708
|
+
justify-content: space-between;
|
|
6709
|
+
height: ${LINK_TOOLTIP_HEIGHT}px;
|
|
6710
|
+
width: ${LINK_TOOLTIP_WIDTH}px;
|
|
6711
|
+
box-sizing: border-box !important;
|
|
6712
|
+
|
|
6713
|
+
img {
|
|
6714
|
+
margin-right: 3px;
|
|
6715
|
+
width: 16px;
|
|
6716
|
+
height: 16px;
|
|
6717
|
+
}
|
|
6718
|
+
|
|
6719
|
+
a.o-link {
|
|
6720
|
+
color: #01666b;
|
|
6721
|
+
text-decoration: none;
|
|
6722
|
+
flex-grow: 2;
|
|
6723
|
+
white-space: nowrap;
|
|
6724
|
+
overflow: hidden;
|
|
6725
|
+
text-overflow: ellipsis;
|
|
6726
|
+
}
|
|
6727
|
+
a.o-link:hover {
|
|
6728
|
+
text-decoration: none;
|
|
6729
|
+
color: #001d1f;
|
|
6730
|
+
cursor: pointer;
|
|
6731
|
+
}
|
|
6732
|
+
}
|
|
6733
|
+
.o-link-icon {
|
|
6734
|
+
float: right;
|
|
6735
|
+
padding-left: 5px;
|
|
6736
|
+
.o-icon {
|
|
6737
|
+
height: 16px;
|
|
6738
|
+
}
|
|
6739
|
+
}
|
|
6740
|
+
.o-link-icon .o-icon {
|
|
6741
|
+
height: 13px;
|
|
6742
|
+
}
|
|
6743
|
+
.o-link-icon:hover {
|
|
6744
|
+
cursor: pointer;
|
|
6745
|
+
color: #000;
|
|
6746
|
+
}
|
|
6747
|
+
`;
|
|
6748
|
+
class LinkDisplay extends Component {
|
|
6749
|
+
static template = "o-spreadsheet-LinkDisplay";
|
|
6750
|
+
static props = {
|
|
6751
|
+
cellPosition: Object,
|
|
6752
|
+
onClosed: { type: Function, optional: true },
|
|
6753
|
+
};
|
|
6754
|
+
get cell() {
|
|
6755
|
+
const { col, row } = this.props.cellPosition;
|
|
6756
|
+
const sheetId = this.env.model.getters.getActiveSheetId();
|
|
6757
|
+
return this.env.model.getters.getEvaluatedCell({ sheetId, col, row });
|
|
6758
|
+
}
|
|
6759
|
+
get link() {
|
|
6760
|
+
if (this.cell.link) {
|
|
6761
|
+
return this.cell.link;
|
|
6762
|
+
}
|
|
6763
|
+
const { col, row } = this.props.cellPosition;
|
|
6764
|
+
throw new Error(`LinkDisplay Component can only be used with link cells. ${toXC(col, row)} is not a link.`);
|
|
6765
|
+
}
|
|
6766
|
+
getUrlRepresentation(link) {
|
|
6767
|
+
return urlRepresentation(link, this.env.model.getters);
|
|
6768
|
+
}
|
|
6769
|
+
openLink() {
|
|
6770
|
+
openLink(this.link, this.env);
|
|
6771
|
+
}
|
|
6772
|
+
edit() {
|
|
6773
|
+
const { col, row } = this.props.cellPosition;
|
|
6774
|
+
this.env.model.dispatch("OPEN_CELL_POPOVER", {
|
|
6775
|
+
col,
|
|
6776
|
+
row,
|
|
6777
|
+
popoverType: "LinkEditor",
|
|
6778
|
+
});
|
|
6779
|
+
}
|
|
6780
|
+
unlink() {
|
|
6781
|
+
const sheetId = this.env.model.getters.getActiveSheetId();
|
|
6782
|
+
const { col, row } = this.props.cellPosition;
|
|
6783
|
+
const style = this.env.model.getters.getCellComputedStyle({ sheetId, col, row });
|
|
6784
|
+
const textColor = style?.textColor === LINK_COLOR ? undefined : style?.textColor;
|
|
6785
|
+
this.env.model.dispatch("UPDATE_CELL", {
|
|
6786
|
+
col,
|
|
6787
|
+
row,
|
|
6788
|
+
sheetId,
|
|
6789
|
+
content: this.link.label,
|
|
6790
|
+
style: { ...style, textColor, underline: undefined },
|
|
6791
|
+
});
|
|
6792
|
+
}
|
|
6793
|
+
}
|
|
6794
|
+
const LinkCellPopoverBuilder = {
|
|
6795
|
+
onHover: (position, getters) => {
|
|
6796
|
+
const cell = getters.getEvaluatedCell(position);
|
|
6797
|
+
const shouldDisplayLink = !getters.isDashboard() && cell.link && getters.isVisibleInViewport(position);
|
|
6798
|
+
if (!shouldDisplayLink)
|
|
6799
|
+
return { isOpen: false };
|
|
6800
|
+
return {
|
|
6801
|
+
isOpen: true,
|
|
6802
|
+
Component: LinkDisplay,
|
|
6803
|
+
props: { cellPosition: position },
|
|
6804
|
+
cellCorner: "BottomLeft",
|
|
6805
|
+
};
|
|
6806
|
+
},
|
|
6807
|
+
};
|
|
6808
|
+
|
|
6695
6809
|
/**
|
|
6696
|
-
*
|
|
6697
|
-
*
|
|
6810
|
+
* Tokenizer
|
|
6811
|
+
*
|
|
6812
|
+
* A tokenizer is a piece of code whose job is to transform a string into a list
|
|
6813
|
+
* of "tokens". For example, "(12+" is converted into:
|
|
6814
|
+
* [{type: "LEFT_PAREN", value: "("},
|
|
6815
|
+
* {type: "NUMBER", value: "12"},
|
|
6816
|
+
* {type: "OPERATOR", value: "+"}]
|
|
6817
|
+
*
|
|
6818
|
+
* As the example shows, a tokenizer does not care about the meaning behind those
|
|
6819
|
+
* tokens. It only cares about the structure.
|
|
6820
|
+
*
|
|
6821
|
+
* The tokenizer is usually the first step in a compilation pipeline. Also, it
|
|
6822
|
+
* is useful for the composer, which needs to be able to work with incomplete
|
|
6823
|
+
* formulas.
|
|
6698
6824
|
*/
|
|
6699
|
-
|
|
6700
|
-
|
|
6825
|
+
const POSTFIX_UNARY_OPERATORS = ["%"];
|
|
6826
|
+
const OPERATORS = "+,-,*,/,:,=,<>,>=,>,<=,<,^,&".split(",").concat(POSTFIX_UNARY_OPERATORS);
|
|
6827
|
+
function tokenize(str, locale = DEFAULT_LOCALE) {
|
|
6828
|
+
str = replaceSpecialSpaces(str);
|
|
6829
|
+
const chars = new TokenizingChars(str);
|
|
6830
|
+
const result = [];
|
|
6831
|
+
while (!chars.isOver()) {
|
|
6832
|
+
let token = tokenizeSpace(chars) ||
|
|
6833
|
+
tokenizeArgsSeparator(chars, locale) ||
|
|
6834
|
+
tokenizeMisc(chars) ||
|
|
6835
|
+
tokenizeOperator(chars) ||
|
|
6836
|
+
tokenizeString(chars) ||
|
|
6837
|
+
tokenizeDebugger(chars) ||
|
|
6838
|
+
tokenizeInvalidRange(chars) ||
|
|
6839
|
+
tokenizeNumber(chars, locale) ||
|
|
6840
|
+
tokenizeSymbol(chars);
|
|
6841
|
+
if (!token) {
|
|
6842
|
+
token = { type: "UNKNOWN", value: chars.shift() };
|
|
6843
|
+
}
|
|
6844
|
+
result.push(token);
|
|
6845
|
+
}
|
|
6846
|
+
return result;
|
|
6701
6847
|
}
|
|
6702
|
-
function
|
|
6703
|
-
|
|
6704
|
-
|
|
6705
|
-
|
|
6706
|
-
return { top, left };
|
|
6848
|
+
function tokenizeDebugger(chars) {
|
|
6849
|
+
if (chars.current === "?") {
|
|
6850
|
+
chars.shift();
|
|
6851
|
+
return { type: "DEBUGGER", value: "?" };
|
|
6707
6852
|
}
|
|
6708
|
-
|
|
6853
|
+
return null;
|
|
6709
6854
|
}
|
|
6710
|
-
|
|
6711
|
-
|
|
6712
|
-
|
|
6713
|
-
|
|
6714
|
-
|
|
6715
|
-
|
|
6716
|
-
|
|
6717
|
-
|
|
6855
|
+
const misc$1 = {
|
|
6856
|
+
"(": "LEFT_PAREN",
|
|
6857
|
+
")": "RIGHT_PAREN",
|
|
6858
|
+
};
|
|
6859
|
+
function tokenizeMisc(chars) {
|
|
6860
|
+
if (chars.current in misc$1) {
|
|
6861
|
+
const value = chars.shift();
|
|
6862
|
+
const type = misc$1[value];
|
|
6863
|
+
return { type, value };
|
|
6864
|
+
}
|
|
6865
|
+
return null;
|
|
6718
6866
|
}
|
|
6719
|
-
|
|
6720
|
-
|
|
6721
|
-
|
|
6722
|
-
|
|
6723
|
-
|
|
6724
|
-
|
|
6725
|
-
|
|
6726
|
-
|
|
6867
|
+
function tokenizeArgsSeparator(chars, locale) {
|
|
6868
|
+
if (chars.current === locale.formulaArgSeparator) {
|
|
6869
|
+
const value = chars.shift();
|
|
6870
|
+
const type = "ARG_SEPARATOR";
|
|
6871
|
+
return { type, value };
|
|
6872
|
+
}
|
|
6873
|
+
return null;
|
|
6874
|
+
}
|
|
6875
|
+
function tokenizeOperator(chars) {
|
|
6876
|
+
for (let op of OPERATORS) {
|
|
6877
|
+
if (chars.currentStartsWith(op)) {
|
|
6878
|
+
chars.advanceBy(op.length);
|
|
6879
|
+
return { type: "OPERATOR", value: op };
|
|
6727
6880
|
}
|
|
6728
6881
|
}
|
|
6882
|
+
return null;
|
|
6729
6883
|
}
|
|
6730
|
-
function
|
|
6731
|
-
|
|
6884
|
+
function tokenizeNumber(chars, locale) {
|
|
6885
|
+
const match = chars.remaining().match(getFormulaNumberRegex(locale.decimalSeparator));
|
|
6886
|
+
if (match) {
|
|
6887
|
+
chars.advanceBy(match[0].length);
|
|
6888
|
+
return { type: "NUMBER", value: match[0] };
|
|
6889
|
+
}
|
|
6890
|
+
return null;
|
|
6732
6891
|
}
|
|
6733
|
-
|
|
6892
|
+
function tokenizeString(chars) {
|
|
6893
|
+
if (chars.current === '"') {
|
|
6894
|
+
const startChar = chars.shift();
|
|
6895
|
+
let letters = startChar;
|
|
6896
|
+
while (chars.current && (chars.current !== startChar || letters[letters.length - 1] === "\\")) {
|
|
6897
|
+
letters += chars.shift();
|
|
6898
|
+
}
|
|
6899
|
+
if (chars.current === '"') {
|
|
6900
|
+
letters += chars.shift();
|
|
6901
|
+
}
|
|
6902
|
+
return {
|
|
6903
|
+
type: "STRING",
|
|
6904
|
+
value: letters,
|
|
6905
|
+
};
|
|
6906
|
+
}
|
|
6907
|
+
return null;
|
|
6908
|
+
}
|
|
6909
|
+
const separatorRegexp = /\w|\.|!|\$/;
|
|
6734
6910
|
/**
|
|
6735
|
-
*
|
|
6736
|
-
*
|
|
6737
|
-
*
|
|
6738
|
-
*
|
|
6911
|
+
* A "Symbol" is just basically any word-like element that can appear in a
|
|
6912
|
+
* formula, which is not a string. So:
|
|
6913
|
+
* A1
|
|
6914
|
+
* SUM
|
|
6915
|
+
* CEILING.MATH
|
|
6916
|
+
* A$1
|
|
6917
|
+
* Sheet2!A2
|
|
6918
|
+
* 'Sheet 2'!A2
|
|
6739
6919
|
*
|
|
6740
|
-
*
|
|
6741
|
-
* event : { ctrlKey: true, key: "a" } => "Ctrl+A"
|
|
6742
|
-
* event : { shift: true, alt: true, key: "Home" } => "Alt+Shift+Home"
|
|
6920
|
+
* are examples of symbols
|
|
6743
6921
|
*/
|
|
6744
|
-
function
|
|
6745
|
-
let
|
|
6746
|
-
|
|
6747
|
-
|
|
6748
|
-
if (
|
|
6749
|
-
|
|
6750
|
-
|
|
6751
|
-
|
|
6752
|
-
|
|
6753
|
-
|
|
6754
|
-
|
|
6755
|
-
|
|
6756
|
-
|
|
6922
|
+
function tokenizeSymbol(chars) {
|
|
6923
|
+
let result = "";
|
|
6924
|
+
// there are two main cases to manage: either something which starts with
|
|
6925
|
+
// a ', like 'Sheet 2'A2, or a word-like element.
|
|
6926
|
+
if (chars.current === "'") {
|
|
6927
|
+
let lastChar = chars.shift();
|
|
6928
|
+
result += lastChar;
|
|
6929
|
+
while (chars.current) {
|
|
6930
|
+
lastChar = chars.shift();
|
|
6931
|
+
result += lastChar;
|
|
6932
|
+
if (lastChar === "'") {
|
|
6933
|
+
if (chars.current && chars.current === "'") {
|
|
6934
|
+
lastChar = chars.shift();
|
|
6935
|
+
result += lastChar;
|
|
6936
|
+
}
|
|
6937
|
+
else {
|
|
6938
|
+
break;
|
|
6939
|
+
}
|
|
6940
|
+
}
|
|
6941
|
+
}
|
|
6942
|
+
if (lastChar !== "'") {
|
|
6943
|
+
return {
|
|
6944
|
+
type: "UNKNOWN",
|
|
6945
|
+
value: result,
|
|
6946
|
+
};
|
|
6947
|
+
}
|
|
6948
|
+
}
|
|
6949
|
+
while (chars.current && separatorRegexp.test(chars.current)) {
|
|
6950
|
+
result += chars.shift();
|
|
6951
|
+
}
|
|
6952
|
+
if (result.length) {
|
|
6953
|
+
const value = result;
|
|
6954
|
+
const isReference = rangeReference.test(value);
|
|
6955
|
+
if (isReference) {
|
|
6956
|
+
return { type: "REFERENCE", value };
|
|
6957
|
+
}
|
|
6958
|
+
return { type: "SYMBOL", value };
|
|
6959
|
+
}
|
|
6960
|
+
return null;
|
|
6757
6961
|
}
|
|
6758
|
-
function
|
|
6759
|
-
|
|
6962
|
+
function tokenizeSpace(chars) {
|
|
6963
|
+
let length = 0;
|
|
6964
|
+
while (chars.current === NEWLINE) {
|
|
6965
|
+
length++;
|
|
6966
|
+
chars.shift();
|
|
6967
|
+
}
|
|
6968
|
+
if (length) {
|
|
6969
|
+
return { type: "SPACE", value: NEWLINE.repeat(length) };
|
|
6970
|
+
}
|
|
6971
|
+
while (chars.current === " ") {
|
|
6972
|
+
length++;
|
|
6973
|
+
chars.shift();
|
|
6974
|
+
}
|
|
6975
|
+
if (length) {
|
|
6976
|
+
return { type: "SPACE", value: " ".repeat(length) };
|
|
6977
|
+
}
|
|
6978
|
+
return null;
|
|
6760
6979
|
}
|
|
6761
|
-
|
|
6762
|
-
|
|
6763
|
-
|
|
6764
|
-
|
|
6765
|
-
|
|
6766
|
-
|
|
6767
|
-
return isMacOS() ? ev.metaKey : ev.ctrlKey;
|
|
6980
|
+
function tokenizeInvalidRange(chars) {
|
|
6981
|
+
if (chars.currentStartsWith(CellErrorType.InvalidReference)) {
|
|
6982
|
+
chars.advanceBy(CellErrorType.InvalidReference.length);
|
|
6983
|
+
return { type: "INVALID_REFERENCE", value: CellErrorType.InvalidReference };
|
|
6984
|
+
}
|
|
6985
|
+
return null;
|
|
6768
6986
|
}
|
|
6769
|
-
|
|
6770
|
-
|
|
6771
|
-
|
|
6772
|
-
|
|
6773
|
-
|
|
6774
|
-
|
|
6775
|
-
|
|
6776
|
-
|
|
6777
|
-
|
|
6778
|
-
|
|
6779
|
-
|
|
6780
|
-
|
|
6781
|
-
|
|
6782
|
-
|
|
6783
|
-
|
|
6784
|
-
|
|
6785
|
-
|
|
6786
|
-
|
|
6787
|
-
|
|
6987
|
+
class TokenizingChars {
|
|
6988
|
+
text;
|
|
6989
|
+
currentIndex = 0;
|
|
6990
|
+
current;
|
|
6991
|
+
constructor(text) {
|
|
6992
|
+
this.text = text;
|
|
6993
|
+
this.current = text[0];
|
|
6994
|
+
}
|
|
6995
|
+
shift() {
|
|
6996
|
+
const current = this.current;
|
|
6997
|
+
const next = this.text[++this.currentIndex];
|
|
6998
|
+
this.current = next;
|
|
6999
|
+
return current;
|
|
7000
|
+
}
|
|
7001
|
+
advanceBy(length) {
|
|
7002
|
+
this.currentIndex += length;
|
|
7003
|
+
this.current = this.text[this.currentIndex];
|
|
7004
|
+
}
|
|
7005
|
+
isOver() {
|
|
7006
|
+
return this.currentIndex >= this.text.length;
|
|
7007
|
+
}
|
|
7008
|
+
remaining() {
|
|
7009
|
+
return this.text.substring(this.currentIndex);
|
|
7010
|
+
}
|
|
7011
|
+
currentStartsWith(str) {
|
|
7012
|
+
for (let j = 0; j < str.length; j++) {
|
|
7013
|
+
if (this.text[this.currentIndex + j] !== str[j]) {
|
|
7014
|
+
return false;
|
|
7015
|
+
}
|
|
6788
7016
|
}
|
|
7017
|
+
return true;
|
|
6789
7018
|
}
|
|
6790
|
-
|
|
6791
|
-
|
|
6792
|
-
|
|
7019
|
+
}
|
|
7020
|
+
|
|
7021
|
+
function isValidLocale(locale) {
|
|
7022
|
+
if (!(locale &&
|
|
7023
|
+
typeof locale === "object" &&
|
|
7024
|
+
typeof locale.name === "string" &&
|
|
7025
|
+
typeof locale.code === "string" &&
|
|
7026
|
+
typeof locale.thousandsSeparator === "string" &&
|
|
7027
|
+
typeof locale.decimalSeparator === "string" &&
|
|
7028
|
+
typeof locale.dateFormat === "string" &&
|
|
7029
|
+
typeof locale.timeFormat === "string" &&
|
|
7030
|
+
typeof locale.formulaArgSeparator === "string")) {
|
|
7031
|
+
return false;
|
|
7032
|
+
}
|
|
7033
|
+
if (!Object.values(locale).every((v) => v)) {
|
|
7034
|
+
return false;
|
|
7035
|
+
}
|
|
7036
|
+
if (locale.formulaArgSeparator === locale.decimalSeparator) {
|
|
7037
|
+
return false;
|
|
7038
|
+
}
|
|
7039
|
+
try {
|
|
7040
|
+
formatValue(1, { locale, format: "#,##0.00" });
|
|
7041
|
+
formatValue(1, { locale, format: locale.dateFormat });
|
|
7042
|
+
formatValue(1, { locale, format: locale.timeFormat });
|
|
7043
|
+
}
|
|
7044
|
+
catch {
|
|
7045
|
+
return false;
|
|
7046
|
+
}
|
|
7047
|
+
return true;
|
|
6793
7048
|
}
|
|
6794
7049
|
/**
|
|
6795
|
-
*
|
|
6796
|
-
* to the upper left corner of the screen (<body> element).
|
|
7050
|
+
* Change a content string from the given locale to its canonical form (en_US locale). Don't convert date string.
|
|
6797
7051
|
*
|
|
6798
|
-
*
|
|
6799
|
-
*
|
|
7052
|
+
* @example
|
|
7053
|
+
* canonicalizeNumberContent("=SUM(1,5; 02/12/2012)", FR_LOCALE) // "=SUM(1.5, 02/12/2012)"
|
|
7054
|
+
* canonicalizeNumberContent("125,9", FR_LOCALE) // "125.9"
|
|
7055
|
+
* canonicalizeNumberContent("02/12/2012", FR_LOCALE) // "02/12/2012"
|
|
6800
7056
|
*/
|
|
6801
|
-
function
|
|
6802
|
-
|
|
6803
|
-
|
|
6804
|
-
|
|
6805
|
-
if (el === null) {
|
|
6806
|
-
return;
|
|
6807
|
-
}
|
|
6808
|
-
const { top, left, width, height } = el.getBoundingClientRect();
|
|
6809
|
-
rect.x = left;
|
|
6810
|
-
rect.y = top;
|
|
6811
|
-
rect.width = width;
|
|
6812
|
-
rect.height = height;
|
|
6813
|
-
}
|
|
6814
|
-
onMounted(updateElRect);
|
|
6815
|
-
onPatched(updateElRect);
|
|
6816
|
-
return rect;
|
|
7057
|
+
function canonicalizeNumberContent(content, locale) {
|
|
7058
|
+
return content.startsWith("=")
|
|
7059
|
+
? canonicalizeFormula$1(content, locale)
|
|
7060
|
+
: canonicalizeNumberLiteral(content, locale);
|
|
6817
7061
|
}
|
|
6818
7062
|
/**
|
|
6819
|
-
*
|
|
6820
|
-
*
|
|
6821
|
-
* element by default.
|
|
7063
|
+
* Change a content string from the given locale to its canonical form (en_US locale). Also convert date string.
|
|
7064
|
+
* This is destructive and won't preserve the original format.
|
|
6822
7065
|
*
|
|
6823
|
-
*
|
|
7066
|
+
* @example
|
|
7067
|
+
* canonicalizeContent("=SUM(1,5; 5)", FR_LOCALE) // "=SUM(1.5, 5)"
|
|
7068
|
+
* canonicalizeContent("125,9", FR_LOCALE) // "125.9"
|
|
7069
|
+
* canonicalizeContent("02/12/2012", FR_LOCALE) // "12/02/2012"
|
|
7070
|
+
* canonicalizeContent("02-12-2012", FR_LOCALE) // "12/02/2012"
|
|
6824
7071
|
*/
|
|
6825
|
-
function
|
|
6826
|
-
|
|
6827
|
-
|
|
6828
|
-
|
|
6829
|
-
function updateRect() {
|
|
6830
|
-
const env = component.env;
|
|
6831
|
-
const newRect = "getPopoverContainerRect" in env ? env.getPopoverContainerRect() : spreadsheetRect;
|
|
6832
|
-
container.x = newRect.x;
|
|
6833
|
-
container.y = newRect.y;
|
|
6834
|
-
container.width = newRect.width;
|
|
6835
|
-
container.height = newRect.height;
|
|
6836
|
-
}
|
|
6837
|
-
updateRect();
|
|
6838
|
-
onMounted(updateRect);
|
|
6839
|
-
onPatched(updateRect);
|
|
6840
|
-
return container;
|
|
7072
|
+
function canonicalizeContent(content, locale) {
|
|
7073
|
+
return content.startsWith("=")
|
|
7074
|
+
? canonicalizeFormula$1(content, locale)
|
|
7075
|
+
: canonicalizeLiteral(content, locale);
|
|
6841
7076
|
}
|
|
6842
|
-
|
|
6843
7077
|
/**
|
|
6844
|
-
*
|
|
7078
|
+
* Change a content string from its canonical form (en_US locale) to the given locale. Also convert date string.
|
|
7079
|
+
*
|
|
7080
|
+
* @example
|
|
7081
|
+
* localizeContent("=SUM(1.5, 5)", FR_LOCALE) // "=SUM(1,5; 5)"
|
|
7082
|
+
* localizeContent("125.9", FR_LOCALE) // "125,9"
|
|
7083
|
+
* localizeContent("12/02/2012", FR_LOCALE) // "02/12/2012"
|
|
6845
7084
|
*/
|
|
6846
|
-
function
|
|
6847
|
-
return
|
|
7085
|
+
function localizeContent(content, locale) {
|
|
7086
|
+
return content.startsWith("=")
|
|
7087
|
+
? localizeFormula(content, locale)
|
|
7088
|
+
: localizeLiteral(content, locale);
|
|
6848
7089
|
}
|
|
6849
|
-
/**
|
|
6850
|
-
function
|
|
6851
|
-
return
|
|
7090
|
+
/** Change a formula to its canonical form (en_US locale) */
|
|
7091
|
+
function canonicalizeFormula$1(formula, locale) {
|
|
7092
|
+
return _localizeFormula$1(formula, locale, DEFAULT_LOCALE);
|
|
6852
7093
|
}
|
|
6853
|
-
|
|
7094
|
+
/** Change a formula from the canonical form to the given locale */
|
|
7095
|
+
function localizeFormula(formula, locale) {
|
|
7096
|
+
return _localizeFormula$1(formula, DEFAULT_LOCALE, locale);
|
|
7097
|
+
}
|
|
7098
|
+
function _localizeFormula$1(formula, fromLocale, toLocale) {
|
|
7099
|
+
if (fromLocale.formulaArgSeparator === toLocale.formulaArgSeparator &&
|
|
7100
|
+
fromLocale.decimalSeparator === toLocale.decimalSeparator) {
|
|
7101
|
+
return formula;
|
|
7102
|
+
}
|
|
7103
|
+
const tokens = tokenize(formula, fromLocale);
|
|
7104
|
+
let localizedFormula = "";
|
|
7105
|
+
for (const token of tokens) {
|
|
7106
|
+
if (token.type === "NUMBER") {
|
|
7107
|
+
localizedFormula += token.value.replace(fromLocale.decimalSeparator, toLocale.decimalSeparator);
|
|
7108
|
+
}
|
|
7109
|
+
else if (token.type === "ARG_SEPARATOR") {
|
|
7110
|
+
localizedFormula += toLocale.formulaArgSeparator;
|
|
7111
|
+
}
|
|
7112
|
+
else {
|
|
7113
|
+
localizedFormula += token.value;
|
|
7114
|
+
}
|
|
7115
|
+
}
|
|
7116
|
+
return localizedFormula;
|
|
7117
|
+
}
|
|
7118
|
+
/**
|
|
7119
|
+
* Change a literal string from the given locale to its canonical form (en_US locale). Don't convert date string.
|
|
7120
|
+
*
|
|
7121
|
+
* @example
|
|
7122
|
+
* canonicalizeNumberLiteral("125,9", FR_LOCALE) // "125.9"
|
|
7123
|
+
* canonicalizeNumberLiteral("02/12/2012", FR_LOCALE) // "02/12/2012"
|
|
7124
|
+
*/
|
|
7125
|
+
function canonicalizeNumberLiteral(content, locale) {
|
|
7126
|
+
if (locale.decimalSeparator === "." || !isNumber(content, locale)) {
|
|
7127
|
+
return content;
|
|
7128
|
+
}
|
|
7129
|
+
return content.replace(locale.decimalSeparator, ".");
|
|
7130
|
+
}
|
|
7131
|
+
/**
|
|
7132
|
+
* Change a content string from the given locale to its canonical form (en_US locale). Also convert date string.
|
|
7133
|
+
* This is destructive and won't preserve the original format.
|
|
7134
|
+
*
|
|
7135
|
+
* @example
|
|
7136
|
+
* canonicalizeLiteral("125,9", FR_LOCALE) // "125.9"
|
|
7137
|
+
* canonicalizeLiteral("02/12/2012", FR_LOCALE) // "12/02/2012"
|
|
7138
|
+
* canonicalizeLiteral("02-12-2012", FR_LOCALE) // "12/02/2012"
|
|
7139
|
+
*/
|
|
7140
|
+
function canonicalizeLiteral(content, locale) {
|
|
7141
|
+
if (isDateTime(content, locale)) {
|
|
7142
|
+
const dateNumber = toNumber(content, locale);
|
|
7143
|
+
let format = DEFAULT_LOCALE.dateFormat;
|
|
7144
|
+
if (!Number.isInteger(dateNumber)) {
|
|
7145
|
+
format += " " + DEFAULT_LOCALE.timeFormat;
|
|
7146
|
+
}
|
|
7147
|
+
return formatValue(dateNumber, { locale: DEFAULT_LOCALE, format });
|
|
7148
|
+
}
|
|
7149
|
+
return canonicalizeNumberLiteral(content, locale);
|
|
7150
|
+
}
|
|
7151
|
+
/**
|
|
7152
|
+
* Change a literal string from its canonical form (en_US locale) to the given locale. Don't convert date string.
|
|
7153
|
+
* This is destructive and won't preserve the original format.
|
|
7154
|
+
*
|
|
7155
|
+
* @example
|
|
7156
|
+
* localizeNumberLiteral("125.9", FR_LOCALE) // "125,9"
|
|
7157
|
+
* localizeNumberLiteral("12/02/2012", FR_LOCALE) // "12/02/2012"
|
|
7158
|
+
* localizeNumberLiteral("12-02-2012", FR_LOCALE) // "12/02/2012"
|
|
7159
|
+
*/
|
|
7160
|
+
function localizeNumberLiteral(literal, locale) {
|
|
7161
|
+
if (locale.decimalSeparator === "." || !isNumber(literal, DEFAULT_LOCALE)) {
|
|
7162
|
+
return literal;
|
|
7163
|
+
}
|
|
7164
|
+
const decimalNumberRegex = getDecimalNumberRegex(DEFAULT_LOCALE);
|
|
7165
|
+
const localized = literal.replace(decimalNumberRegex, (match) => {
|
|
7166
|
+
return match.replace(".", locale.decimalSeparator);
|
|
7167
|
+
});
|
|
7168
|
+
return localized;
|
|
7169
|
+
}
|
|
7170
|
+
/**
|
|
7171
|
+
* Change a literal string from its canonical form (en_US locale) to the given locale. Also convert date string.
|
|
7172
|
+
*
|
|
7173
|
+
* @example
|
|
7174
|
+
* localizeLiteral("125.9", FR_LOCALE) // "125,9"
|
|
7175
|
+
* localizeLiteral("12/02/2012", FR_LOCALE) // "02/12/2012"
|
|
7176
|
+
*/
|
|
7177
|
+
function localizeLiteral(literal, locale) {
|
|
7178
|
+
if (isDateTime(literal, DEFAULT_LOCALE)) {
|
|
7179
|
+
const dateNumber = toNumber(literal, DEFAULT_LOCALE);
|
|
7180
|
+
let format = locale.dateFormat;
|
|
7181
|
+
if (!Number.isInteger(dateNumber)) {
|
|
7182
|
+
format += " " + locale.timeFormat;
|
|
7183
|
+
}
|
|
7184
|
+
return formatValue(dateNumber, { locale, format });
|
|
7185
|
+
}
|
|
7186
|
+
return localizeNumberLiteral(literal, locale);
|
|
7187
|
+
}
|
|
7188
|
+
function canonicalizeCFRule(cf, locale) {
|
|
7189
|
+
return changeCFRuleLocale(cf, (content) => canonicalizeContent(content, locale));
|
|
7190
|
+
}
|
|
7191
|
+
function localizeCFRule(cf, locale) {
|
|
7192
|
+
return changeCFRuleLocale(cf, (content) => localizeContent(content, locale));
|
|
7193
|
+
}
|
|
7194
|
+
function localizeDataValidationRule(rule, locale) {
|
|
7195
|
+
const localizedDVRule = deepCopy(rule);
|
|
7196
|
+
localizedDVRule.criterion.values = localizedDVRule.criterion.values.map((content) => localizeContent(content, locale));
|
|
7197
|
+
return localizedDVRule;
|
|
7198
|
+
}
|
|
7199
|
+
function changeCFRuleLocale(rule, changeContentLocale) {
|
|
7200
|
+
rule = deepCopy(rule);
|
|
7201
|
+
switch (rule.type) {
|
|
7202
|
+
case "CellIsRule":
|
|
7203
|
+
// Only change value for number operators
|
|
7204
|
+
switch (rule.operator) {
|
|
7205
|
+
case "Between":
|
|
7206
|
+
case "NotBetween":
|
|
7207
|
+
case "Equal":
|
|
7208
|
+
case "NotEqual":
|
|
7209
|
+
case "GreaterThan":
|
|
7210
|
+
case "GreaterThanOrEqual":
|
|
7211
|
+
case "LessThan":
|
|
7212
|
+
case "LessThanOrEqual":
|
|
7213
|
+
rule.values = rule.values.map((v) => changeContentLocale(v));
|
|
7214
|
+
return rule;
|
|
7215
|
+
case "BeginsWith":
|
|
7216
|
+
case "ContainsText":
|
|
7217
|
+
case "EndsWith":
|
|
7218
|
+
case "NotContains":
|
|
7219
|
+
case "IsEmpty":
|
|
7220
|
+
case "IsNotEmpty":
|
|
7221
|
+
return rule;
|
|
7222
|
+
}
|
|
7223
|
+
break;
|
|
7224
|
+
case "ColorScaleRule":
|
|
7225
|
+
rule.minimum = changeCFRuleThresholdLocale(rule.minimum, changeContentLocale);
|
|
7226
|
+
rule.maximum = changeCFRuleThresholdLocale(rule.maximum, changeContentLocale);
|
|
7227
|
+
if (rule.midpoint) {
|
|
7228
|
+
rule.midpoint = changeCFRuleThresholdLocale(rule.midpoint, changeContentLocale);
|
|
7229
|
+
}
|
|
7230
|
+
return rule;
|
|
7231
|
+
case "IconSetRule":
|
|
7232
|
+
rule.lowerInflectionPoint.value = changeContentLocale(rule.lowerInflectionPoint.value);
|
|
7233
|
+
rule.upperInflectionPoint.value = changeContentLocale(rule.upperInflectionPoint.value);
|
|
7234
|
+
return rule;
|
|
7235
|
+
}
|
|
7236
|
+
}
|
|
7237
|
+
function changeCFRuleThresholdLocale(threshold, changeContentLocale) {
|
|
7238
|
+
if (!threshold?.value) {
|
|
7239
|
+
return threshold;
|
|
7240
|
+
}
|
|
7241
|
+
const value = threshold.type === "formula" ? "=" + threshold.value : threshold.value;
|
|
7242
|
+
const modified = changeContentLocale(value);
|
|
7243
|
+
const newValue = threshold.type === "formula" ? modified.slice(1) : modified;
|
|
7244
|
+
return { ...threshold, value: newValue };
|
|
7245
|
+
}
|
|
7246
|
+
function getDateTimeFormat(locale) {
|
|
7247
|
+
return locale.dateFormat + " " + locale.timeFormat;
|
|
7248
|
+
}
|
|
7249
|
+
|
|
7250
|
+
const linkSheet = {
|
|
7251
|
+
name: _t("Link sheet"),
|
|
7252
|
+
children: [
|
|
7253
|
+
(env) => {
|
|
7254
|
+
const sheets = env.model.getters
|
|
7255
|
+
.getSheetIds()
|
|
7256
|
+
.map((sheetId) => env.model.getters.getSheet(sheetId));
|
|
7257
|
+
return sheets.map((sheet) => ({
|
|
7258
|
+
id: sheet.id,
|
|
7259
|
+
name: sheet.name,
|
|
7260
|
+
execute: () => markdownLink(sheet.name, buildSheetLink(sheet.id)),
|
|
7261
|
+
}));
|
|
7262
|
+
},
|
|
7263
|
+
],
|
|
7264
|
+
};
|
|
7265
|
+
const deleteSheet = {
|
|
7266
|
+
name: _t("Delete"),
|
|
7267
|
+
isVisible: (env) => {
|
|
7268
|
+
return env.model.getters.getSheetIds().length > 1;
|
|
7269
|
+
},
|
|
7270
|
+
execute: (env) => env.askConfirmation(_t("Are you sure you want to delete this sheet?"), () => {
|
|
7271
|
+
env.model.dispatch("DELETE_SHEET", { sheetId: env.model.getters.getActiveSheetId() });
|
|
7272
|
+
}),
|
|
7273
|
+
};
|
|
7274
|
+
const duplicateSheet = {
|
|
7275
|
+
name: _t("Duplicate"),
|
|
7276
|
+
execute: (env) => {
|
|
7277
|
+
const sheetIdFrom = env.model.getters.getActiveSheetId();
|
|
7278
|
+
const sheetIdTo = env.model.uuidGenerator.uuidv4();
|
|
7279
|
+
env.model.dispatch("DUPLICATE_SHEET", {
|
|
7280
|
+
sheetId: sheetIdFrom,
|
|
7281
|
+
sheetIdTo,
|
|
7282
|
+
});
|
|
7283
|
+
env.model.dispatch("ACTIVATE_SHEET", { sheetIdFrom, sheetIdTo });
|
|
7284
|
+
},
|
|
7285
|
+
};
|
|
7286
|
+
const renameSheet = (args) => {
|
|
7287
|
+
return {
|
|
7288
|
+
name: _t("Rename"),
|
|
7289
|
+
execute: args.renameSheetCallback,
|
|
7290
|
+
};
|
|
7291
|
+
};
|
|
7292
|
+
const sheetMoveRight = {
|
|
7293
|
+
name: _t("Move right"),
|
|
7294
|
+
isVisible: (env) => {
|
|
7295
|
+
const sheetId = env.model.getters.getActiveSheetId();
|
|
7296
|
+
const sheetIds = env.model.getters.getVisibleSheetIds();
|
|
7297
|
+
return sheetIds.indexOf(sheetId) !== sheetIds.length - 1;
|
|
7298
|
+
},
|
|
7299
|
+
execute: (env) => env.model.dispatch("MOVE_SHEET", {
|
|
7300
|
+
sheetId: env.model.getters.getActiveSheetId(),
|
|
7301
|
+
delta: 1,
|
|
7302
|
+
}),
|
|
7303
|
+
};
|
|
7304
|
+
const sheetMoveLeft = {
|
|
7305
|
+
name: _t("Move left"),
|
|
7306
|
+
isVisible: (env) => {
|
|
7307
|
+
const sheetId = env.model.getters.getActiveSheetId();
|
|
7308
|
+
return env.model.getters.getVisibleSheetIds()[0] !== sheetId;
|
|
7309
|
+
},
|
|
7310
|
+
execute: (env) => env.model.dispatch("MOVE_SHEET", {
|
|
7311
|
+
sheetId: env.model.getters.getActiveSheetId(),
|
|
7312
|
+
delta: -1,
|
|
7313
|
+
}),
|
|
7314
|
+
};
|
|
7315
|
+
const hideSheet = {
|
|
7316
|
+
name: _t("Hide sheet"),
|
|
7317
|
+
isVisible: (env) => env.model.getters.getVisibleSheetIds().length !== 1,
|
|
7318
|
+
execute: (env) => env.model.dispatch("HIDE_SHEET", { sheetId: env.model.getters.getActiveSheetId() }),
|
|
7319
|
+
};
|
|
7320
|
+
|
|
7321
|
+
/**
|
|
7322
|
+
* The class Registry is extended in order to add the function addChild
|
|
7323
|
+
*
|
|
7324
|
+
*/
|
|
7325
|
+
class MenuItemRegistry extends Registry {
|
|
7326
|
+
/**
|
|
7327
|
+
* @override
|
|
7328
|
+
*/
|
|
7329
|
+
add(key, value) {
|
|
7330
|
+
if (value.id === undefined) {
|
|
7331
|
+
value.id = key;
|
|
7332
|
+
}
|
|
7333
|
+
this.content[key] = value;
|
|
7334
|
+
return this;
|
|
7335
|
+
}
|
|
7336
|
+
/**
|
|
7337
|
+
* Add a subitem to an existing item
|
|
7338
|
+
* @param path Path of items to add this subitem
|
|
7339
|
+
* @param value Subitem to add
|
|
7340
|
+
*/
|
|
7341
|
+
addChild(key, path, value) {
|
|
7342
|
+
if (typeof value !== "function" && value.id === undefined) {
|
|
7343
|
+
value.id = key;
|
|
7344
|
+
}
|
|
7345
|
+
const root = path.splice(0, 1)[0];
|
|
7346
|
+
let node = this.content[root];
|
|
7347
|
+
if (!node) {
|
|
7348
|
+
throw new Error(`Path ${root + ":" + path.join(":")} not found`);
|
|
7349
|
+
}
|
|
7350
|
+
for (let p of path) {
|
|
7351
|
+
const children = node.children;
|
|
7352
|
+
if (!children || typeof children === "function") {
|
|
7353
|
+
throw new Error(`${p} is either not a node or it's dynamically computed`);
|
|
7354
|
+
}
|
|
7355
|
+
node = children.find((elt) => elt.id === p);
|
|
7356
|
+
if (!node) {
|
|
7357
|
+
throw new Error(`Path ${root + ":" + path.join(":")} not found`);
|
|
7358
|
+
}
|
|
7359
|
+
}
|
|
7360
|
+
if (!node.children) {
|
|
7361
|
+
node.children = [];
|
|
7362
|
+
}
|
|
7363
|
+
node.children.push(value);
|
|
7364
|
+
return this;
|
|
7365
|
+
}
|
|
7366
|
+
getMenuItems() {
|
|
7367
|
+
return createActions(this.getAll());
|
|
7368
|
+
}
|
|
7369
|
+
}
|
|
7370
|
+
|
|
7371
|
+
//------------------------------------------------------------------------------
|
|
7372
|
+
// Link Menu Registry
|
|
7373
|
+
//------------------------------------------------------------------------------
|
|
7374
|
+
const linkMenuRegistry = new MenuItemRegistry();
|
|
7375
|
+
linkMenuRegistry.add("sheet", {
|
|
7376
|
+
...linkSheet,
|
|
7377
|
+
sequence: 10,
|
|
7378
|
+
});
|
|
7379
|
+
|
|
7380
|
+
/**
|
|
7381
|
+
* Return the o-spreadsheet element position relative
|
|
7382
|
+
* to the browser viewport.
|
|
7383
|
+
*/
|
|
7384
|
+
function useSpreadsheetRect() {
|
|
7385
|
+
const position = useState({ x: 0, y: 0, width: 0, height: 0 });
|
|
7386
|
+
let spreadsheetElement = document.querySelector(".o-spreadsheet");
|
|
7387
|
+
updatePosition();
|
|
7388
|
+
function updatePosition() {
|
|
7389
|
+
if (!spreadsheetElement) {
|
|
7390
|
+
spreadsheetElement = document.querySelector(".o-spreadsheet");
|
|
7391
|
+
}
|
|
7392
|
+
if (spreadsheetElement) {
|
|
7393
|
+
const { top, left, width, height } = spreadsheetElement.getBoundingClientRect();
|
|
7394
|
+
position.x = left;
|
|
7395
|
+
position.y = top;
|
|
7396
|
+
position.width = width;
|
|
7397
|
+
position.height = height;
|
|
7398
|
+
}
|
|
7399
|
+
}
|
|
7400
|
+
onMounted(updatePosition);
|
|
7401
|
+
onPatched(updatePosition);
|
|
7402
|
+
return position;
|
|
7403
|
+
}
|
|
7404
|
+
/**
|
|
7405
|
+
* Return the component (or ref's component) BoundingRect, relative
|
|
7406
|
+
* to the upper left corner of the screen (<body> element).
|
|
7407
|
+
*
|
|
7408
|
+
* Note: when used with a <Portal/> component, it will
|
|
7409
|
+
* return the portal position, not the teleported position.
|
|
7410
|
+
*/
|
|
7411
|
+
function useAbsoluteBoundingRect(ref) {
|
|
7412
|
+
const rect = useState({ x: 0, y: 0, width: 0, height: 0 });
|
|
7413
|
+
function updateElRect() {
|
|
7414
|
+
const el = ref.el;
|
|
7415
|
+
if (el === null) {
|
|
7416
|
+
return;
|
|
7417
|
+
}
|
|
7418
|
+
const { top, left, width, height } = el.getBoundingClientRect();
|
|
7419
|
+
rect.x = left;
|
|
7420
|
+
rect.y = top;
|
|
7421
|
+
rect.width = width;
|
|
7422
|
+
rect.height = height;
|
|
7423
|
+
}
|
|
7424
|
+
onMounted(updateElRect);
|
|
7425
|
+
onPatched(updateElRect);
|
|
7426
|
+
return rect;
|
|
7427
|
+
}
|
|
7428
|
+
/**
|
|
7429
|
+
* Get the rectangle inside which a popover should stay when being displayed.
|
|
7430
|
+
* It's the value defined in `env.getPopoverContainerRect`, or the Rect of the "o-spreadsheet"
|
|
7431
|
+
* element by default.
|
|
7432
|
+
*
|
|
7433
|
+
* Coordinates are expressed expressed as absolute DOM position.
|
|
7434
|
+
*/
|
|
7435
|
+
function usePopoverContainer() {
|
|
7436
|
+
const container = useState({ x: 0, y: 0, width: 0, height: 0 });
|
|
7437
|
+
const component = useComponent();
|
|
7438
|
+
const spreadsheetRect = useSpreadsheetRect();
|
|
7439
|
+
function updateRect() {
|
|
7440
|
+
const env = component.env;
|
|
7441
|
+
const newRect = "getPopoverContainerRect" in env ? env.getPopoverContainerRect() : spreadsheetRect;
|
|
7442
|
+
container.x = newRect.x;
|
|
7443
|
+
container.y = newRect.y;
|
|
7444
|
+
container.width = newRect.width;
|
|
7445
|
+
container.height = newRect.height;
|
|
7446
|
+
}
|
|
7447
|
+
updateRect();
|
|
7448
|
+
onMounted(updateRect);
|
|
7449
|
+
onPatched(updateRect);
|
|
7450
|
+
return container;
|
|
7451
|
+
}
|
|
7452
|
+
|
|
7453
|
+
const macRegex = /Mac/i;
|
|
7454
|
+
/**
|
|
7455
|
+
* Return true if the event was triggered from
|
|
7456
|
+
* a child element.
|
|
7457
|
+
*/
|
|
7458
|
+
function isChildEvent(parent, ev) {
|
|
7459
|
+
return !!ev.target && parent.contains(ev.target);
|
|
7460
|
+
}
|
|
7461
|
+
function gridOverlayPosition() {
|
|
7462
|
+
const spreadsheetElement = document.querySelector(".o-grid-overlay");
|
|
7463
|
+
if (spreadsheetElement) {
|
|
7464
|
+
const { top, left } = spreadsheetElement?.getBoundingClientRect();
|
|
7465
|
+
return { top, left };
|
|
7466
|
+
}
|
|
7467
|
+
throw new Error("Can't find spreadsheet position");
|
|
7468
|
+
}
|
|
7469
|
+
function getBoundingRectAsPOJO(el) {
|
|
7470
|
+
const rect = el.getBoundingClientRect();
|
|
7471
|
+
return {
|
|
7472
|
+
x: rect.x,
|
|
7473
|
+
y: rect.y,
|
|
7474
|
+
width: rect.width,
|
|
7475
|
+
height: rect.height,
|
|
7476
|
+
};
|
|
7477
|
+
}
|
|
7478
|
+
/**
|
|
7479
|
+
* Iterate over all the children of `el` in the dom tree starting at `el`, depth first.
|
|
7480
|
+
*/
|
|
7481
|
+
function* iterateChildren(el) {
|
|
7482
|
+
yield el;
|
|
7483
|
+
if (el.hasChildNodes()) {
|
|
7484
|
+
for (let child of el.childNodes) {
|
|
7485
|
+
yield* iterateChildren(child);
|
|
7486
|
+
}
|
|
7487
|
+
}
|
|
7488
|
+
}
|
|
7489
|
+
function getOpenedMenus() {
|
|
7490
|
+
return Array.from(document.querySelectorAll(".o-spreadsheet .o-menu"));
|
|
7491
|
+
}
|
|
7492
|
+
const letterRegex = /^[a-zA-Z]$/;
|
|
7493
|
+
/**
|
|
7494
|
+
* Transform a keyboard event into a shortcut string that represent this event. The letters keys will be uppercased.
|
|
7495
|
+
*
|
|
7496
|
+
* @argument ev - The keyboard event to transform
|
|
7497
|
+
* @argument mode - Use either ev.key of ev.code to get the string shortcut
|
|
7498
|
+
*
|
|
7499
|
+
* @example
|
|
7500
|
+
* event : { ctrlKey: true, key: "a" } => "Ctrl+A"
|
|
7501
|
+
* event : { shift: true, alt: true, key: "Home" } => "Alt+Shift+Home"
|
|
7502
|
+
*/
|
|
7503
|
+
function keyboardEventToShortcutString(ev, mode = "key") {
|
|
7504
|
+
let keyDownString = "";
|
|
7505
|
+
if (isCtrlKey(ev) && ev.key !== "Ctrl")
|
|
7506
|
+
keyDownString += "Ctrl+";
|
|
7507
|
+
if (ev.metaKey)
|
|
7508
|
+
keyDownString += "Ctrl+";
|
|
7509
|
+
if (ev.altKey && ev.key !== "Alt")
|
|
7510
|
+
keyDownString += "Alt+";
|
|
7511
|
+
if (ev.shiftKey && ev.key !== "Shift")
|
|
7512
|
+
keyDownString += "Shift+";
|
|
7513
|
+
const key = mode === "key" ? ev.key : ev.code;
|
|
7514
|
+
keyDownString += letterRegex.test(key) ? key.toUpperCase() : key;
|
|
7515
|
+
return keyDownString;
|
|
7516
|
+
}
|
|
7517
|
+
function isMacOS() {
|
|
7518
|
+
return Boolean(macRegex.test(navigator.userAgent));
|
|
7519
|
+
}
|
|
7520
|
+
/**
|
|
7521
|
+
* @param {KeyboardEvent | MouseEvent} ev
|
|
7522
|
+
* @returns Returns true if the event was triggered with the "ctrl" modifier pressed.
|
|
7523
|
+
* On Mac, this is the "meta" or "command" key.
|
|
7524
|
+
*/
|
|
7525
|
+
function isCtrlKey(ev) {
|
|
7526
|
+
return isMacOS() ? ev.metaKey : ev.ctrlKey;
|
|
7527
|
+
}
|
|
7528
|
+
|
|
7529
|
+
/**
|
|
7530
|
+
* Compute the intersection of two rectangles. Returns nothing if the two rectangles don't overlap
|
|
7531
|
+
*/
|
|
7532
|
+
function rectIntersection(rect1, rect2) {
|
|
7533
|
+
return zoneToRect(intersection(rectToZone(rect1), rectToZone(rect2)));
|
|
7534
|
+
}
|
|
7535
|
+
/** Compute the union of the rectangles, ie. the smallest rectangle that contain them all */
|
|
7536
|
+
function rectUnion(...rects) {
|
|
7537
|
+
return zoneToRect(union(...rects.map(rectToZone)));
|
|
7538
|
+
}
|
|
7539
|
+
function rectToZone(rect) {
|
|
6854
7540
|
return {
|
|
6855
7541
|
left: rect.x,
|
|
6856
7542
|
top: rect.y,
|
|
@@ -7145,841 +7831,158 @@ class Menu extends Component {
|
|
|
7145
7831
|
depth: 1,
|
|
7146
7832
|
};
|
|
7147
7833
|
subMenu = useState({
|
|
7148
|
-
isOpen: false,
|
|
7149
|
-
position: null,
|
|
7150
|
-
scrollOffset: 0,
|
|
7151
|
-
menuItems: [],
|
|
7152
|
-
});
|
|
7153
|
-
menuRef = useRef("menu");
|
|
7154
|
-
position = useAbsoluteBoundingRect(this.menuRef);
|
|
7155
|
-
setup() {
|
|
7156
|
-
useExternalListener(window, "click", this.onExternalClick, { capture: true });
|
|
7157
|
-
useExternalListener(window, "contextmenu", this.onExternalClick, { capture: true });
|
|
7158
|
-
onWillUpdateProps((nextProps) => {
|
|
7159
|
-
if (nextProps.menuItems !== this.props.menuItems) {
|
|
7160
|
-
this.closeSubMenu();
|
|
7161
|
-
}
|
|
7162
|
-
});
|
|
7163
|
-
}
|
|
7164
|
-
get menuItemsAndSeparators() {
|
|
7165
|
-
const menuItemsAndSeparators = [];
|
|
7166
|
-
for (let i = 0; i < this.props.menuItems.length; i++) {
|
|
7167
|
-
const menuItem = this.props.menuItems[i];
|
|
7168
|
-
if (menuItem.isVisible(this.env)) {
|
|
7169
|
-
menuItemsAndSeparators.push(menuItem);
|
|
7170
|
-
}
|
|
7171
|
-
if (menuItem.separator &&
|
|
7172
|
-
i !== this.props.menuItems.length - 1 && // no separator at the end
|
|
7173
|
-
menuItemsAndSeparators[menuItemsAndSeparators.length - 1] !== "separator" // no double separator
|
|
7174
|
-
) {
|
|
7175
|
-
menuItemsAndSeparators.push("separator");
|
|
7176
|
-
}
|
|
7177
|
-
}
|
|
7178
|
-
if (menuItemsAndSeparators[menuItemsAndSeparators.length - 1] === "separator") {
|
|
7179
|
-
menuItemsAndSeparators.pop();
|
|
7180
|
-
}
|
|
7181
|
-
if (menuItemsAndSeparators.length === 1 && menuItemsAndSeparators[0] === "separator") {
|
|
7182
|
-
return [];
|
|
7183
|
-
}
|
|
7184
|
-
return menuItemsAndSeparators;
|
|
7185
|
-
}
|
|
7186
|
-
get subMenuPosition() {
|
|
7187
|
-
const position = Object.assign({}, this.subMenu.position);
|
|
7188
|
-
position.y -= this.subMenu.scrollOffset || 0;
|
|
7189
|
-
return position;
|
|
7190
|
-
}
|
|
7191
|
-
get popoverProps() {
|
|
7192
|
-
const isRoot = this.props.depth === 1;
|
|
7193
|
-
return {
|
|
7194
|
-
anchorRect: {
|
|
7195
|
-
x: this.props.position.x - MENU_WIDTH * (this.props.depth - 1),
|
|
7196
|
-
y: this.props.position.y,
|
|
7197
|
-
width: isRoot ? 0 : MENU_WIDTH,
|
|
7198
|
-
height: isRoot ? 0 : MENU_ITEM_HEIGHT,
|
|
7199
|
-
},
|
|
7200
|
-
positioning: "TopRight",
|
|
7201
|
-
verticalOffset: isRoot ? 0 : MENU_VERTICAL_PADDING,
|
|
7202
|
-
onPopoverHidden: () => this.closeSubMenu(),
|
|
7203
|
-
onPopoverMoved: () => this.closeSubMenu(),
|
|
7204
|
-
};
|
|
7205
|
-
}
|
|
7206
|
-
get childrenHaveIcon() {
|
|
7207
|
-
return this.props.menuItems.some((menuItem) => !!this.getIconName(menuItem));
|
|
7208
|
-
}
|
|
7209
|
-
getIconName(menu) {
|
|
7210
|
-
if (menu.icon(this.env)) {
|
|
7211
|
-
return menu.icon(this.env);
|
|
7212
|
-
}
|
|
7213
|
-
if (menu.isActive?.(this.env)) {
|
|
7214
|
-
return "o-spreadsheet-Icon.CHECK";
|
|
7215
|
-
}
|
|
7216
|
-
return "";
|
|
7217
|
-
}
|
|
7218
|
-
getColor(menu) {
|
|
7219
|
-
return menu.textColor ? `color: ${menu.textColor}` : undefined;
|
|
7220
|
-
}
|
|
7221
|
-
async activateMenu(menu) {
|
|
7222
|
-
const result = await menu.execute?.(this.env);
|
|
7223
|
-
this.close();
|
|
7224
|
-
this.props.onMenuClicked?.({ detail: result });
|
|
7225
|
-
}
|
|
7226
|
-
close() {
|
|
7227
|
-
this.closeSubMenu();
|
|
7228
|
-
this.props.onClose();
|
|
7229
|
-
}
|
|
7230
|
-
onExternalClick(ev) {
|
|
7231
|
-
// Don't close a root menu when clicked to open the submenus.
|
|
7232
|
-
const el = this.menuRef.el;
|
|
7233
|
-
if (el && getOpenedMenus().some((el) => isChildEvent(el, ev))) {
|
|
7234
|
-
return;
|
|
7235
|
-
}
|
|
7236
|
-
ev.closedMenuId = this.props.menuId;
|
|
7237
|
-
this.close();
|
|
7238
|
-
}
|
|
7239
|
-
getName(menu) {
|
|
7240
|
-
return menu.name(this.env);
|
|
7241
|
-
}
|
|
7242
|
-
isRoot(menu) {
|
|
7243
|
-
return !menu.execute;
|
|
7244
|
-
}
|
|
7245
|
-
isEnabled(menu) {
|
|
7246
|
-
if (menu.isEnabled(this.env)) {
|
|
7247
|
-
return this.env.model.getters.isReadonly() ? menu.isReadonlyAllowed : true;
|
|
7248
|
-
}
|
|
7249
|
-
return false;
|
|
7250
|
-
}
|
|
7251
|
-
onScroll(ev) {
|
|
7252
|
-
this.subMenu.scrollOffset = ev.target.scrollTop;
|
|
7253
|
-
}
|
|
7254
|
-
/**
|
|
7255
|
-
* If the given menu is not disabled, open it's submenu at the
|
|
7256
|
-
* correct position according to available surrounding space.
|
|
7257
|
-
*/
|
|
7258
|
-
openSubMenu(menu, menuIndex, ev) {
|
|
7259
|
-
const parentMenuEl = ev.currentTarget;
|
|
7260
|
-
if (!parentMenuEl)
|
|
7261
|
-
return;
|
|
7262
|
-
const y = parentMenuEl.getBoundingClientRect().top;
|
|
7263
|
-
this.subMenu.position = {
|
|
7264
|
-
x: this.position.x + this.props.depth * MENU_WIDTH,
|
|
7265
|
-
y: y - (this.subMenu.scrollOffset || 0),
|
|
7266
|
-
};
|
|
7267
|
-
this.subMenu.menuItems = menu.children(this.env);
|
|
7268
|
-
this.subMenu.isOpen = true;
|
|
7269
|
-
this.subMenu.parentMenu = menu;
|
|
7270
|
-
}
|
|
7271
|
-
isParentMenu(subMenu, menuItem) {
|
|
7272
|
-
return subMenu.parentMenu?.id === menuItem.id;
|
|
7273
|
-
}
|
|
7274
|
-
closeSubMenu() {
|
|
7275
|
-
this.subMenu.isOpen = false;
|
|
7276
|
-
this.subMenu.parentMenu = undefined;
|
|
7277
|
-
}
|
|
7278
|
-
onClickMenu(menu, menuIndex, ev) {
|
|
7279
|
-
if (this.isEnabled(menu)) {
|
|
7280
|
-
if (this.isRoot(menu)) {
|
|
7281
|
-
this.openSubMenu(menu, menuIndex, ev);
|
|
7282
|
-
}
|
|
7283
|
-
else {
|
|
7284
|
-
this.activateMenu(menu);
|
|
7285
|
-
}
|
|
7286
|
-
}
|
|
7287
|
-
}
|
|
7288
|
-
onMouseOver(menu, position, ev) {
|
|
7289
|
-
if (this.isEnabled(menu)) {
|
|
7290
|
-
if (this.isRoot(menu)) {
|
|
7291
|
-
this.openSubMenu(menu, position, ev);
|
|
7292
|
-
}
|
|
7293
|
-
else {
|
|
7294
|
-
this.closeSubMenu();
|
|
7295
|
-
}
|
|
7296
|
-
}
|
|
7297
|
-
}
|
|
7298
|
-
}
|
|
7299
|
-
|
|
7300
|
-
const LINK_TOOLTIP_HEIGHT = 32;
|
|
7301
|
-
const LINK_TOOLTIP_WIDTH = 220;
|
|
7302
|
-
css /* scss */ `
|
|
7303
|
-
.o-link-tool {
|
|
7304
|
-
font-size: 13px;
|
|
7305
|
-
background-color: white;
|
|
7306
|
-
box-shadow: 0 1px 4px 3px rgba(60, 64, 67, 0.15);
|
|
7307
|
-
padding: 6px 12px;
|
|
7308
|
-
border-radius: 4px;
|
|
7309
|
-
display: flex;
|
|
7310
|
-
justify-content: space-between;
|
|
7311
|
-
height: ${LINK_TOOLTIP_HEIGHT}px;
|
|
7312
|
-
width: ${LINK_TOOLTIP_WIDTH}px;
|
|
7313
|
-
box-sizing: border-box !important;
|
|
7314
|
-
|
|
7315
|
-
img {
|
|
7316
|
-
margin-right: 3px;
|
|
7317
|
-
width: 16px;
|
|
7318
|
-
height: 16px;
|
|
7319
|
-
}
|
|
7320
|
-
|
|
7321
|
-
a.o-link {
|
|
7322
|
-
color: #01666b;
|
|
7323
|
-
text-decoration: none;
|
|
7324
|
-
flex-grow: 2;
|
|
7325
|
-
white-space: nowrap;
|
|
7326
|
-
overflow: hidden;
|
|
7327
|
-
text-overflow: ellipsis;
|
|
7328
|
-
}
|
|
7329
|
-
a.o-link:hover {
|
|
7330
|
-
text-decoration: none;
|
|
7331
|
-
color: #001d1f;
|
|
7332
|
-
cursor: pointer;
|
|
7333
|
-
}
|
|
7334
|
-
}
|
|
7335
|
-
.o-link-icon {
|
|
7336
|
-
float: right;
|
|
7337
|
-
padding-left: 5px;
|
|
7338
|
-
.o-icon {
|
|
7339
|
-
height: 16px;
|
|
7340
|
-
}
|
|
7341
|
-
}
|
|
7342
|
-
.o-link-icon .o-icon {
|
|
7343
|
-
height: 13px;
|
|
7344
|
-
}
|
|
7345
|
-
.o-link-icon:hover {
|
|
7346
|
-
cursor: pointer;
|
|
7347
|
-
color: #000;
|
|
7348
|
-
}
|
|
7349
|
-
`;
|
|
7350
|
-
class LinkDisplay extends Component {
|
|
7351
|
-
static template = "o-spreadsheet-LinkDisplay";
|
|
7352
|
-
static props = {
|
|
7353
|
-
cellPosition: Object,
|
|
7354
|
-
onClosed: { type: Function, optional: true },
|
|
7355
|
-
};
|
|
7356
|
-
static components = { Menu };
|
|
7357
|
-
get cell() {
|
|
7358
|
-
const { col, row } = this.props.cellPosition;
|
|
7359
|
-
const sheetId = this.env.model.getters.getActiveSheetId();
|
|
7360
|
-
return this.env.model.getters.getEvaluatedCell({ sheetId, col, row });
|
|
7361
|
-
}
|
|
7362
|
-
get link() {
|
|
7363
|
-
if (this.cell.link) {
|
|
7364
|
-
return this.cell.link;
|
|
7365
|
-
}
|
|
7366
|
-
const { col, row } = this.props.cellPosition;
|
|
7367
|
-
throw new Error(`LinkDisplay Component can only be used with link cells. ${toXC(col, row)} is not a link.`);
|
|
7368
|
-
}
|
|
7369
|
-
getUrlRepresentation(link) {
|
|
7370
|
-
return urlRepresentation(link, this.env.model.getters);
|
|
7371
|
-
}
|
|
7372
|
-
openLink() {
|
|
7373
|
-
openLink(this.link, this.env);
|
|
7374
|
-
}
|
|
7375
|
-
edit() {
|
|
7376
|
-
const { col, row } = this.props.cellPosition;
|
|
7377
|
-
this.env.model.dispatch("OPEN_CELL_POPOVER", {
|
|
7378
|
-
col,
|
|
7379
|
-
row,
|
|
7380
|
-
popoverType: "LinkEditor",
|
|
7381
|
-
});
|
|
7382
|
-
}
|
|
7383
|
-
unlink() {
|
|
7384
|
-
const sheetId = this.env.model.getters.getActiveSheetId();
|
|
7385
|
-
const { col, row } = this.props.cellPosition;
|
|
7386
|
-
const style = this.env.model.getters.getCellComputedStyle({ sheetId, col, row });
|
|
7387
|
-
const textColor = style?.textColor === LINK_COLOR ? undefined : style?.textColor;
|
|
7388
|
-
this.env.model.dispatch("UPDATE_CELL", {
|
|
7389
|
-
col,
|
|
7390
|
-
row,
|
|
7391
|
-
sheetId,
|
|
7392
|
-
content: this.link.label,
|
|
7393
|
-
style: { ...style, textColor, underline: undefined },
|
|
7394
|
-
});
|
|
7395
|
-
}
|
|
7396
|
-
}
|
|
7397
|
-
const LinkCellPopoverBuilder = {
|
|
7398
|
-
onHover: (position, getters) => {
|
|
7399
|
-
const cell = getters.getEvaluatedCell(position);
|
|
7400
|
-
const shouldDisplayLink = !getters.isDashboard() && cell.link && getters.isVisibleInViewport(position);
|
|
7401
|
-
if (!shouldDisplayLink)
|
|
7402
|
-
return { isOpen: false };
|
|
7403
|
-
return {
|
|
7404
|
-
isOpen: true,
|
|
7405
|
-
Component: LinkDisplay,
|
|
7406
|
-
props: { cellPosition: position },
|
|
7407
|
-
cellCorner: "BottomLeft",
|
|
7408
|
-
};
|
|
7409
|
-
},
|
|
7410
|
-
};
|
|
7411
|
-
|
|
7412
|
-
/**
|
|
7413
|
-
* Tokenizer
|
|
7414
|
-
*
|
|
7415
|
-
* A tokenizer is a piece of code whose job is to transform a string into a list
|
|
7416
|
-
* of "tokens". For example, "(12+" is converted into:
|
|
7417
|
-
* [{type: "LEFT_PAREN", value: "("},
|
|
7418
|
-
* {type: "NUMBER", value: "12"},
|
|
7419
|
-
* {type: "OPERATOR", value: "+"}]
|
|
7420
|
-
*
|
|
7421
|
-
* As the example shows, a tokenizer does not care about the meaning behind those
|
|
7422
|
-
* tokens. It only cares about the structure.
|
|
7423
|
-
*
|
|
7424
|
-
* The tokenizer is usually the first step in a compilation pipeline. Also, it
|
|
7425
|
-
* is useful for the composer, which needs to be able to work with incomplete
|
|
7426
|
-
* formulas.
|
|
7427
|
-
*/
|
|
7428
|
-
const POSTFIX_UNARY_OPERATORS = ["%"];
|
|
7429
|
-
const OPERATORS = "+,-,*,/,:,=,<>,>=,>,<=,<,^,&".split(",").concat(POSTFIX_UNARY_OPERATORS);
|
|
7430
|
-
function tokenize(str, locale = DEFAULT_LOCALE) {
|
|
7431
|
-
str = replaceSpecialSpaces(str);
|
|
7432
|
-
const chars = new TokenizingChars(str);
|
|
7433
|
-
const result = [];
|
|
7434
|
-
while (!chars.isOver()) {
|
|
7435
|
-
let token = tokenizeSpace(chars) ||
|
|
7436
|
-
tokenizeArgsSeparator(chars, locale) ||
|
|
7437
|
-
tokenizeMisc(chars) ||
|
|
7438
|
-
tokenizeOperator(chars) ||
|
|
7439
|
-
tokenizeString(chars) ||
|
|
7440
|
-
tokenizeDebugger(chars) ||
|
|
7441
|
-
tokenizeInvalidRange(chars) ||
|
|
7442
|
-
tokenizeNumber(chars, locale) ||
|
|
7443
|
-
tokenizeSymbol(chars);
|
|
7444
|
-
if (!token) {
|
|
7445
|
-
token = { type: "UNKNOWN", value: chars.shift() };
|
|
7446
|
-
}
|
|
7447
|
-
result.push(token);
|
|
7448
|
-
}
|
|
7449
|
-
return result;
|
|
7450
|
-
}
|
|
7451
|
-
function tokenizeDebugger(chars) {
|
|
7452
|
-
if (chars.current === "?") {
|
|
7453
|
-
chars.shift();
|
|
7454
|
-
return { type: "DEBUGGER", value: "?" };
|
|
7455
|
-
}
|
|
7456
|
-
return null;
|
|
7457
|
-
}
|
|
7458
|
-
const misc$1 = {
|
|
7459
|
-
"(": "LEFT_PAREN",
|
|
7460
|
-
")": "RIGHT_PAREN",
|
|
7461
|
-
};
|
|
7462
|
-
function tokenizeMisc(chars) {
|
|
7463
|
-
if (chars.current in misc$1) {
|
|
7464
|
-
const value = chars.shift();
|
|
7465
|
-
const type = misc$1[value];
|
|
7466
|
-
return { type, value };
|
|
7467
|
-
}
|
|
7468
|
-
return null;
|
|
7469
|
-
}
|
|
7470
|
-
function tokenizeArgsSeparator(chars, locale) {
|
|
7471
|
-
if (chars.current === locale.formulaArgSeparator) {
|
|
7472
|
-
const value = chars.shift();
|
|
7473
|
-
const type = "ARG_SEPARATOR";
|
|
7474
|
-
return { type, value };
|
|
7475
|
-
}
|
|
7476
|
-
return null;
|
|
7477
|
-
}
|
|
7478
|
-
function tokenizeOperator(chars) {
|
|
7479
|
-
for (let op of OPERATORS) {
|
|
7480
|
-
if (chars.currentStartsWith(op)) {
|
|
7481
|
-
chars.advanceBy(op.length);
|
|
7482
|
-
return { type: "OPERATOR", value: op };
|
|
7483
|
-
}
|
|
7484
|
-
}
|
|
7485
|
-
return null;
|
|
7486
|
-
}
|
|
7487
|
-
function tokenizeNumber(chars, locale) {
|
|
7488
|
-
const match = chars.remaining().match(getFormulaNumberRegex(locale.decimalSeparator));
|
|
7489
|
-
if (match) {
|
|
7490
|
-
chars.advanceBy(match[0].length);
|
|
7491
|
-
return { type: "NUMBER", value: match[0] };
|
|
7492
|
-
}
|
|
7493
|
-
return null;
|
|
7494
|
-
}
|
|
7495
|
-
function tokenizeString(chars) {
|
|
7496
|
-
if (chars.current === '"') {
|
|
7497
|
-
const startChar = chars.shift();
|
|
7498
|
-
let letters = startChar;
|
|
7499
|
-
while (chars.current && (chars.current !== startChar || letters[letters.length - 1] === "\\")) {
|
|
7500
|
-
letters += chars.shift();
|
|
7501
|
-
}
|
|
7502
|
-
if (chars.current === '"') {
|
|
7503
|
-
letters += chars.shift();
|
|
7504
|
-
}
|
|
7505
|
-
return {
|
|
7506
|
-
type: "STRING",
|
|
7507
|
-
value: letters,
|
|
7508
|
-
};
|
|
7509
|
-
}
|
|
7510
|
-
return null;
|
|
7511
|
-
}
|
|
7512
|
-
const separatorRegexp = /\w|\.|!|\$/;
|
|
7513
|
-
/**
|
|
7514
|
-
* A "Symbol" is just basically any word-like element that can appear in a
|
|
7515
|
-
* formula, which is not a string. So:
|
|
7516
|
-
* A1
|
|
7517
|
-
* SUM
|
|
7518
|
-
* CEILING.MATH
|
|
7519
|
-
* A$1
|
|
7520
|
-
* Sheet2!A2
|
|
7521
|
-
* 'Sheet 2'!A2
|
|
7522
|
-
*
|
|
7523
|
-
* are examples of symbols
|
|
7524
|
-
*/
|
|
7525
|
-
function tokenizeSymbol(chars) {
|
|
7526
|
-
let result = "";
|
|
7527
|
-
// there are two main cases to manage: either something which starts with
|
|
7528
|
-
// a ', like 'Sheet 2'A2, or a word-like element.
|
|
7529
|
-
if (chars.current === "'") {
|
|
7530
|
-
let lastChar = chars.shift();
|
|
7531
|
-
result += lastChar;
|
|
7532
|
-
while (chars.current) {
|
|
7533
|
-
lastChar = chars.shift();
|
|
7534
|
-
result += lastChar;
|
|
7535
|
-
if (lastChar === "'") {
|
|
7536
|
-
if (chars.current && chars.current === "'") {
|
|
7537
|
-
lastChar = chars.shift();
|
|
7538
|
-
result += lastChar;
|
|
7539
|
-
}
|
|
7540
|
-
else {
|
|
7541
|
-
break;
|
|
7542
|
-
}
|
|
7543
|
-
}
|
|
7544
|
-
}
|
|
7545
|
-
if (lastChar !== "'") {
|
|
7546
|
-
return {
|
|
7547
|
-
type: "UNKNOWN",
|
|
7548
|
-
value: result,
|
|
7549
|
-
};
|
|
7550
|
-
}
|
|
7551
|
-
}
|
|
7552
|
-
while (chars.current && separatorRegexp.test(chars.current)) {
|
|
7553
|
-
result += chars.shift();
|
|
7554
|
-
}
|
|
7555
|
-
if (result.length) {
|
|
7556
|
-
const value = result;
|
|
7557
|
-
const isReference = rangeReference.test(value);
|
|
7558
|
-
if (isReference) {
|
|
7559
|
-
return { type: "REFERENCE", value };
|
|
7560
|
-
}
|
|
7561
|
-
return { type: "SYMBOL", value };
|
|
7562
|
-
}
|
|
7563
|
-
return null;
|
|
7564
|
-
}
|
|
7565
|
-
function tokenizeSpace(chars) {
|
|
7566
|
-
let length = 0;
|
|
7567
|
-
while (chars.current === NEWLINE) {
|
|
7568
|
-
length++;
|
|
7569
|
-
chars.shift();
|
|
7570
|
-
}
|
|
7571
|
-
if (length) {
|
|
7572
|
-
return { type: "SPACE", value: NEWLINE.repeat(length) };
|
|
7573
|
-
}
|
|
7574
|
-
while (chars.current === " ") {
|
|
7575
|
-
length++;
|
|
7576
|
-
chars.shift();
|
|
7577
|
-
}
|
|
7578
|
-
if (length) {
|
|
7579
|
-
return { type: "SPACE", value: " ".repeat(length) };
|
|
7580
|
-
}
|
|
7581
|
-
return null;
|
|
7582
|
-
}
|
|
7583
|
-
function tokenizeInvalidRange(chars) {
|
|
7584
|
-
if (chars.currentStartsWith(CellErrorType.InvalidReference)) {
|
|
7585
|
-
chars.advanceBy(CellErrorType.InvalidReference.length);
|
|
7586
|
-
return { type: "INVALID_REFERENCE", value: CellErrorType.InvalidReference };
|
|
7587
|
-
}
|
|
7588
|
-
return null;
|
|
7589
|
-
}
|
|
7590
|
-
class TokenizingChars {
|
|
7591
|
-
text;
|
|
7592
|
-
currentIndex = 0;
|
|
7593
|
-
current;
|
|
7594
|
-
constructor(text) {
|
|
7595
|
-
this.text = text;
|
|
7596
|
-
this.current = text[0];
|
|
7597
|
-
}
|
|
7598
|
-
shift() {
|
|
7599
|
-
const current = this.current;
|
|
7600
|
-
const next = this.text[++this.currentIndex];
|
|
7601
|
-
this.current = next;
|
|
7602
|
-
return current;
|
|
7603
|
-
}
|
|
7604
|
-
advanceBy(length) {
|
|
7605
|
-
this.currentIndex += length;
|
|
7606
|
-
this.current = this.text[this.currentIndex];
|
|
7607
|
-
}
|
|
7608
|
-
isOver() {
|
|
7609
|
-
return this.currentIndex >= this.text.length;
|
|
7610
|
-
}
|
|
7611
|
-
remaining() {
|
|
7612
|
-
return this.text.substring(this.currentIndex);
|
|
7613
|
-
}
|
|
7614
|
-
currentStartsWith(str) {
|
|
7615
|
-
for (let j = 0; j < str.length; j++) {
|
|
7616
|
-
if (this.text[this.currentIndex + j] !== str[j]) {
|
|
7617
|
-
return false;
|
|
7618
|
-
}
|
|
7619
|
-
}
|
|
7620
|
-
return true;
|
|
7621
|
-
}
|
|
7622
|
-
}
|
|
7623
|
-
|
|
7624
|
-
function isValidLocale(locale) {
|
|
7625
|
-
if (!(locale &&
|
|
7626
|
-
typeof locale === "object" &&
|
|
7627
|
-
typeof locale.name === "string" &&
|
|
7628
|
-
typeof locale.code === "string" &&
|
|
7629
|
-
typeof locale.thousandsSeparator === "string" &&
|
|
7630
|
-
typeof locale.decimalSeparator === "string" &&
|
|
7631
|
-
typeof locale.dateFormat === "string" &&
|
|
7632
|
-
typeof locale.timeFormat === "string" &&
|
|
7633
|
-
typeof locale.formulaArgSeparator === "string")) {
|
|
7634
|
-
return false;
|
|
7635
|
-
}
|
|
7636
|
-
if (!Object.values(locale).every((v) => v)) {
|
|
7637
|
-
return false;
|
|
7638
|
-
}
|
|
7639
|
-
if (locale.formulaArgSeparator === locale.decimalSeparator) {
|
|
7640
|
-
return false;
|
|
7641
|
-
}
|
|
7642
|
-
try {
|
|
7643
|
-
formatValue(1, { locale, format: "#,##0.00" });
|
|
7644
|
-
formatValue(1, { locale, format: locale.dateFormat });
|
|
7645
|
-
formatValue(1, { locale, format: locale.timeFormat });
|
|
7646
|
-
}
|
|
7647
|
-
catch {
|
|
7648
|
-
return false;
|
|
7649
|
-
}
|
|
7650
|
-
return true;
|
|
7651
|
-
}
|
|
7652
|
-
/**
|
|
7653
|
-
* Change a content string from the given locale to its canonical form (en_US locale). Don't convert date string.
|
|
7654
|
-
*
|
|
7655
|
-
* @example
|
|
7656
|
-
* canonicalizeNumberContent("=SUM(1,5; 02/12/2012)", FR_LOCALE) // "=SUM(1.5, 02/12/2012)"
|
|
7657
|
-
* canonicalizeNumberContent("125,9", FR_LOCALE) // "125.9"
|
|
7658
|
-
* canonicalizeNumberContent("02/12/2012", FR_LOCALE) // "02/12/2012"
|
|
7659
|
-
*/
|
|
7660
|
-
function canonicalizeNumberContent(content, locale) {
|
|
7661
|
-
return content.startsWith("=")
|
|
7662
|
-
? canonicalizeFormula$1(content, locale)
|
|
7663
|
-
: canonicalizeNumberLiteral(content, locale);
|
|
7664
|
-
}
|
|
7665
|
-
/**
|
|
7666
|
-
* Change a content string from the given locale to its canonical form (en_US locale). Also convert date string.
|
|
7667
|
-
* This is destructive and won't preserve the original format.
|
|
7668
|
-
*
|
|
7669
|
-
* @example
|
|
7670
|
-
* canonicalizeContent("=SUM(1,5; 5)", FR_LOCALE) // "=SUM(1.5, 5)"
|
|
7671
|
-
* canonicalizeContent("125,9", FR_LOCALE) // "125.9"
|
|
7672
|
-
* canonicalizeContent("02/12/2012", FR_LOCALE) // "12/02/2012"
|
|
7673
|
-
* canonicalizeContent("02-12-2012", FR_LOCALE) // "12/02/2012"
|
|
7674
|
-
*/
|
|
7675
|
-
function canonicalizeContent(content, locale) {
|
|
7676
|
-
return content.startsWith("=")
|
|
7677
|
-
? canonicalizeFormula$1(content, locale)
|
|
7678
|
-
: canonicalizeLiteral(content, locale);
|
|
7679
|
-
}
|
|
7680
|
-
/**
|
|
7681
|
-
* Change a content string from its canonical form (en_US locale) to the given locale. Also convert date string.
|
|
7682
|
-
*
|
|
7683
|
-
* @example
|
|
7684
|
-
* localizeContent("=SUM(1.5, 5)", FR_LOCALE) // "=SUM(1,5; 5)"
|
|
7685
|
-
* localizeContent("125.9", FR_LOCALE) // "125,9"
|
|
7686
|
-
* localizeContent("12/02/2012", FR_LOCALE) // "02/12/2012"
|
|
7687
|
-
*/
|
|
7688
|
-
function localizeContent(content, locale) {
|
|
7689
|
-
return content.startsWith("=")
|
|
7690
|
-
? localizeFormula(content, locale)
|
|
7691
|
-
: localizeLiteral(content, locale);
|
|
7692
|
-
}
|
|
7693
|
-
/** Change a formula to its canonical form (en_US locale) */
|
|
7694
|
-
function canonicalizeFormula$1(formula, locale) {
|
|
7695
|
-
return _localizeFormula$1(formula, locale, DEFAULT_LOCALE);
|
|
7696
|
-
}
|
|
7697
|
-
/** Change a formula from the canonical form to the given locale */
|
|
7698
|
-
function localizeFormula(formula, locale) {
|
|
7699
|
-
return _localizeFormula$1(formula, DEFAULT_LOCALE, locale);
|
|
7700
|
-
}
|
|
7701
|
-
function _localizeFormula$1(formula, fromLocale, toLocale) {
|
|
7702
|
-
if (fromLocale.formulaArgSeparator === toLocale.formulaArgSeparator &&
|
|
7703
|
-
fromLocale.decimalSeparator === toLocale.decimalSeparator) {
|
|
7704
|
-
return formula;
|
|
7834
|
+
isOpen: false,
|
|
7835
|
+
position: null,
|
|
7836
|
+
scrollOffset: 0,
|
|
7837
|
+
menuItems: [],
|
|
7838
|
+
});
|
|
7839
|
+
menuRef = useRef("menu");
|
|
7840
|
+
position = useAbsoluteBoundingRect(this.menuRef);
|
|
7841
|
+
setup() {
|
|
7842
|
+
useExternalListener(window, "click", this.onExternalClick, { capture: true });
|
|
7843
|
+
useExternalListener(window, "contextmenu", this.onExternalClick, { capture: true });
|
|
7844
|
+
onWillUpdateProps((nextProps) => {
|
|
7845
|
+
if (nextProps.menuItems !== this.props.menuItems) {
|
|
7846
|
+
this.closeSubMenu();
|
|
7847
|
+
}
|
|
7848
|
+
});
|
|
7705
7849
|
}
|
|
7706
|
-
|
|
7707
|
-
|
|
7708
|
-
|
|
7709
|
-
|
|
7710
|
-
|
|
7850
|
+
get menuItemsAndSeparators() {
|
|
7851
|
+
const menuItemsAndSeparators = [];
|
|
7852
|
+
for (let i = 0; i < this.props.menuItems.length; i++) {
|
|
7853
|
+
const menuItem = this.props.menuItems[i];
|
|
7854
|
+
if (menuItem.isVisible(this.env)) {
|
|
7855
|
+
menuItemsAndSeparators.push(menuItem);
|
|
7856
|
+
}
|
|
7857
|
+
if (menuItem.separator &&
|
|
7858
|
+
i !== this.props.menuItems.length - 1 && // no separator at the end
|
|
7859
|
+
menuItemsAndSeparators[menuItemsAndSeparators.length - 1] !== "separator" // no double separator
|
|
7860
|
+
) {
|
|
7861
|
+
menuItemsAndSeparators.push("separator");
|
|
7862
|
+
}
|
|
7711
7863
|
}
|
|
7712
|
-
|
|
7713
|
-
|
|
7864
|
+
if (menuItemsAndSeparators[menuItemsAndSeparators.length - 1] === "separator") {
|
|
7865
|
+
menuItemsAndSeparators.pop();
|
|
7714
7866
|
}
|
|
7715
|
-
|
|
7716
|
-
|
|
7867
|
+
if (menuItemsAndSeparators.length === 1 && menuItemsAndSeparators[0] === "separator") {
|
|
7868
|
+
return [];
|
|
7717
7869
|
}
|
|
7870
|
+
return menuItemsAndSeparators;
|
|
7718
7871
|
}
|
|
7719
|
-
|
|
7720
|
-
}
|
|
7721
|
-
|
|
7722
|
-
|
|
7723
|
-
*
|
|
7724
|
-
* @example
|
|
7725
|
-
* canonicalizeNumberLiteral("125,9", FR_LOCALE) // "125.9"
|
|
7726
|
-
* canonicalizeNumberLiteral("02/12/2012", FR_LOCALE) // "02/12/2012"
|
|
7727
|
-
*/
|
|
7728
|
-
function canonicalizeNumberLiteral(content, locale) {
|
|
7729
|
-
if (locale.decimalSeparator === "." || !isNumber(content, locale)) {
|
|
7730
|
-
return content;
|
|
7872
|
+
get subMenuPosition() {
|
|
7873
|
+
const position = Object.assign({}, this.subMenu.position);
|
|
7874
|
+
position.y -= this.subMenu.scrollOffset || 0;
|
|
7875
|
+
return position;
|
|
7731
7876
|
}
|
|
7732
|
-
|
|
7733
|
-
|
|
7734
|
-
|
|
7735
|
-
|
|
7736
|
-
|
|
7737
|
-
|
|
7738
|
-
|
|
7739
|
-
|
|
7740
|
-
|
|
7741
|
-
|
|
7742
|
-
|
|
7743
|
-
|
|
7744
|
-
|
|
7745
|
-
|
|
7746
|
-
|
|
7747
|
-
|
|
7748
|
-
|
|
7877
|
+
get popoverProps() {
|
|
7878
|
+
const isRoot = this.props.depth === 1;
|
|
7879
|
+
return {
|
|
7880
|
+
anchorRect: {
|
|
7881
|
+
x: this.props.position.x - MENU_WIDTH * (this.props.depth - 1),
|
|
7882
|
+
y: this.props.position.y,
|
|
7883
|
+
width: isRoot ? 0 : MENU_WIDTH,
|
|
7884
|
+
height: isRoot ? 0 : MENU_ITEM_HEIGHT,
|
|
7885
|
+
},
|
|
7886
|
+
positioning: "TopRight",
|
|
7887
|
+
verticalOffset: isRoot ? 0 : MENU_VERTICAL_PADDING,
|
|
7888
|
+
onPopoverHidden: () => this.closeSubMenu(),
|
|
7889
|
+
onPopoverMoved: () => this.closeSubMenu(),
|
|
7890
|
+
};
|
|
7891
|
+
}
|
|
7892
|
+
get childrenHaveIcon() {
|
|
7893
|
+
return this.props.menuItems.some((menuItem) => !!this.getIconName(menuItem));
|
|
7894
|
+
}
|
|
7895
|
+
getIconName(menu) {
|
|
7896
|
+
if (menu.icon(this.env)) {
|
|
7897
|
+
return menu.icon(this.env);
|
|
7749
7898
|
}
|
|
7750
|
-
|
|
7899
|
+
if (menu.isActive?.(this.env)) {
|
|
7900
|
+
return "o-spreadsheet-Icon.CHECK";
|
|
7901
|
+
}
|
|
7902
|
+
return "";
|
|
7751
7903
|
}
|
|
7752
|
-
|
|
7753
|
-
}
|
|
7754
|
-
/**
|
|
7755
|
-
* Change a literal string from its canonical form (en_US locale) to the given locale. Don't convert date string.
|
|
7756
|
-
* This is destructive and won't preserve the original format.
|
|
7757
|
-
*
|
|
7758
|
-
* @example
|
|
7759
|
-
* localizeNumberLiteral("125.9", FR_LOCALE) // "125,9"
|
|
7760
|
-
* localizeNumberLiteral("12/02/2012", FR_LOCALE) // "12/02/2012"
|
|
7761
|
-
* localizeNumberLiteral("12-02-2012", FR_LOCALE) // "12/02/2012"
|
|
7762
|
-
*/
|
|
7763
|
-
function localizeNumberLiteral(literal, locale) {
|
|
7764
|
-
if (locale.decimalSeparator === "." || !isNumber(literal, DEFAULT_LOCALE)) {
|
|
7765
|
-
return literal;
|
|
7904
|
+
getColor(menu) {
|
|
7905
|
+
return menu.textColor ? `color: ${menu.textColor}` : undefined;
|
|
7766
7906
|
}
|
|
7767
|
-
|
|
7768
|
-
|
|
7769
|
-
|
|
7770
|
-
|
|
7771
|
-
|
|
7772
|
-
|
|
7773
|
-
|
|
7774
|
-
|
|
7775
|
-
|
|
7776
|
-
|
|
7777
|
-
|
|
7778
|
-
|
|
7779
|
-
|
|
7780
|
-
|
|
7781
|
-
if (isDateTime(literal, DEFAULT_LOCALE)) {
|
|
7782
|
-
const dateNumber = toNumber(literal, DEFAULT_LOCALE);
|
|
7783
|
-
let format = locale.dateFormat;
|
|
7784
|
-
if (!Number.isInteger(dateNumber)) {
|
|
7785
|
-
format += " " + locale.timeFormat;
|
|
7907
|
+
async activateMenu(menu) {
|
|
7908
|
+
const result = await menu.execute?.(this.env);
|
|
7909
|
+
this.close();
|
|
7910
|
+
this.props.onMenuClicked?.({ detail: result });
|
|
7911
|
+
}
|
|
7912
|
+
close() {
|
|
7913
|
+
this.closeSubMenu();
|
|
7914
|
+
this.props.onClose();
|
|
7915
|
+
}
|
|
7916
|
+
onExternalClick(ev) {
|
|
7917
|
+
// Don't close a root menu when clicked to open the submenus.
|
|
7918
|
+
const el = this.menuRef.el;
|
|
7919
|
+
if (el && getOpenedMenus().some((el) => isChildEvent(el, ev))) {
|
|
7920
|
+
return;
|
|
7786
7921
|
}
|
|
7787
|
-
|
|
7922
|
+
ev.closedMenuId = this.props.menuId;
|
|
7923
|
+
this.close();
|
|
7788
7924
|
}
|
|
7789
|
-
|
|
7790
|
-
|
|
7791
|
-
function canonicalizeCFRule(cf, locale) {
|
|
7792
|
-
return changeCFRuleLocale(cf, (content) => canonicalizeContent(content, locale));
|
|
7793
|
-
}
|
|
7794
|
-
function localizeCFRule(cf, locale) {
|
|
7795
|
-
return changeCFRuleLocale(cf, (content) => localizeContent(content, locale));
|
|
7796
|
-
}
|
|
7797
|
-
function localizeDataValidationRule(rule, locale) {
|
|
7798
|
-
const localizedDVRule = deepCopy(rule);
|
|
7799
|
-
localizedDVRule.criterion.values = localizedDVRule.criterion.values.map((content) => localizeContent(content, locale));
|
|
7800
|
-
return localizedDVRule;
|
|
7801
|
-
}
|
|
7802
|
-
function changeCFRuleLocale(rule, changeContentLocale) {
|
|
7803
|
-
rule = deepCopy(rule);
|
|
7804
|
-
switch (rule.type) {
|
|
7805
|
-
case "CellIsRule":
|
|
7806
|
-
// Only change value for number operators
|
|
7807
|
-
switch (rule.operator) {
|
|
7808
|
-
case "Between":
|
|
7809
|
-
case "NotBetween":
|
|
7810
|
-
case "Equal":
|
|
7811
|
-
case "NotEqual":
|
|
7812
|
-
case "GreaterThan":
|
|
7813
|
-
case "GreaterThanOrEqual":
|
|
7814
|
-
case "LessThan":
|
|
7815
|
-
case "LessThanOrEqual":
|
|
7816
|
-
rule.values = rule.values.map((v) => changeContentLocale(v));
|
|
7817
|
-
return rule;
|
|
7818
|
-
case "BeginsWith":
|
|
7819
|
-
case "ContainsText":
|
|
7820
|
-
case "EndsWith":
|
|
7821
|
-
case "NotContains":
|
|
7822
|
-
case "IsEmpty":
|
|
7823
|
-
case "IsNotEmpty":
|
|
7824
|
-
return rule;
|
|
7825
|
-
}
|
|
7826
|
-
break;
|
|
7827
|
-
case "ColorScaleRule":
|
|
7828
|
-
rule.minimum = changeCFRuleThresholdLocale(rule.minimum, changeContentLocale);
|
|
7829
|
-
rule.maximum = changeCFRuleThresholdLocale(rule.maximum, changeContentLocale);
|
|
7830
|
-
if (rule.midpoint) {
|
|
7831
|
-
rule.midpoint = changeCFRuleThresholdLocale(rule.midpoint, changeContentLocale);
|
|
7832
|
-
}
|
|
7833
|
-
return rule;
|
|
7834
|
-
case "IconSetRule":
|
|
7835
|
-
rule.lowerInflectionPoint.value = changeContentLocale(rule.lowerInflectionPoint.value);
|
|
7836
|
-
rule.upperInflectionPoint.value = changeContentLocale(rule.upperInflectionPoint.value);
|
|
7837
|
-
return rule;
|
|
7925
|
+
getName(menu) {
|
|
7926
|
+
return menu.name(this.env);
|
|
7838
7927
|
}
|
|
7839
|
-
|
|
7840
|
-
|
|
7841
|
-
if (!threshold?.value) {
|
|
7842
|
-
return threshold;
|
|
7928
|
+
isRoot(menu) {
|
|
7929
|
+
return !menu.execute;
|
|
7843
7930
|
}
|
|
7844
|
-
|
|
7845
|
-
|
|
7846
|
-
|
|
7847
|
-
return { ...threshold, value: newValue };
|
|
7848
|
-
}
|
|
7849
|
-
function getDateTimeFormat(locale) {
|
|
7850
|
-
return locale.dateFormat + " " + locale.timeFormat;
|
|
7851
|
-
}
|
|
7852
|
-
|
|
7853
|
-
const linkSheet = {
|
|
7854
|
-
name: _t("Link sheet"),
|
|
7855
|
-
children: [
|
|
7856
|
-
(env) => {
|
|
7857
|
-
const sheets = env.model.getters
|
|
7858
|
-
.getSheetIds()
|
|
7859
|
-
.map((sheetId) => env.model.getters.getSheet(sheetId));
|
|
7860
|
-
return sheets.map((sheet) => ({
|
|
7861
|
-
id: sheet.id,
|
|
7862
|
-
name: sheet.name,
|
|
7863
|
-
execute: () => markdownLink(sheet.name, buildSheetLink(sheet.id)),
|
|
7864
|
-
}));
|
|
7865
|
-
},
|
|
7866
|
-
],
|
|
7867
|
-
};
|
|
7868
|
-
const deleteSheet = {
|
|
7869
|
-
name: _t("Delete"),
|
|
7870
|
-
isVisible: (env) => {
|
|
7871
|
-
return env.model.getters.getSheetIds().length > 1;
|
|
7872
|
-
},
|
|
7873
|
-
execute: (env) => env.askConfirmation(_t("Are you sure you want to delete this sheet?"), () => {
|
|
7874
|
-
env.model.dispatch("DELETE_SHEET", { sheetId: env.model.getters.getActiveSheetId() });
|
|
7875
|
-
}),
|
|
7876
|
-
};
|
|
7877
|
-
const duplicateSheet = {
|
|
7878
|
-
name: _t("Duplicate"),
|
|
7879
|
-
execute: (env) => {
|
|
7880
|
-
const sheetIdFrom = env.model.getters.getActiveSheetId();
|
|
7881
|
-
const sheetIdTo = env.model.uuidGenerator.uuidv4();
|
|
7882
|
-
env.model.dispatch("DUPLICATE_SHEET", {
|
|
7883
|
-
sheetId: sheetIdFrom,
|
|
7884
|
-
sheetIdTo,
|
|
7885
|
-
});
|
|
7886
|
-
env.model.dispatch("ACTIVATE_SHEET", { sheetIdFrom, sheetIdTo });
|
|
7887
|
-
},
|
|
7888
|
-
};
|
|
7889
|
-
const renameSheet = (args) => {
|
|
7890
|
-
return {
|
|
7891
|
-
name: _t("Rename"),
|
|
7892
|
-
execute: args.renameSheetCallback,
|
|
7893
|
-
};
|
|
7894
|
-
};
|
|
7895
|
-
const sheetMoveRight = {
|
|
7896
|
-
name: _t("Move right"),
|
|
7897
|
-
isVisible: (env) => {
|
|
7898
|
-
const sheetId = env.model.getters.getActiveSheetId();
|
|
7899
|
-
const sheetIds = env.model.getters.getVisibleSheetIds();
|
|
7900
|
-
return sheetIds.indexOf(sheetId) !== sheetIds.length - 1;
|
|
7901
|
-
},
|
|
7902
|
-
execute: (env) => env.model.dispatch("MOVE_SHEET", {
|
|
7903
|
-
sheetId: env.model.getters.getActiveSheetId(),
|
|
7904
|
-
delta: 1,
|
|
7905
|
-
}),
|
|
7906
|
-
};
|
|
7907
|
-
const sheetMoveLeft = {
|
|
7908
|
-
name: _t("Move left"),
|
|
7909
|
-
isVisible: (env) => {
|
|
7910
|
-
const sheetId = env.model.getters.getActiveSheetId();
|
|
7911
|
-
return env.model.getters.getVisibleSheetIds()[0] !== sheetId;
|
|
7912
|
-
},
|
|
7913
|
-
execute: (env) => env.model.dispatch("MOVE_SHEET", {
|
|
7914
|
-
sheetId: env.model.getters.getActiveSheetId(),
|
|
7915
|
-
delta: -1,
|
|
7916
|
-
}),
|
|
7917
|
-
};
|
|
7918
|
-
const hideSheet = {
|
|
7919
|
-
name: _t("Hide sheet"),
|
|
7920
|
-
isVisible: (env) => env.model.getters.getVisibleSheetIds().length !== 1,
|
|
7921
|
-
execute: (env) => env.model.dispatch("HIDE_SHEET", { sheetId: env.model.getters.getActiveSheetId() }),
|
|
7922
|
-
};
|
|
7923
|
-
|
|
7924
|
-
/**
|
|
7925
|
-
* The class Registry is extended in order to add the function addChild
|
|
7926
|
-
*
|
|
7927
|
-
*/
|
|
7928
|
-
class MenuItemRegistry extends Registry {
|
|
7929
|
-
/**
|
|
7930
|
-
* @override
|
|
7931
|
-
*/
|
|
7932
|
-
add(key, value) {
|
|
7933
|
-
if (value.id === undefined) {
|
|
7934
|
-
value.id = key;
|
|
7931
|
+
isEnabled(menu) {
|
|
7932
|
+
if (menu.isEnabled(this.env)) {
|
|
7933
|
+
return this.env.model.getters.isReadonly() ? menu.isReadonlyAllowed : true;
|
|
7935
7934
|
}
|
|
7936
|
-
|
|
7937
|
-
|
|
7935
|
+
return false;
|
|
7936
|
+
}
|
|
7937
|
+
onScroll(ev) {
|
|
7938
|
+
this.subMenu.scrollOffset = ev.target.scrollTop;
|
|
7938
7939
|
}
|
|
7939
7940
|
/**
|
|
7940
|
-
*
|
|
7941
|
-
*
|
|
7942
|
-
* @param value Subitem to add
|
|
7941
|
+
* If the given menu is not disabled, open it's submenu at the
|
|
7942
|
+
* correct position according to available surrounding space.
|
|
7943
7943
|
*/
|
|
7944
|
-
|
|
7945
|
-
|
|
7946
|
-
|
|
7947
|
-
|
|
7948
|
-
const
|
|
7949
|
-
|
|
7950
|
-
|
|
7951
|
-
|
|
7952
|
-
}
|
|
7953
|
-
|
|
7954
|
-
|
|
7955
|
-
|
|
7956
|
-
|
|
7944
|
+
openSubMenu(menu, menuIndex, ev) {
|
|
7945
|
+
const parentMenuEl = ev.currentTarget;
|
|
7946
|
+
if (!parentMenuEl)
|
|
7947
|
+
return;
|
|
7948
|
+
const y = parentMenuEl.getBoundingClientRect().top;
|
|
7949
|
+
this.subMenu.position = {
|
|
7950
|
+
x: this.position.x + this.props.depth * MENU_WIDTH,
|
|
7951
|
+
y: y - (this.subMenu.scrollOffset || 0),
|
|
7952
|
+
};
|
|
7953
|
+
this.subMenu.menuItems = menu.children(this.env);
|
|
7954
|
+
this.subMenu.isOpen = true;
|
|
7955
|
+
this.subMenu.parentMenu = menu;
|
|
7956
|
+
}
|
|
7957
|
+
isParentMenu(subMenu, menuItem) {
|
|
7958
|
+
return subMenu.parentMenu?.id === menuItem.id;
|
|
7959
|
+
}
|
|
7960
|
+
closeSubMenu() {
|
|
7961
|
+
this.subMenu.isOpen = false;
|
|
7962
|
+
this.subMenu.parentMenu = undefined;
|
|
7963
|
+
}
|
|
7964
|
+
onClickMenu(menu, menuIndex, ev) {
|
|
7965
|
+
if (this.isEnabled(menu)) {
|
|
7966
|
+
if (this.isRoot(menu)) {
|
|
7967
|
+
this.openSubMenu(menu, menuIndex, ev);
|
|
7957
7968
|
}
|
|
7958
|
-
|
|
7959
|
-
|
|
7960
|
-
throw new Error(`Path ${root + ":" + path.join(":")} not found`);
|
|
7969
|
+
else {
|
|
7970
|
+
this.activateMenu(menu);
|
|
7961
7971
|
}
|
|
7962
7972
|
}
|
|
7963
|
-
if (!node.children) {
|
|
7964
|
-
node.children = [];
|
|
7965
|
-
}
|
|
7966
|
-
node.children.push(value);
|
|
7967
|
-
return this;
|
|
7968
7973
|
}
|
|
7969
|
-
|
|
7970
|
-
|
|
7974
|
+
onMouseOver(menu, position, ev) {
|
|
7975
|
+
if (this.isEnabled(menu)) {
|
|
7976
|
+
if (this.isRoot(menu)) {
|
|
7977
|
+
this.openSubMenu(menu, position, ev);
|
|
7978
|
+
}
|
|
7979
|
+
else {
|
|
7980
|
+
this.closeSubMenu();
|
|
7981
|
+
}
|
|
7982
|
+
}
|
|
7971
7983
|
}
|
|
7972
7984
|
}
|
|
7973
7985
|
|
|
7974
|
-
//------------------------------------------------------------------------------
|
|
7975
|
-
// Link Menu Registry
|
|
7976
|
-
//------------------------------------------------------------------------------
|
|
7977
|
-
const linkMenuRegistry = new MenuItemRegistry();
|
|
7978
|
-
linkMenuRegistry.add("sheet", {
|
|
7979
|
-
...linkSheet,
|
|
7980
|
-
sequence: 10,
|
|
7981
|
-
});
|
|
7982
|
-
|
|
7983
7986
|
const MENU_OFFSET_X = 320;
|
|
7984
7987
|
const MENU_OFFSET_Y = 100;
|
|
7985
7988
|
const PADDING = 12;
|
|
@@ -9130,119 +9133,6 @@ function getBestTimeUnitForScale(labels, format, locale) {
|
|
|
9130
9133
|
return "year";
|
|
9131
9134
|
}
|
|
9132
9135
|
|
|
9133
|
-
class LineChart extends AbstractChart {
|
|
9134
|
-
dataSets;
|
|
9135
|
-
labelRange;
|
|
9136
|
-
background;
|
|
9137
|
-
verticalAxisPosition;
|
|
9138
|
-
legendPosition;
|
|
9139
|
-
labelsAsText;
|
|
9140
|
-
stacked;
|
|
9141
|
-
aggregated;
|
|
9142
|
-
type = "line";
|
|
9143
|
-
dataSetsHaveTitle;
|
|
9144
|
-
cumulative;
|
|
9145
|
-
constructor(definition, sheetId, getters) {
|
|
9146
|
-
super(definition, sheetId, getters);
|
|
9147
|
-
this.dataSets = createDataSets(this.getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
|
|
9148
|
-
this.labelRange = createRange(this.getters, sheetId, definition.labelRange);
|
|
9149
|
-
this.background = definition.background;
|
|
9150
|
-
this.verticalAxisPosition = definition.verticalAxisPosition;
|
|
9151
|
-
this.legendPosition = definition.legendPosition;
|
|
9152
|
-
this.labelsAsText = definition.labelsAsText;
|
|
9153
|
-
this.stacked = definition.stacked;
|
|
9154
|
-
this.aggregated = definition.aggregated;
|
|
9155
|
-
this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
|
|
9156
|
-
this.cumulative = definition.cumulative;
|
|
9157
|
-
}
|
|
9158
|
-
static validateChartDefinition(validator, definition) {
|
|
9159
|
-
return validator.checkValidations(definition, checkDataset, checkLabelRange);
|
|
9160
|
-
}
|
|
9161
|
-
static transformDefinition(definition, executed) {
|
|
9162
|
-
return transformChartDefinitionWithDataSetsWithZone(definition, executed);
|
|
9163
|
-
}
|
|
9164
|
-
static getDefinitionFromContextCreation(context) {
|
|
9165
|
-
return {
|
|
9166
|
-
background: context.background,
|
|
9167
|
-
dataSets: context.range ? context.range : [],
|
|
9168
|
-
dataSetsHaveTitle: false,
|
|
9169
|
-
labelsAsText: false,
|
|
9170
|
-
legendPosition: "top",
|
|
9171
|
-
title: context.title || "",
|
|
9172
|
-
type: "line",
|
|
9173
|
-
verticalAxisPosition: "left",
|
|
9174
|
-
labelRange: context.auxiliaryRange || undefined,
|
|
9175
|
-
stacked: false,
|
|
9176
|
-
aggregated: false,
|
|
9177
|
-
cumulative: false,
|
|
9178
|
-
};
|
|
9179
|
-
}
|
|
9180
|
-
getDefinition() {
|
|
9181
|
-
return this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange);
|
|
9182
|
-
}
|
|
9183
|
-
getDefinitionWithSpecificDataSets(dataSets, labelRange, targetSheetId) {
|
|
9184
|
-
return {
|
|
9185
|
-
type: "line",
|
|
9186
|
-
dataSetsHaveTitle: dataSets.length ? Boolean(dataSets[0].labelCell) : false,
|
|
9187
|
-
background: this.background,
|
|
9188
|
-
dataSets: dataSets.map((ds) => this.getters.getRangeString(ds.dataRange, targetSheetId || this.sheetId)),
|
|
9189
|
-
legendPosition: this.legendPosition,
|
|
9190
|
-
verticalAxisPosition: this.verticalAxisPosition,
|
|
9191
|
-
labelRange: labelRange
|
|
9192
|
-
? this.getters.getRangeString(labelRange, targetSheetId || this.sheetId)
|
|
9193
|
-
: undefined,
|
|
9194
|
-
title: this.title,
|
|
9195
|
-
labelsAsText: this.labelsAsText,
|
|
9196
|
-
stacked: this.stacked,
|
|
9197
|
-
aggregated: this.aggregated,
|
|
9198
|
-
cumulative: this.cumulative,
|
|
9199
|
-
};
|
|
9200
|
-
}
|
|
9201
|
-
getContextCreation() {
|
|
9202
|
-
return {
|
|
9203
|
-
background: this.background,
|
|
9204
|
-
title: this.title,
|
|
9205
|
-
range: this.dataSets.map((ds) => this.getters.getRangeString(ds.dataRange, this.sheetId)),
|
|
9206
|
-
auxiliaryRange: this.labelRange
|
|
9207
|
-
? this.getters.getRangeString(this.labelRange, this.sheetId)
|
|
9208
|
-
: undefined,
|
|
9209
|
-
};
|
|
9210
|
-
}
|
|
9211
|
-
updateRanges(applyChange) {
|
|
9212
|
-
const { dataSets, labelRange, isStale } = updateChartRangesWithDataSets(this.getters, applyChange, this.dataSets, this.labelRange);
|
|
9213
|
-
if (!isStale) {
|
|
9214
|
-
return this;
|
|
9215
|
-
}
|
|
9216
|
-
const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange);
|
|
9217
|
-
return new LineChart(definition, this.sheetId, this.getters);
|
|
9218
|
-
}
|
|
9219
|
-
getDefinitionForExcel() {
|
|
9220
|
-
// Excel does not support aggregating labels
|
|
9221
|
-
if (this.aggregated)
|
|
9222
|
-
return undefined;
|
|
9223
|
-
const dataSets = this.dataSets
|
|
9224
|
-
.map((ds) => toExcelDataset(this.getters, ds))
|
|
9225
|
-
.filter((ds) => ds.range !== ""); // && range !== CellErrorType.InvalidReference ? show incorrect #ref ?
|
|
9226
|
-
const labelRange = toExcelLabelRange(this.getters, this.labelRange, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
|
|
9227
|
-
return {
|
|
9228
|
-
...this.getDefinition(),
|
|
9229
|
-
backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),
|
|
9230
|
-
fontColor: toXlsxHexColor(chartFontColor(this.background)),
|
|
9231
|
-
dataSets,
|
|
9232
|
-
labelRange,
|
|
9233
|
-
};
|
|
9234
|
-
}
|
|
9235
|
-
copyForSheetId(sheetId) {
|
|
9236
|
-
const dataSets = copyDataSetsWithNewSheetId(this.sheetId, sheetId, this.dataSets);
|
|
9237
|
-
const labelRange = copyLabelRangeWithNewSheetId(this.sheetId, sheetId, this.labelRange);
|
|
9238
|
-
const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange, sheetId);
|
|
9239
|
-
return new LineChart(definition, sheetId, this.getters);
|
|
9240
|
-
}
|
|
9241
|
-
copyInSheetId(sheetId) {
|
|
9242
|
-
const definition = this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange, sheetId);
|
|
9243
|
-
return new LineChart(definition, sheetId, this.getters);
|
|
9244
|
-
}
|
|
9245
|
-
}
|
|
9246
9136
|
function fixEmptyLabelsForDateCharts(labels, dataSetsValues) {
|
|
9247
9137
|
if (labels.length === 0 || labels.every((label) => !label)) {
|
|
9248
9138
|
return { labels, dataSetsValues };
|
|
@@ -9301,7 +9191,23 @@ function canBeLinearChart(labelRange, getters) {
|
|
|
9301
9191
|
}
|
|
9302
9192
|
return true;
|
|
9303
9193
|
}
|
|
9304
|
-
|
|
9194
|
+
let missingTimeAdapterAlreadyWarned = false;
|
|
9195
|
+
function isLuxonTimeAdapterInstalled() {
|
|
9196
|
+
// @ts-ignore
|
|
9197
|
+
if (!window.Chart) {
|
|
9198
|
+
return false;
|
|
9199
|
+
}
|
|
9200
|
+
// @ts-ignore
|
|
9201
|
+
const adapter = new window.Chart._adapters._date({});
|
|
9202
|
+
// @ts-ignore
|
|
9203
|
+
const isInstalled = adapter._id === "luxon";
|
|
9204
|
+
if (!isInstalled && !missingTimeAdapterAlreadyWarned) {
|
|
9205
|
+
missingTimeAdapterAlreadyWarned = true;
|
|
9206
|
+
console.warn("'chartjs-adapter-luxon' time adapter is not installed. Time scale axes are disabled.");
|
|
9207
|
+
}
|
|
9208
|
+
return isInstalled;
|
|
9209
|
+
}
|
|
9210
|
+
function getLineOrScatterConfiguration(chart, labels, localeFormat) {
|
|
9305
9211
|
const fontColor = chartFontColor(chart.background);
|
|
9306
9212
|
const config = getDefaultChartJsRuntime(chart, labels, fontColor, localeFormat);
|
|
9307
9213
|
const legend = {
|
|
@@ -9352,13 +9258,13 @@ function getLineConfiguration(chart, labels, localeFormat) {
|
|
|
9352
9258
|
},
|
|
9353
9259
|
},
|
|
9354
9260
|
};
|
|
9355
|
-
if (chart.stacked && config.options?.scales?.y) {
|
|
9261
|
+
if ("stacked" in chart && chart.stacked && config.options?.scales?.y) {
|
|
9356
9262
|
// @ts-ignore chart.js type is wrong
|
|
9357
9263
|
config.options.scales.y.stacked = true;
|
|
9358
9264
|
}
|
|
9359
9265
|
return config;
|
|
9360
9266
|
}
|
|
9361
|
-
function
|
|
9267
|
+
function createLineOrScatterChartRuntime(chart, getters) {
|
|
9362
9268
|
const axisType = getChartAxisType(chart, getters);
|
|
9363
9269
|
const labelValues = getChartLabelValues(getters, chart.dataSets, chart.labelRange);
|
|
9364
9270
|
let labels = axisType === "linear" ? labelValues.values : labelValues.formattedValues;
|
|
@@ -9378,7 +9284,7 @@ function createLineChartRuntime(chart, getters) {
|
|
|
9378
9284
|
const locale = getters.getLocale();
|
|
9379
9285
|
const dataSetFormat = getChartDatasetFormat(getters, chart.dataSets);
|
|
9380
9286
|
const localeFormat = { format: dataSetFormat, locale };
|
|
9381
|
-
const config =
|
|
9287
|
+
const config = getLineOrScatterConfiguration(chart, labels, localeFormat);
|
|
9382
9288
|
const labelFormat = getChartLabelFormat(getters, chart.labelRange);
|
|
9383
9289
|
if (axisType === "time") {
|
|
9384
9290
|
const axis = {
|
|
@@ -9398,6 +9304,8 @@ function createLineChartRuntime(chart, getters) {
|
|
|
9398
9304
|
});
|
|
9399
9305
|
};
|
|
9400
9306
|
}
|
|
9307
|
+
const stacked = "stacked" in chart ? chart.stacked : false;
|
|
9308
|
+
const cumulative = "cumulative" in chart ? chart.cumulative : false;
|
|
9401
9309
|
const colors = new ChartColors();
|
|
9402
9310
|
for (let [index, { label, data }] of dataSetsValues.entries()) {
|
|
9403
9311
|
if (["linear", "time"].includes(axisType)) {
|
|
@@ -9406,10 +9314,10 @@ function createLineChartRuntime(chart, getters) {
|
|
|
9406
9314
|
}
|
|
9407
9315
|
const color = colors.next();
|
|
9408
9316
|
let backgroundRGBA = colorToRGBA(color);
|
|
9409
|
-
if (
|
|
9317
|
+
if (stacked) {
|
|
9410
9318
|
backgroundRGBA.a = LINE_FILL_TRANSPARENCY;
|
|
9411
9319
|
}
|
|
9412
|
-
if (
|
|
9320
|
+
if (cumulative) {
|
|
9413
9321
|
let accumulator = 0;
|
|
9414
9322
|
data = data.map((value) => {
|
|
9415
9323
|
if (!isNaN(value)) {
|
|
@@ -9427,27 +9335,136 @@ function createLineChartRuntime(chart, getters) {
|
|
|
9427
9335
|
borderColor: color,
|
|
9428
9336
|
backgroundColor,
|
|
9429
9337
|
pointBackgroundColor: color,
|
|
9430
|
-
fill:
|
|
9338
|
+
fill: stacked ? getFillingMode(index) : false,
|
|
9431
9339
|
};
|
|
9432
9340
|
config.data.datasets.push(dataset);
|
|
9433
9341
|
}
|
|
9434
|
-
return {
|
|
9342
|
+
return {
|
|
9343
|
+
chartJsConfig: config,
|
|
9344
|
+
background: chart.background || BACKGROUND_CHART_COLOR,
|
|
9345
|
+
dataSetsValues,
|
|
9346
|
+
labelValues,
|
|
9347
|
+
dataSetFormat,
|
|
9348
|
+
labelFormat,
|
|
9349
|
+
};
|
|
9435
9350
|
}
|
|
9436
|
-
|
|
9437
|
-
|
|
9438
|
-
|
|
9439
|
-
|
|
9440
|
-
|
|
9351
|
+
|
|
9352
|
+
class LineChart extends AbstractChart {
|
|
9353
|
+
dataSets;
|
|
9354
|
+
labelRange;
|
|
9355
|
+
background;
|
|
9356
|
+
verticalAxisPosition;
|
|
9357
|
+
legendPosition;
|
|
9358
|
+
labelsAsText;
|
|
9359
|
+
stacked;
|
|
9360
|
+
aggregated;
|
|
9361
|
+
type = "line";
|
|
9362
|
+
dataSetsHaveTitle;
|
|
9363
|
+
cumulative;
|
|
9364
|
+
constructor(definition, sheetId, getters) {
|
|
9365
|
+
super(definition, sheetId, getters);
|
|
9366
|
+
this.dataSets = createDataSets(this.getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
|
|
9367
|
+
this.labelRange = createRange(this.getters, sheetId, definition.labelRange);
|
|
9368
|
+
this.background = definition.background;
|
|
9369
|
+
this.verticalAxisPosition = definition.verticalAxisPosition;
|
|
9370
|
+
this.legendPosition = definition.legendPosition;
|
|
9371
|
+
this.labelsAsText = definition.labelsAsText;
|
|
9372
|
+
this.stacked = definition.stacked;
|
|
9373
|
+
this.aggregated = definition.aggregated;
|
|
9374
|
+
this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
|
|
9375
|
+
this.cumulative = definition.cumulative;
|
|
9441
9376
|
}
|
|
9442
|
-
|
|
9443
|
-
|
|
9444
|
-
|
|
9445
|
-
|
|
9446
|
-
|
|
9447
|
-
|
|
9448
|
-
|
|
9377
|
+
static validateChartDefinition(validator, definition) {
|
|
9378
|
+
return validator.checkValidations(definition, checkDataset, checkLabelRange);
|
|
9379
|
+
}
|
|
9380
|
+
static transformDefinition(definition, executed) {
|
|
9381
|
+
return transformChartDefinitionWithDataSetsWithZone(definition, executed);
|
|
9382
|
+
}
|
|
9383
|
+
static getDefinitionFromContextCreation(context) {
|
|
9384
|
+
return {
|
|
9385
|
+
background: context.background,
|
|
9386
|
+
dataSets: context.range ? context.range : [],
|
|
9387
|
+
dataSetsHaveTitle: false,
|
|
9388
|
+
labelsAsText: false,
|
|
9389
|
+
legendPosition: "top",
|
|
9390
|
+
title: context.title || "",
|
|
9391
|
+
type: "line",
|
|
9392
|
+
verticalAxisPosition: "left",
|
|
9393
|
+
labelRange: context.auxiliaryRange || undefined,
|
|
9394
|
+
stacked: false,
|
|
9395
|
+
aggregated: false,
|
|
9396
|
+
cumulative: false,
|
|
9397
|
+
};
|
|
9398
|
+
}
|
|
9399
|
+
getDefinition() {
|
|
9400
|
+
return this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange);
|
|
9401
|
+
}
|
|
9402
|
+
getDefinitionWithSpecificDataSets(dataSets, labelRange, targetSheetId) {
|
|
9403
|
+
return {
|
|
9404
|
+
type: "line",
|
|
9405
|
+
dataSetsHaveTitle: dataSets.length ? Boolean(dataSets[0].labelCell) : false,
|
|
9406
|
+
background: this.background,
|
|
9407
|
+
dataSets: dataSets.map((ds) => this.getters.getRangeString(ds.dataRange, targetSheetId || this.sheetId)),
|
|
9408
|
+
legendPosition: this.legendPosition,
|
|
9409
|
+
verticalAxisPosition: this.verticalAxisPosition,
|
|
9410
|
+
labelRange: labelRange
|
|
9411
|
+
? this.getters.getRangeString(labelRange, targetSheetId || this.sheetId)
|
|
9412
|
+
: undefined,
|
|
9413
|
+
title: this.title,
|
|
9414
|
+
labelsAsText: this.labelsAsText,
|
|
9415
|
+
stacked: this.stacked,
|
|
9416
|
+
aggregated: this.aggregated,
|
|
9417
|
+
cumulative: this.cumulative,
|
|
9418
|
+
};
|
|
9419
|
+
}
|
|
9420
|
+
getContextCreation() {
|
|
9421
|
+
return {
|
|
9422
|
+
background: this.background,
|
|
9423
|
+
title: this.title,
|
|
9424
|
+
range: this.dataSets.map((ds) => this.getters.getRangeString(ds.dataRange, this.sheetId)),
|
|
9425
|
+
auxiliaryRange: this.labelRange
|
|
9426
|
+
? this.getters.getRangeString(this.labelRange, this.sheetId)
|
|
9427
|
+
: undefined,
|
|
9428
|
+
};
|
|
9429
|
+
}
|
|
9430
|
+
updateRanges(applyChange) {
|
|
9431
|
+
const { dataSets, labelRange, isStale } = updateChartRangesWithDataSets(this.getters, applyChange, this.dataSets, this.labelRange);
|
|
9432
|
+
if (!isStale) {
|
|
9433
|
+
return this;
|
|
9434
|
+
}
|
|
9435
|
+
const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange);
|
|
9436
|
+
return new LineChart(definition, this.sheetId, this.getters);
|
|
9437
|
+
}
|
|
9438
|
+
getDefinitionForExcel() {
|
|
9439
|
+
// Excel does not support aggregating labels
|
|
9440
|
+
if (this.aggregated)
|
|
9441
|
+
return undefined;
|
|
9442
|
+
const dataSets = this.dataSets
|
|
9443
|
+
.map((ds) => toExcelDataset(this.getters, ds))
|
|
9444
|
+
.filter((ds) => ds.range !== ""); // && range !== CellErrorType.InvalidReference ? show incorrect #ref ?
|
|
9445
|
+
const labelRange = toExcelLabelRange(this.getters, this.labelRange, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
|
|
9446
|
+
return {
|
|
9447
|
+
...this.getDefinition(),
|
|
9448
|
+
backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),
|
|
9449
|
+
fontColor: toXlsxHexColor(chartFontColor(this.background)),
|
|
9450
|
+
dataSets,
|
|
9451
|
+
labelRange,
|
|
9452
|
+
};
|
|
9453
|
+
}
|
|
9454
|
+
copyForSheetId(sheetId) {
|
|
9455
|
+
const dataSets = copyDataSetsWithNewSheetId(this.sheetId, sheetId, this.dataSets);
|
|
9456
|
+
const labelRange = copyLabelRangeWithNewSheetId(this.sheetId, sheetId, this.labelRange);
|
|
9457
|
+
const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange, sheetId);
|
|
9458
|
+
return new LineChart(definition, sheetId, this.getters);
|
|
9459
|
+
}
|
|
9460
|
+
copyInSheetId(sheetId) {
|
|
9461
|
+
const definition = this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange, sheetId);
|
|
9462
|
+
return new LineChart(definition, sheetId, this.getters);
|
|
9449
9463
|
}
|
|
9450
|
-
|
|
9464
|
+
}
|
|
9465
|
+
function createLineChartRuntime(chart, getters) {
|
|
9466
|
+
const { chartJsConfig, background } = createLineOrScatterChartRuntime(chart, getters);
|
|
9467
|
+
return { chartJsConfig, background };
|
|
9451
9468
|
}
|
|
9452
9469
|
|
|
9453
9470
|
class PieChart extends AbstractChart {
|
|
@@ -9649,6 +9666,132 @@ function createPieChartRuntime(chart, getters) {
|
|
|
9649
9666
|
return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
|
|
9650
9667
|
}
|
|
9651
9668
|
|
|
9669
|
+
class ScatterChart extends AbstractChart {
|
|
9670
|
+
dataSets;
|
|
9671
|
+
labelRange;
|
|
9672
|
+
background;
|
|
9673
|
+
verticalAxisPosition;
|
|
9674
|
+
legendPosition;
|
|
9675
|
+
labelsAsText;
|
|
9676
|
+
aggregated;
|
|
9677
|
+
type = "scatter";
|
|
9678
|
+
dataSetsHaveTitle;
|
|
9679
|
+
constructor(definition, sheetId, getters) {
|
|
9680
|
+
super(definition, sheetId, getters);
|
|
9681
|
+
this.dataSets = createDataSets(this.getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
|
|
9682
|
+
this.labelRange = createRange(this.getters, sheetId, definition.labelRange);
|
|
9683
|
+
this.background = definition.background;
|
|
9684
|
+
this.verticalAxisPosition = definition.verticalAxisPosition;
|
|
9685
|
+
this.legendPosition = definition.legendPosition;
|
|
9686
|
+
this.labelsAsText = definition.labelsAsText;
|
|
9687
|
+
this.aggregated = definition.aggregated;
|
|
9688
|
+
this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
|
|
9689
|
+
}
|
|
9690
|
+
static validateChartDefinition(validator, definition) {
|
|
9691
|
+
return validator.checkValidations(definition, checkDataset, checkLabelRange);
|
|
9692
|
+
}
|
|
9693
|
+
static transformDefinition(definition, executed) {
|
|
9694
|
+
return transformChartDefinitionWithDataSetsWithZone(definition, executed);
|
|
9695
|
+
}
|
|
9696
|
+
static getDefinitionFromContextCreation(context) {
|
|
9697
|
+
return {
|
|
9698
|
+
background: context.background,
|
|
9699
|
+
dataSets: context.range ? context.range : [],
|
|
9700
|
+
dataSetsHaveTitle: false,
|
|
9701
|
+
labelsAsText: false,
|
|
9702
|
+
legendPosition: "top",
|
|
9703
|
+
title: context.title || "",
|
|
9704
|
+
type: "scatter",
|
|
9705
|
+
verticalAxisPosition: "left",
|
|
9706
|
+
labelRange: context.auxiliaryRange || undefined,
|
|
9707
|
+
aggregated: false,
|
|
9708
|
+
};
|
|
9709
|
+
}
|
|
9710
|
+
getDefinition() {
|
|
9711
|
+
return this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange);
|
|
9712
|
+
}
|
|
9713
|
+
getDefinitionWithSpecificDataSets(dataSets, labelRange, targetSheetId) {
|
|
9714
|
+
return {
|
|
9715
|
+
type: "scatter",
|
|
9716
|
+
dataSetsHaveTitle: dataSets.length ? Boolean(dataSets[0].labelCell) : false,
|
|
9717
|
+
background: this.background,
|
|
9718
|
+
dataSets: dataSets.map((ds) => this.getters.getRangeString(ds.dataRange, targetSheetId || this.sheetId)),
|
|
9719
|
+
legendPosition: this.legendPosition,
|
|
9720
|
+
verticalAxisPosition: this.verticalAxisPosition,
|
|
9721
|
+
labelRange: labelRange
|
|
9722
|
+
? this.getters.getRangeString(labelRange, targetSheetId || this.sheetId)
|
|
9723
|
+
: undefined,
|
|
9724
|
+
title: this.title,
|
|
9725
|
+
labelsAsText: this.labelsAsText,
|
|
9726
|
+
aggregated: this.aggregated,
|
|
9727
|
+
};
|
|
9728
|
+
}
|
|
9729
|
+
getContextCreation() {
|
|
9730
|
+
return {
|
|
9731
|
+
background: this.background,
|
|
9732
|
+
title: this.title,
|
|
9733
|
+
range: this.dataSets.map((ds) => this.getters.getRangeString(ds.dataRange, this.sheetId)),
|
|
9734
|
+
auxiliaryRange: this.labelRange
|
|
9735
|
+
? this.getters.getRangeString(this.labelRange, this.sheetId)
|
|
9736
|
+
: undefined,
|
|
9737
|
+
};
|
|
9738
|
+
}
|
|
9739
|
+
updateRanges(applyChange) {
|
|
9740
|
+
const { dataSets, labelRange, isStale } = updateChartRangesWithDataSets(this.getters, applyChange, this.dataSets, this.labelRange);
|
|
9741
|
+
if (!isStale) {
|
|
9742
|
+
return this;
|
|
9743
|
+
}
|
|
9744
|
+
const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange);
|
|
9745
|
+
return new ScatterChart(definition, this.sheetId, this.getters);
|
|
9746
|
+
}
|
|
9747
|
+
getDefinitionForExcel() {
|
|
9748
|
+
return undefined; // TODO
|
|
9749
|
+
}
|
|
9750
|
+
copyForSheetId(sheetId) {
|
|
9751
|
+
const dataSets = copyDataSetsWithNewSheetId(this.sheetId, sheetId, this.dataSets);
|
|
9752
|
+
const labelRange = copyLabelRangeWithNewSheetId(this.sheetId, sheetId, this.labelRange);
|
|
9753
|
+
const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange, sheetId);
|
|
9754
|
+
return new ScatterChart(definition, sheetId, this.getters);
|
|
9755
|
+
}
|
|
9756
|
+
copyInSheetId(sheetId) {
|
|
9757
|
+
const definition = this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange, sheetId);
|
|
9758
|
+
return new ScatterChart(definition, sheetId, this.getters);
|
|
9759
|
+
}
|
|
9760
|
+
}
|
|
9761
|
+
function createScatterChartRuntime(chart, getters) {
|
|
9762
|
+
const { chartJsConfig, background, dataSetsValues, dataSetFormat, labelValues, labelFormat } = createLineOrScatterChartRuntime(chart, getters);
|
|
9763
|
+
// use chartJS line chart and disable the lines instead of chartJS scatter chart. This is because the scatter chart
|
|
9764
|
+
// have less options than the line chart (it only works with linear labels)
|
|
9765
|
+
chartJsConfig.type = "line";
|
|
9766
|
+
const configOptions = chartJsConfig.options;
|
|
9767
|
+
configOptions.elements = {
|
|
9768
|
+
point: {
|
|
9769
|
+
radius: 3,
|
|
9770
|
+
hoverRadius: 3,
|
|
9771
|
+
hitRadius: 8,
|
|
9772
|
+
},
|
|
9773
|
+
};
|
|
9774
|
+
const locale = getters.getLocale();
|
|
9775
|
+
configOptions.plugins.tooltip.callbacks.title = () => "";
|
|
9776
|
+
configOptions.plugins.tooltip.callbacks.label = (tooltipItem) => {
|
|
9777
|
+
const dataSetPoint = dataSetsValues[tooltipItem.datasetIndex].data[tooltipItem.dataIndex];
|
|
9778
|
+
let label = tooltipItem.label || labelValues.values[tooltipItem.dataIndex];
|
|
9779
|
+
if (isNumber(label, locale)) {
|
|
9780
|
+
label = toNumber(label, locale);
|
|
9781
|
+
}
|
|
9782
|
+
const formattedX = formatValue(label, { locale, format: labelFormat });
|
|
9783
|
+
const formattedY = formatValue(dataSetPoint, { locale, format: dataSetFormat });
|
|
9784
|
+
const dataSetTitle = tooltipItem.dataset.label;
|
|
9785
|
+
return formattedX
|
|
9786
|
+
? `${dataSetTitle}: (${formattedX}, ${formattedY})`
|
|
9787
|
+
: `${dataSetTitle}: ${formattedY}`;
|
|
9788
|
+
};
|
|
9789
|
+
for (const dataSet of chartJsConfig.data.datasets) {
|
|
9790
|
+
dataSet.showLine = false;
|
|
9791
|
+
}
|
|
9792
|
+
return { chartJsConfig, background };
|
|
9793
|
+
}
|
|
9794
|
+
|
|
9652
9795
|
/**
|
|
9653
9796
|
* This registry is intended to map a cell content (raw string) to
|
|
9654
9797
|
* an instance of a cell.
|
|
@@ -9704,11 +9847,22 @@ chartRegistry.add("gauge", {
|
|
|
9704
9847
|
name: _t("Gauge"),
|
|
9705
9848
|
sequence: 50,
|
|
9706
9849
|
});
|
|
9850
|
+
chartRegistry.add("scatter", {
|
|
9851
|
+
match: (type) => type === "scatter",
|
|
9852
|
+
createChart: (definition, sheetId, getters) => new ScatterChart(definition, sheetId, getters),
|
|
9853
|
+
getChartRuntime: createScatterChartRuntime,
|
|
9854
|
+
validateChartDefinition: (validator, definition) => ScatterChart.validateChartDefinition(validator, definition),
|
|
9855
|
+
transformDefinition: (definition, executed) => ScatterChart.transformDefinition(definition, executed),
|
|
9856
|
+
getChartDefinitionFromContextCreation: (context) => ScatterChart.getDefinitionFromContextCreation(context),
|
|
9857
|
+
name: _t("Scatter"),
|
|
9858
|
+
sequence: 60,
|
|
9859
|
+
});
|
|
9707
9860
|
const chartComponentRegistry = new Registry();
|
|
9708
9861
|
chartComponentRegistry.add("line", ChartJsComponent);
|
|
9709
9862
|
chartComponentRegistry.add("bar", ChartJsComponent);
|
|
9710
9863
|
chartComponentRegistry.add("pie", ChartJsComponent);
|
|
9711
9864
|
chartComponentRegistry.add("gauge", ChartJsComponent);
|
|
9865
|
+
chartComponentRegistry.add("scatter", ChartJsComponent);
|
|
9712
9866
|
chartComponentRegistry.add("scorecard", ScorecardChart);
|
|
9713
9867
|
|
|
9714
9868
|
/**
|
|
@@ -19647,9 +19801,6 @@ const categories = [
|
|
|
19647
19801
|
{ name: _t("Web"), functions: web },
|
|
19648
19802
|
];
|
|
19649
19803
|
const functionNameRegex = /^[A-Z0-9\_\.]+$/;
|
|
19650
|
-
//------------------------------------------------------------------------------
|
|
19651
|
-
// Function registry
|
|
19652
|
-
//------------------------------------------------------------------------------
|
|
19653
19804
|
class FunctionRegistry extends Registry {
|
|
19654
19805
|
mapping = {};
|
|
19655
19806
|
add(name, addDescr) {
|
|
@@ -19935,6 +20086,58 @@ const insertLink = {
|
|
|
19935
20086
|
execute: INSERT_LINK,
|
|
19936
20087
|
icon: "o-spreadsheet-Icon.INSERT_LINK",
|
|
19937
20088
|
};
|
|
20089
|
+
const insertCheckbox = {
|
|
20090
|
+
name: _t("Checkbox"),
|
|
20091
|
+
execute: (env) => {
|
|
20092
|
+
const zones = env.model.getters.getSelectedZones();
|
|
20093
|
+
const sheetId = env.model.getters.getActiveSheetId();
|
|
20094
|
+
const ranges = zones.map((zone) => env.model.getters.getRangeDataFromZone(sheetId, zone));
|
|
20095
|
+
env.model.dispatch("ADD_DATA_VALIDATION_RULE", {
|
|
20096
|
+
ranges,
|
|
20097
|
+
sheetId,
|
|
20098
|
+
rule: {
|
|
20099
|
+
id: env.model.uuidGenerator.uuidv4(),
|
|
20100
|
+
criterion: {
|
|
20101
|
+
type: "isBoolean",
|
|
20102
|
+
values: [],
|
|
20103
|
+
},
|
|
20104
|
+
},
|
|
20105
|
+
});
|
|
20106
|
+
},
|
|
20107
|
+
icon: "o-spreadsheet-Icon.INSERT_CHECKBOX",
|
|
20108
|
+
};
|
|
20109
|
+
const insertDropdown = {
|
|
20110
|
+
name: _t("Dropdown list"),
|
|
20111
|
+
execute: (env) => {
|
|
20112
|
+
const zones = env.model.getters.getSelectedZones();
|
|
20113
|
+
const sheetId = env.model.getters.getActiveSheetId();
|
|
20114
|
+
const ranges = zones.map((zone) => env.model.getters.getRangeDataFromZone(sheetId, zone));
|
|
20115
|
+
const ruleID = env.model.uuidGenerator.uuidv4();
|
|
20116
|
+
env.model.dispatch("ADD_DATA_VALIDATION_RULE", {
|
|
20117
|
+
ranges,
|
|
20118
|
+
sheetId,
|
|
20119
|
+
rule: {
|
|
20120
|
+
id: ruleID,
|
|
20121
|
+
criterion: {
|
|
20122
|
+
type: "isValueInList",
|
|
20123
|
+
values: [],
|
|
20124
|
+
displayStyle: "arrow",
|
|
20125
|
+
},
|
|
20126
|
+
},
|
|
20127
|
+
});
|
|
20128
|
+
const rule = env.model.getters.getDataValidationRule(sheetId, ruleID);
|
|
20129
|
+
if (!rule) {
|
|
20130
|
+
return;
|
|
20131
|
+
}
|
|
20132
|
+
env.openSidePanel("DataValidationEditor", {
|
|
20133
|
+
rule: localizeDataValidationRule(rule, env.model.getters.getLocale()),
|
|
20134
|
+
onExit: () => {
|
|
20135
|
+
env.openSidePanel("DataValidation");
|
|
20136
|
+
},
|
|
20137
|
+
});
|
|
20138
|
+
},
|
|
20139
|
+
icon: "o-spreadsheet-Icon.INSERT_DROPDOWN",
|
|
20140
|
+
};
|
|
19938
20141
|
const insertSheet = {
|
|
19939
20142
|
name: _t("Insert sheet"),
|
|
19940
20143
|
execute: (env) => {
|
|
@@ -21337,7 +21540,7 @@ topbarMenuRegistry
|
|
|
21337
21540
|
})
|
|
21338
21541
|
.addChild("insert_cell", ["insert"], {
|
|
21339
21542
|
...insertCell,
|
|
21340
|
-
sequence:
|
|
21543
|
+
sequence: 30,
|
|
21341
21544
|
})
|
|
21342
21545
|
.addChild("insert_cell_down", ["insert", "insert_cell"], {
|
|
21343
21546
|
...insertCellShiftDown,
|
|
@@ -21351,7 +21554,7 @@ topbarMenuRegistry
|
|
|
21351
21554
|
})
|
|
21352
21555
|
.addChild("insert_sheet", ["insert"], {
|
|
21353
21556
|
...insertSheet,
|
|
21354
|
-
sequence:
|
|
21557
|
+
sequence: 40,
|
|
21355
21558
|
separator: true,
|
|
21356
21559
|
})
|
|
21357
21560
|
.addChild("insert_chart", ["insert"], {
|
|
@@ -21396,6 +21599,15 @@ topbarMenuRegistry
|
|
|
21396
21599
|
...insertLink,
|
|
21397
21600
|
separator: true,
|
|
21398
21601
|
sequence: 70,
|
|
21602
|
+
})
|
|
21603
|
+
.addChild("insert_checkbox", ["insert"], {
|
|
21604
|
+
...insertCheckbox,
|
|
21605
|
+
sequence: 80,
|
|
21606
|
+
})
|
|
21607
|
+
.addChild("insert_dropdown", ["insert"], {
|
|
21608
|
+
...insertDropdown,
|
|
21609
|
+
separator: true,
|
|
21610
|
+
sequence: 90,
|
|
21399
21611
|
})
|
|
21400
21612
|
// ---------------------------------------------------------------------
|
|
21401
21613
|
// FORMAT MENU ITEMS
|
|
@@ -22604,7 +22816,7 @@ class ChartColor extends Component {
|
|
|
22604
22816
|
|
|
22605
22817
|
class ChartTitle extends Component {
|
|
22606
22818
|
static template = "o-spreadsheet.ChartTitle";
|
|
22607
|
-
static components = {
|
|
22819
|
+
static components = { Section };
|
|
22608
22820
|
static props = { title: String, update: Function };
|
|
22609
22821
|
updateTitle(ev) {
|
|
22610
22822
|
this.props.update(ev.target.value);
|
|
@@ -22613,7 +22825,7 @@ class ChartTitle extends Component {
|
|
|
22613
22825
|
|
|
22614
22826
|
class LineBarPieDesignPanel extends Component {
|
|
22615
22827
|
static template = "o-spreadsheet-LineBarPieDesignPanel";
|
|
22616
|
-
static components = { ChartColor,
|
|
22828
|
+
static components = { ChartColor, ChartTitle, Section };
|
|
22617
22829
|
static props = {
|
|
22618
22830
|
figureId: String,
|
|
22619
22831
|
definition: Object,
|
|
@@ -22644,7 +22856,7 @@ class BarChartDesignPanel extends LineBarPieDesignPanel {
|
|
|
22644
22856
|
|
|
22645
22857
|
class GaugeChartConfigPanel extends Component {
|
|
22646
22858
|
static template = "o-spreadsheet-GaugeChartConfigPanel";
|
|
22647
|
-
static components = {
|
|
22859
|
+
static components = { ChartErrorSection, ChartDataSeries };
|
|
22648
22860
|
static props = {
|
|
22649
22861
|
figureId: String,
|
|
22650
22862
|
definition: Object,
|
|
@@ -22843,9 +23055,37 @@ class LineChartDesignPanel extends LineBarPieDesignPanel {
|
|
|
22843
23055
|
static template = "o-spreadsheet-LineChartDesignPanel";
|
|
22844
23056
|
}
|
|
22845
23057
|
|
|
23058
|
+
class ScatterConfigPanel extends LineBarPieConfigPanel {
|
|
23059
|
+
static template = "o-spreadsheet-ScatterConfigPanel";
|
|
23060
|
+
get canTreatLabelsAsText() {
|
|
23061
|
+
const chart = this.env.model.getters.getChart(this.props.figureId);
|
|
23062
|
+
if (chart && chart instanceof ScatterChart) {
|
|
23063
|
+
return canChartParseLabels(chart.labelRange, this.env.model.getters);
|
|
23064
|
+
}
|
|
23065
|
+
return false;
|
|
23066
|
+
}
|
|
23067
|
+
onUpdateLabelsAsText(labelsAsText) {
|
|
23068
|
+
this.props.updateChart(this.props.figureId, {
|
|
23069
|
+
labelsAsText,
|
|
23070
|
+
});
|
|
23071
|
+
}
|
|
23072
|
+
getLabelRangeOptions() {
|
|
23073
|
+
const options = super.getLabelRangeOptions();
|
|
23074
|
+
if (this.canTreatLabelsAsText) {
|
|
23075
|
+
options.push({
|
|
23076
|
+
name: "labelsAsText",
|
|
23077
|
+
value: this.props.definition.labelsAsText,
|
|
23078
|
+
label: _t("Treat labels as text"),
|
|
23079
|
+
onChange: this.onUpdateLabelsAsText.bind(this),
|
|
23080
|
+
});
|
|
23081
|
+
}
|
|
23082
|
+
return options;
|
|
23083
|
+
}
|
|
23084
|
+
}
|
|
23085
|
+
|
|
22846
23086
|
class ScorecardChartConfigPanel extends Component {
|
|
22847
23087
|
static template = "o-spreadsheet-ScorecardChartConfigPanel";
|
|
22848
|
-
static components = { SelectionInput,
|
|
23088
|
+
static components = { SelectionInput, ChartErrorSection, Section };
|
|
22849
23089
|
static props = {
|
|
22850
23090
|
figureId: String,
|
|
22851
23091
|
definition: Object,
|
|
@@ -22963,6 +23203,10 @@ chartSidePanelComponentRegistry
|
|
|
22963
23203
|
.add("line", {
|
|
22964
23204
|
configuration: LineConfigPanel,
|
|
22965
23205
|
design: LineChartDesignPanel,
|
|
23206
|
+
})
|
|
23207
|
+
.add("scatter", {
|
|
23208
|
+
configuration: ScatterConfigPanel,
|
|
23209
|
+
design: LineChartDesignPanel,
|
|
22966
23210
|
})
|
|
22967
23211
|
.add("bar", {
|
|
22968
23212
|
configuration: BarConfigPanel,
|
|
@@ -24296,377 +24540,6 @@ class CustomCurrencyPanel extends Component {
|
|
|
24296
24540
|
}
|
|
24297
24541
|
}
|
|
24298
24542
|
|
|
24299
|
-
css /* scss */ `
|
|
24300
|
-
.o-find-and-replace {
|
|
24301
|
-
outline: none;
|
|
24302
|
-
height: 100%;
|
|
24303
|
-
.o-input-search-container {
|
|
24304
|
-
display: flex;
|
|
24305
|
-
.o-input-with-count {
|
|
24306
|
-
flex-grow: 1;
|
|
24307
|
-
width: auto;
|
|
24308
|
-
}
|
|
24309
|
-
.o-input-without-count {
|
|
24310
|
-
width: 100%;
|
|
24311
|
-
}
|
|
24312
|
-
.o-input-count {
|
|
24313
|
-
width: fit-content;
|
|
24314
|
-
padding: 4px 0 4px 4px;
|
|
24315
|
-
}
|
|
24316
|
-
}
|
|
24317
|
-
|
|
24318
|
-
.o-matches-count div {
|
|
24319
|
-
text-overflow: ellipsis;
|
|
24320
|
-
overflow: hidden;
|
|
24321
|
-
white-space: nowrap;
|
|
24322
|
-
}
|
|
24323
|
-
}
|
|
24324
|
-
`;
|
|
24325
|
-
class FindAndReplacePanel extends Component {
|
|
24326
|
-
static template = "o-spreadsheet-FindAndReplacePanel";
|
|
24327
|
-
static components = { SelectionInput, Section, Checkbox };
|
|
24328
|
-
static props = {
|
|
24329
|
-
onCloseSidePanel: Function,
|
|
24330
|
-
};
|
|
24331
|
-
debounceTimeoutId;
|
|
24332
|
-
initialShowFormulaState = false;
|
|
24333
|
-
dataRange = "";
|
|
24334
|
-
searchInput = useRef("searchInput");
|
|
24335
|
-
replaceInput = useRef("replaceInput");
|
|
24336
|
-
get hasSearchResult() {
|
|
24337
|
-
return this.env.model.getters.getCurrentSelectedMatchIndex() !== null;
|
|
24338
|
-
}
|
|
24339
|
-
get pendingSearch() {
|
|
24340
|
-
return this.debounceTimeoutId !== undefined;
|
|
24341
|
-
}
|
|
24342
|
-
get searchOptions() {
|
|
24343
|
-
return this.env.model.getters.getSearchOptions();
|
|
24344
|
-
}
|
|
24345
|
-
get toSearch() {
|
|
24346
|
-
return this.searchInput.el?.value || "";
|
|
24347
|
-
}
|
|
24348
|
-
get toReplace() {
|
|
24349
|
-
return this.replaceInput.el?.value || "";
|
|
24350
|
-
}
|
|
24351
|
-
get allSheetsMatchesCount() {
|
|
24352
|
-
return _t("%s in all sheets", this.env.model.getters.getAllSheetMatchesCount());
|
|
24353
|
-
}
|
|
24354
|
-
get currentSheetMatchesCount() {
|
|
24355
|
-
return _t("%(matches)s in sheet %(sheetName)s", {
|
|
24356
|
-
matches: this.env.model.getters.getActiveSheetMatchesCount(),
|
|
24357
|
-
sheetName: this.env.model.getters.getSheetName(this.env.model.getters.getActiveSheetId()),
|
|
24358
|
-
});
|
|
24359
|
-
}
|
|
24360
|
-
get specificRangeMatchesCount() {
|
|
24361
|
-
const range = this.searchOptions.specificRange;
|
|
24362
|
-
if (!range) {
|
|
24363
|
-
return "";
|
|
24364
|
-
}
|
|
24365
|
-
const { _sheetId, _zone } = range;
|
|
24366
|
-
return _t("%(matches)s in range %(range)s of sheet %(sheetName)s", {
|
|
24367
|
-
matches: this.env.model.getters.getSpecificRangeMatchesCount().toString(),
|
|
24368
|
-
range: zoneToXc(_zone),
|
|
24369
|
-
sheetName: this.env.model.getters.getSheetName(_sheetId),
|
|
24370
|
-
});
|
|
24371
|
-
}
|
|
24372
|
-
setup() {
|
|
24373
|
-
this.initialShowFormulaState = this.env.model.getters.shouldShowFormulas();
|
|
24374
|
-
onMounted(() => this.searchInput.el?.focus());
|
|
24375
|
-
onWillUnmount(() => {
|
|
24376
|
-
clearTimeout(this.debounceTimeoutId);
|
|
24377
|
-
this.env.model.dispatch("CLEAR_SEARCH");
|
|
24378
|
-
this.env.model.dispatch("SET_FORMULA_VISIBILITY", { show: this.initialShowFormulaState });
|
|
24379
|
-
});
|
|
24380
|
-
useEffect(() => {
|
|
24381
|
-
const showFormula = this.env.model.getters.shouldShowFormulas();
|
|
24382
|
-
this.updateSearch({ searchFormulas: showFormula });
|
|
24383
|
-
}, () => [this.env.model.getters.shouldShowFormulas()]);
|
|
24384
|
-
}
|
|
24385
|
-
onFocusSearch() {
|
|
24386
|
-
this.updateDataRange();
|
|
24387
|
-
}
|
|
24388
|
-
onInput() {
|
|
24389
|
-
this.debouncedUpdateSearch();
|
|
24390
|
-
}
|
|
24391
|
-
onKeydownSearch(ev) {
|
|
24392
|
-
if (ev.key === "Enter") {
|
|
24393
|
-
ev.preventDefault();
|
|
24394
|
-
ev.stopPropagation();
|
|
24395
|
-
this.onSelectNextCell();
|
|
24396
|
-
}
|
|
24397
|
-
}
|
|
24398
|
-
onKeydownReplace(ev) {
|
|
24399
|
-
if (ev.key === "Enter") {
|
|
24400
|
-
ev.preventDefault();
|
|
24401
|
-
ev.stopPropagation();
|
|
24402
|
-
this.replace();
|
|
24403
|
-
}
|
|
24404
|
-
}
|
|
24405
|
-
searchFormulas(showFormula) {
|
|
24406
|
-
this.env.model.dispatch("SET_FORMULA_VISIBILITY", {
|
|
24407
|
-
show: showFormula,
|
|
24408
|
-
});
|
|
24409
|
-
this.updateSearch({ searchFormulas: showFormula });
|
|
24410
|
-
}
|
|
24411
|
-
searchExactMatch(exactMatch) {
|
|
24412
|
-
this.updateSearch({ exactMatch });
|
|
24413
|
-
}
|
|
24414
|
-
searchMatchCase(matchCase) {
|
|
24415
|
-
this.updateSearch({ matchCase });
|
|
24416
|
-
}
|
|
24417
|
-
changeSearchScope(ev) {
|
|
24418
|
-
const searchScope = ev.target.value;
|
|
24419
|
-
this.updateSearch({ searchScope });
|
|
24420
|
-
}
|
|
24421
|
-
onSearchRangeChanged(ranges) {
|
|
24422
|
-
this.dataRange = ranges[0];
|
|
24423
|
-
}
|
|
24424
|
-
updateDataRange() {
|
|
24425
|
-
if (!this.dataRange) {
|
|
24426
|
-
return;
|
|
24427
|
-
}
|
|
24428
|
-
if (this.searchOptions.searchScope === "specificRange") {
|
|
24429
|
-
const specificRange = this.env.model.getters.getRangeFromSheetXC(this.env.model.getters.getActiveSheetId(), this.dataRange).rangeData;
|
|
24430
|
-
this.updateSearch({ specificRange });
|
|
24431
|
-
}
|
|
24432
|
-
}
|
|
24433
|
-
onSelectPreviousCell() {
|
|
24434
|
-
this.env.model.dispatch("SELECT_SEARCH_PREVIOUS_MATCH");
|
|
24435
|
-
}
|
|
24436
|
-
onSelectNextCell() {
|
|
24437
|
-
this.env.model.dispatch("SELECT_SEARCH_NEXT_MATCH");
|
|
24438
|
-
}
|
|
24439
|
-
updateSearch(updateSearchOptions) {
|
|
24440
|
-
const searchOptions = {
|
|
24441
|
-
...this.env.model.getters.getSearchOptions(),
|
|
24442
|
-
...updateSearchOptions,
|
|
24443
|
-
};
|
|
24444
|
-
this.env.model.dispatch("UPDATE_SEARCH", {
|
|
24445
|
-
toSearch: this.toSearch,
|
|
24446
|
-
searchOptions,
|
|
24447
|
-
});
|
|
24448
|
-
}
|
|
24449
|
-
debouncedUpdateSearch() {
|
|
24450
|
-
clearTimeout(this.debounceTimeoutId);
|
|
24451
|
-
this.debounceTimeoutId = setTimeout(() => {
|
|
24452
|
-
this.updateSearch();
|
|
24453
|
-
this.debounceTimeoutId = undefined;
|
|
24454
|
-
}, 200);
|
|
24455
|
-
}
|
|
24456
|
-
replace() {
|
|
24457
|
-
this.env.model.dispatch("REPLACE_SEARCH", {
|
|
24458
|
-
replaceWith: this.toReplace,
|
|
24459
|
-
});
|
|
24460
|
-
}
|
|
24461
|
-
replaceAll() {
|
|
24462
|
-
this.env.model.dispatch("REPLACE_ALL_SEARCH", {
|
|
24463
|
-
replaceWith: this.toReplace,
|
|
24464
|
-
});
|
|
24465
|
-
}
|
|
24466
|
-
}
|
|
24467
|
-
|
|
24468
|
-
css /* scss */ `
|
|
24469
|
-
.o-more-formats-panel {
|
|
24470
|
-
.format-preview {
|
|
24471
|
-
height: 48px;
|
|
24472
|
-
background-color: white;
|
|
24473
|
-
|
|
24474
|
-
&:hover {
|
|
24475
|
-
background-color: rgba(0, 0, 0, 0.08);
|
|
24476
|
-
}
|
|
24477
|
-
}
|
|
24478
|
-
.check-icon {
|
|
24479
|
-
width: 24px;
|
|
24480
|
-
}
|
|
24481
|
-
}
|
|
24482
|
-
`;
|
|
24483
|
-
const DATE_FORMAT_ACTIONS = createActions([
|
|
24484
|
-
formatNumberFullDateTime,
|
|
24485
|
-
formatNumberFullWeekDayAndMonth,
|
|
24486
|
-
formatNumberDayAndFullMonth,
|
|
24487
|
-
formatNumberShortWeekDay,
|
|
24488
|
-
formatNumberDayAndShortMonth,
|
|
24489
|
-
formatNumberFullMonth,
|
|
24490
|
-
formatNumberShortMonth,
|
|
24491
|
-
formatNumberDate,
|
|
24492
|
-
formatNumberTime,
|
|
24493
|
-
formatNumberDateTime,
|
|
24494
|
-
formatNumberDuration,
|
|
24495
|
-
]);
|
|
24496
|
-
class MoreFormatsPanel extends Component {
|
|
24497
|
-
static template = "o-spreadsheet-MoreFormatsPanel";
|
|
24498
|
-
static props = {
|
|
24499
|
-
onCloseSidePanel: Function,
|
|
24500
|
-
};
|
|
24501
|
-
get dateFormatsActions() {
|
|
24502
|
-
return DATE_FORMAT_ACTIONS;
|
|
24503
|
-
}
|
|
24504
|
-
}
|
|
24505
|
-
|
|
24506
|
-
css /* scss */ `
|
|
24507
|
-
.o-checkbox-selection {
|
|
24508
|
-
height: 150px;
|
|
24509
|
-
}
|
|
24510
|
-
`;
|
|
24511
|
-
class RemoveDuplicatesPanel extends Component {
|
|
24512
|
-
static template = "o-spreadsheet-RemoveDuplicatesPanel";
|
|
24513
|
-
static components = { ValidationMessages, Section, Checkbox };
|
|
24514
|
-
state = useState({
|
|
24515
|
-
hasHeader: false,
|
|
24516
|
-
columns: {},
|
|
24517
|
-
});
|
|
24518
|
-
setup() {
|
|
24519
|
-
onWillUpdateProps(() => this.updateColumns());
|
|
24520
|
-
}
|
|
24521
|
-
toggleHasHeader() {
|
|
24522
|
-
this.state.hasHeader = !this.state.hasHeader;
|
|
24523
|
-
}
|
|
24524
|
-
toggleAllColumns() {
|
|
24525
|
-
const newState = !this.isEveryColumnSelected;
|
|
24526
|
-
for (const index in this.state.columns) {
|
|
24527
|
-
this.state.columns[index] = newState;
|
|
24528
|
-
}
|
|
24529
|
-
}
|
|
24530
|
-
toggleColumn(colIndex) {
|
|
24531
|
-
this.state.columns[colIndex] = !this.state.columns[colIndex];
|
|
24532
|
-
}
|
|
24533
|
-
onRemoveDuplicates() {
|
|
24534
|
-
this.env.model.dispatch("REMOVE_DUPLICATES", {
|
|
24535
|
-
hasHeader: this.state.hasHeader,
|
|
24536
|
-
columns: this.getColsToAnalyze(),
|
|
24537
|
-
});
|
|
24538
|
-
}
|
|
24539
|
-
getColLabel(colKey) {
|
|
24540
|
-
const col = parseInt(colKey);
|
|
24541
|
-
let colLabel = _t("Column %s", numberToLetters(col));
|
|
24542
|
-
if (this.state.hasHeader) {
|
|
24543
|
-
const sheetId = this.env.model.getters.getActiveSheetId();
|
|
24544
|
-
const row = this.env.model.getters.getSelectedZone().top;
|
|
24545
|
-
const colHeader = this.env.model.getters.getEvaluatedCell({ sheetId, col, row });
|
|
24546
|
-
if (colHeader.type !== "empty") {
|
|
24547
|
-
colLabel += ` - ${colHeader.value}`;
|
|
24548
|
-
}
|
|
24549
|
-
}
|
|
24550
|
-
return colLabel;
|
|
24551
|
-
}
|
|
24552
|
-
get isEveryColumnSelected() {
|
|
24553
|
-
return Object.values(this.state.columns).every((value) => value === true);
|
|
24554
|
-
}
|
|
24555
|
-
get errorMessages() {
|
|
24556
|
-
const cancelledReasons = this.env.model.canDispatch("REMOVE_DUPLICATES", {
|
|
24557
|
-
hasHeader: this.state.hasHeader,
|
|
24558
|
-
columns: this.getColsToAnalyze(),
|
|
24559
|
-
}).reasons;
|
|
24560
|
-
const errors = new Set();
|
|
24561
|
-
for (const reason of cancelledReasons) {
|
|
24562
|
-
errors.add(RemoveDuplicateTerms.Errors[reason] || RemoveDuplicateTerms.Errors.Unexpected);
|
|
24563
|
-
}
|
|
24564
|
-
return Array.from(errors);
|
|
24565
|
-
}
|
|
24566
|
-
get selectionStatisticalInformation() {
|
|
24567
|
-
const dimension = zoneToDimension(this.env.model.getters.getSelectedZone());
|
|
24568
|
-
return _t("%(row_count)s rows and %(column_count)s columns selected", {
|
|
24569
|
-
row_count: dimension.numberOfRows,
|
|
24570
|
-
column_count: dimension.numberOfCols,
|
|
24571
|
-
});
|
|
24572
|
-
}
|
|
24573
|
-
get canConfirm() {
|
|
24574
|
-
return this.errorMessages.length === 0;
|
|
24575
|
-
}
|
|
24576
|
-
// ---------------------------------------------------------------------------
|
|
24577
|
-
// Private
|
|
24578
|
-
// ---------------------------------------------------------------------------
|
|
24579
|
-
updateColumns() {
|
|
24580
|
-
const zone = this.env.model.getters.getSelectedZone();
|
|
24581
|
-
const oldColumns = this.state.columns;
|
|
24582
|
-
const newColumns = {};
|
|
24583
|
-
for (let i = zone.left; i <= zone.right; i++) {
|
|
24584
|
-
newColumns[i] = i in oldColumns ? oldColumns[i] : true;
|
|
24585
|
-
}
|
|
24586
|
-
this.state.columns = newColumns;
|
|
24587
|
-
}
|
|
24588
|
-
getColsToAnalyze() {
|
|
24589
|
-
return Object.keys(this.state.columns)
|
|
24590
|
-
.filter((colIndex) => this.state.columns[colIndex])
|
|
24591
|
-
.map((colIndex) => parseInt(colIndex));
|
|
24592
|
-
}
|
|
24593
|
-
}
|
|
24594
|
-
|
|
24595
|
-
css /* scss */ `
|
|
24596
|
-
.o-locale-preview {
|
|
24597
|
-
color: dimgrey;
|
|
24598
|
-
}
|
|
24599
|
-
`;
|
|
24600
|
-
class SettingsPanel extends Component {
|
|
24601
|
-
static template = "o-spreadsheet-SettingsPanel";
|
|
24602
|
-
static components = { Section };
|
|
24603
|
-
static props = { onCloseSidePanel: Function };
|
|
24604
|
-
loadedLocales = [];
|
|
24605
|
-
setup() {
|
|
24606
|
-
onWillStart(() => this.loadLocales());
|
|
24607
|
-
}
|
|
24608
|
-
onLocaleChange(code) {
|
|
24609
|
-
const locale = this.loadedLocales.find((l) => l.code === code);
|
|
24610
|
-
if (!locale)
|
|
24611
|
-
return;
|
|
24612
|
-
this.env.model.dispatch("UPDATE_LOCALE", { locale });
|
|
24613
|
-
}
|
|
24614
|
-
async loadLocales() {
|
|
24615
|
-
this.loadedLocales = (await this.env.loadLocales())
|
|
24616
|
-
.filter(isValidLocale)
|
|
24617
|
-
.sort((a, b) => a.name.localeCompare(b.name));
|
|
24618
|
-
}
|
|
24619
|
-
get numberFormatPreview() {
|
|
24620
|
-
const locale = this.env.model.getters.getLocale();
|
|
24621
|
-
return formatValue(1234567.89, { format: "#,##0.00", locale });
|
|
24622
|
-
}
|
|
24623
|
-
get dateFormatPreview() {
|
|
24624
|
-
const locale = this.env.model.getters.getLocale();
|
|
24625
|
-
return formatValue(1.6, { format: locale.dateFormat, locale });
|
|
24626
|
-
}
|
|
24627
|
-
get dateTimeFormatPreview() {
|
|
24628
|
-
const locale = this.env.model.getters.getLocale();
|
|
24629
|
-
const dateTimeFormat = getDateTimeFormat(locale);
|
|
24630
|
-
return formatValue(1.6, { format: dateTimeFormat, locale });
|
|
24631
|
-
}
|
|
24632
|
-
get currentLocale() {
|
|
24633
|
-
return this.env.model.getters.getLocale();
|
|
24634
|
-
}
|
|
24635
|
-
get supportedLocales() {
|
|
24636
|
-
const currentLocale = this.currentLocale;
|
|
24637
|
-
const localeInLoadedLocales = this.loadedLocales.find((l) => l.code === currentLocale.code);
|
|
24638
|
-
if (!localeInLoadedLocales) {
|
|
24639
|
-
const locales = [...this.loadedLocales, currentLocale].sort((a, b) => a.name.localeCompare(b.name));
|
|
24640
|
-
return locales;
|
|
24641
|
-
}
|
|
24642
|
-
else if (!deepEquals(currentLocale, localeInLoadedLocales)) {
|
|
24643
|
-
const index = this.loadedLocales.indexOf(localeInLoadedLocales);
|
|
24644
|
-
const locales = [...this.loadedLocales];
|
|
24645
|
-
locales[index] = currentLocale;
|
|
24646
|
-
locales.sort((a, b) => a.name.localeCompare(b.name));
|
|
24647
|
-
return locales;
|
|
24648
|
-
}
|
|
24649
|
-
return this.loadedLocales;
|
|
24650
|
-
}
|
|
24651
|
-
}
|
|
24652
|
-
|
|
24653
|
-
const SplitToColumnsInteractiveContent = {
|
|
24654
|
-
SplitIsDestructive: _t("This will overwrite data in the subsequent columns. Split anyway?"),
|
|
24655
|
-
};
|
|
24656
|
-
function interactiveSplitToColumns(env, separator, addNewColumns) {
|
|
24657
|
-
let result = env.model.dispatch("SPLIT_TEXT_INTO_COLUMNS", { separator, addNewColumns });
|
|
24658
|
-
if (result.isCancelledBecause("SplitWillOverwriteContent" /* CommandResult.SplitWillOverwriteContent */)) {
|
|
24659
|
-
env.askConfirmation(SplitToColumnsInteractiveContent.SplitIsDestructive, () => {
|
|
24660
|
-
result = env.model.dispatch("SPLIT_TEXT_INTO_COLUMNS", {
|
|
24661
|
-
separator,
|
|
24662
|
-
addNewColumns,
|
|
24663
|
-
force: true,
|
|
24664
|
-
});
|
|
24665
|
-
});
|
|
24666
|
-
}
|
|
24667
|
-
return result;
|
|
24668
|
-
}
|
|
24669
|
-
|
|
24670
24543
|
const dataValidationEvaluatorRegistry = new Registry();
|
|
24671
24544
|
dataValidationEvaluatorRegistry.add("textContains", {
|
|
24672
24545
|
type: "textContains",
|
|
@@ -25210,111 +25083,6 @@ function getDateCriterionFormattedValues(criterion, getters) {
|
|
|
25210
25083
|
});
|
|
25211
25084
|
}
|
|
25212
25085
|
|
|
25213
|
-
function interactiveStopEdition(env) {
|
|
25214
|
-
const result = env.model.dispatch("STOP_EDITION");
|
|
25215
|
-
if (result.isCancelledBecause("BlockingValidationRule" /* CommandResult.BlockingValidationRule */)) {
|
|
25216
|
-
const editedCell = env.model.getters.getCurrentEditedCell();
|
|
25217
|
-
const cellXc = toXC(editedCell.col, editedCell.row);
|
|
25218
|
-
const rule = env.model.getters.getValidationRuleForCell(editedCell);
|
|
25219
|
-
if (!rule) {
|
|
25220
|
-
return;
|
|
25221
|
-
}
|
|
25222
|
-
const evaluator = dataValidationEvaluatorRegistry.get(rule.criterion.type);
|
|
25223
|
-
const errorStr = evaluator.getErrorString(rule.criterion, env.model.getters, editedCell.sheetId);
|
|
25224
|
-
env.raiseError(_t("The data you entered in %s violates the data validation rule set on the cell:\n%s", cellXc, errorStr));
|
|
25225
|
-
env.model.dispatch("CANCEL_EDITION");
|
|
25226
|
-
}
|
|
25227
|
-
}
|
|
25228
|
-
|
|
25229
|
-
const SEPARATORS = [
|
|
25230
|
-
{ name: _t("Detect automatically"), value: "auto" },
|
|
25231
|
-
{ name: _t("Custom separator"), value: "custom" },
|
|
25232
|
-
{ name: _t("Space"), value: " " },
|
|
25233
|
-
{ name: _t("Comma"), value: "," },
|
|
25234
|
-
{ name: _t("Semicolon"), value: ";" },
|
|
25235
|
-
{ name: _t("Line Break"), value: NEWLINE },
|
|
25236
|
-
];
|
|
25237
|
-
class SplitIntoColumnsPanel extends Component {
|
|
25238
|
-
static template = "o-spreadsheet-SplitIntoColumnsPanel";
|
|
25239
|
-
static components = { ValidationMessages, Section, Checkbox };
|
|
25240
|
-
static props = { onCloseSidePanel: Function };
|
|
25241
|
-
state = useState({ separatorValue: "auto", addNewColumns: false, customSeparator: "" });
|
|
25242
|
-
setup() {
|
|
25243
|
-
onWillUpdateProps(() => {
|
|
25244
|
-
// The feature makes no sense if we are editing a cell, because then the selection isn't active
|
|
25245
|
-
// Stop the edition when the panel is mounted, and close the panel if the user start editing a cell
|
|
25246
|
-
if (this.env.model.getters.getEditionMode() !== "inactive") {
|
|
25247
|
-
this.props.onCloseSidePanel();
|
|
25248
|
-
}
|
|
25249
|
-
});
|
|
25250
|
-
onMounted(() => {
|
|
25251
|
-
interactiveStopEdition(this.env);
|
|
25252
|
-
});
|
|
25253
|
-
}
|
|
25254
|
-
onSeparatorChange(value) {
|
|
25255
|
-
this.state.separatorValue = value;
|
|
25256
|
-
}
|
|
25257
|
-
updateCustomSeparator(ev) {
|
|
25258
|
-
if (!ev.target)
|
|
25259
|
-
return;
|
|
25260
|
-
this.state.customSeparator = ev.target.value;
|
|
25261
|
-
}
|
|
25262
|
-
updateAddNewColumnsCheckbox(addNewColumns) {
|
|
25263
|
-
this.state.addNewColumns = addNewColumns;
|
|
25264
|
-
}
|
|
25265
|
-
confirm() {
|
|
25266
|
-
const result = interactiveSplitToColumns(this.env, this.separatorValue, this.state.addNewColumns);
|
|
25267
|
-
if (result.isSuccessful) {
|
|
25268
|
-
this.props.onCloseSidePanel();
|
|
25269
|
-
}
|
|
25270
|
-
}
|
|
25271
|
-
get errorMessages() {
|
|
25272
|
-
const cancelledReasons = this.env.model.canDispatch("SPLIT_TEXT_INTO_COLUMNS", {
|
|
25273
|
-
separator: this.separatorValue,
|
|
25274
|
-
addNewColumns: this.state.addNewColumns,
|
|
25275
|
-
force: true,
|
|
25276
|
-
}).reasons;
|
|
25277
|
-
const errors = new Set();
|
|
25278
|
-
for (const reason of cancelledReasons) {
|
|
25279
|
-
switch (reason) {
|
|
25280
|
-
case "SplitWillOverwriteContent" /* CommandResult.SplitWillOverwriteContent */:
|
|
25281
|
-
case "EmptySplitSeparator" /* CommandResult.EmptySplitSeparator */:
|
|
25282
|
-
break;
|
|
25283
|
-
default:
|
|
25284
|
-
errors.add(SplitToColumnsTerms.Errors[reason] || SplitToColumnsTerms.Errors.Unexpected);
|
|
25285
|
-
}
|
|
25286
|
-
}
|
|
25287
|
-
return Array.from(errors);
|
|
25288
|
-
}
|
|
25289
|
-
get warningMessages() {
|
|
25290
|
-
const warnings = [];
|
|
25291
|
-
const cancelledReasons = this.env.model.canDispatch("SPLIT_TEXT_INTO_COLUMNS", {
|
|
25292
|
-
separator: this.separatorValue,
|
|
25293
|
-
addNewColumns: this.state.addNewColumns,
|
|
25294
|
-
force: false,
|
|
25295
|
-
}).reasons;
|
|
25296
|
-
if (cancelledReasons.includes("SplitWillOverwriteContent" /* CommandResult.SplitWillOverwriteContent */)) {
|
|
25297
|
-
warnings.push(SplitToColumnsTerms.Errors["SplitWillOverwriteContent" /* CommandResult.SplitWillOverwriteContent */]);
|
|
25298
|
-
}
|
|
25299
|
-
return warnings;
|
|
25300
|
-
}
|
|
25301
|
-
get separatorValue() {
|
|
25302
|
-
if (this.state.separatorValue === "custom") {
|
|
25303
|
-
return this.state.customSeparator;
|
|
25304
|
-
}
|
|
25305
|
-
else if (this.state.separatorValue === "auto") {
|
|
25306
|
-
return this.env.model.getters.getAutomaticSeparator();
|
|
25307
|
-
}
|
|
25308
|
-
return this.state.separatorValue;
|
|
25309
|
-
}
|
|
25310
|
-
get separators() {
|
|
25311
|
-
return SEPARATORS;
|
|
25312
|
-
}
|
|
25313
|
-
get isConfirmDisabled() {
|
|
25314
|
-
return !this.separatorValue || this.errorMessages.length > 0;
|
|
25315
|
-
}
|
|
25316
|
-
}
|
|
25317
|
-
|
|
25318
25086
|
/** This component looks like a select input, but on click it opens a Menu with the items given as props instead of a dropdown */
|
|
25319
25087
|
class SelectMenu extends Component {
|
|
25320
25088
|
static template = "o-spreadsheet-SelectMenu";
|
|
@@ -25343,6 +25111,22 @@ class SelectMenu extends Component {
|
|
|
25343
25111
|
}
|
|
25344
25112
|
}
|
|
25345
25113
|
|
|
25114
|
+
function interactiveStopEdition(env) {
|
|
25115
|
+
const result = env.model.dispatch("STOP_EDITION");
|
|
25116
|
+
if (result.isCancelledBecause("BlockingValidationRule" /* CommandResult.BlockingValidationRule */)) {
|
|
25117
|
+
const editedCell = env.model.getters.getCurrentEditedCell();
|
|
25118
|
+
const cellXc = toXC(editedCell.col, editedCell.row);
|
|
25119
|
+
const rule = env.model.getters.getValidationRuleForCell(editedCell);
|
|
25120
|
+
if (!rule) {
|
|
25121
|
+
return;
|
|
25122
|
+
}
|
|
25123
|
+
const evaluator = dataValidationEvaluatorRegistry.get(rule.criterion.type);
|
|
25124
|
+
const errorStr = evaluator.getErrorString(rule.criterion, env.model.getters, editedCell.sheetId);
|
|
25125
|
+
env.raiseError(_t("The data you entered in %s violates the data validation rule set on the cell:\n%s", cellXc, errorStr));
|
|
25126
|
+
env.model.dispatch("CANCEL_EDITION");
|
|
25127
|
+
}
|
|
25128
|
+
}
|
|
25129
|
+
|
|
25346
25130
|
class DataValidationCriterionForm extends Component {
|
|
25347
25131
|
static props = {
|
|
25348
25132
|
criterion: Object,
|
|
@@ -25749,92 +25533,552 @@ function getDataValidationCriterionMenuItems(callback) {
|
|
|
25749
25533
|
}
|
|
25750
25534
|
|
|
25751
25535
|
css /* scss */ `
|
|
25752
|
-
.o-sidePanel .o-sidePanelBody .o-dv-form {
|
|
25753
|
-
.o-section {
|
|
25754
|
-
padding: 16px 16px 0 16px;
|
|
25536
|
+
.o-sidePanel .o-sidePanelBody .o-dv-form {
|
|
25537
|
+
.o-section {
|
|
25538
|
+
padding: 16px 16px 0 16px;
|
|
25539
|
+
}
|
|
25540
|
+
}
|
|
25541
|
+
`;
|
|
25542
|
+
class DataValidationEditor extends Component {
|
|
25543
|
+
static template = "o-spreadsheet-DataValidationEditor";
|
|
25544
|
+
static components = { SelectionInput, SelectMenu, Section };
|
|
25545
|
+
static props = {
|
|
25546
|
+
rule: { type: Object, optional: true },
|
|
25547
|
+
onExit: Function,
|
|
25548
|
+
};
|
|
25549
|
+
state = useState({ rule: this.defaultDataValidationRule });
|
|
25550
|
+
setup() {
|
|
25551
|
+
if (this.props.rule) {
|
|
25552
|
+
const sheetId = this.env.model.getters.getActiveSheetId();
|
|
25553
|
+
this.state.rule = {
|
|
25554
|
+
...this.props.rule,
|
|
25555
|
+
ranges: this.props.rule.ranges.map((range) => this.env.model.getters.getRangeString(range, sheetId)),
|
|
25556
|
+
};
|
|
25557
|
+
this.state.rule.criterion.type = this.props.rule.criterion.type;
|
|
25558
|
+
}
|
|
25559
|
+
}
|
|
25560
|
+
onCriterionTypeChanged(type) {
|
|
25561
|
+
this.state.rule.criterion.type = type;
|
|
25562
|
+
}
|
|
25563
|
+
onRangesChanged(ranges) {
|
|
25564
|
+
this.state.rule.ranges = ranges;
|
|
25565
|
+
}
|
|
25566
|
+
onCriterionChanged(criterion) {
|
|
25567
|
+
this.state.rule.criterion = criterion;
|
|
25568
|
+
}
|
|
25569
|
+
changeRuleIsBlocking(ev) {
|
|
25570
|
+
const isBlocking = ev.target.value;
|
|
25571
|
+
this.state.rule.isBlocking = isBlocking === "true";
|
|
25572
|
+
}
|
|
25573
|
+
onSave() {
|
|
25574
|
+
if (!this.canSave) {
|
|
25575
|
+
return;
|
|
25576
|
+
}
|
|
25577
|
+
this.env.model.dispatch("ADD_DATA_VALIDATION_RULE", this.dispatchPayload);
|
|
25578
|
+
this.props.onExit();
|
|
25579
|
+
}
|
|
25580
|
+
get canSave() {
|
|
25581
|
+
return this.env.model.canDispatch("ADD_DATA_VALIDATION_RULE", this.dispatchPayload)
|
|
25582
|
+
.isSuccessful;
|
|
25583
|
+
}
|
|
25584
|
+
get dispatchPayload() {
|
|
25585
|
+
const rule = { ...this.state.rule, ranges: undefined };
|
|
25586
|
+
const locale = this.env.model.getters.getLocale();
|
|
25587
|
+
const criterion = rule.criterion;
|
|
25588
|
+
const criterionEvaluator = dataValidationEvaluatorRegistry.get(criterion.type);
|
|
25589
|
+
const sheetId = this.env.model.getters.getActiveSheetId();
|
|
25590
|
+
const values = criterion.values
|
|
25591
|
+
.slice(0, criterionEvaluator.numberOfValues(criterion))
|
|
25592
|
+
.map((value) => value?.trim())
|
|
25593
|
+
.filter((value) => value !== "" && value !== undefined)
|
|
25594
|
+
.map((value) => canonicalizeContent(value, locale));
|
|
25595
|
+
rule.criterion = { ...criterion, values };
|
|
25596
|
+
return {
|
|
25597
|
+
sheetId,
|
|
25598
|
+
ranges: this.state.rule.ranges.map((xc) => this.env.model.getters.getRangeDataFromXc(sheetId, xc)),
|
|
25599
|
+
rule,
|
|
25600
|
+
};
|
|
25601
|
+
}
|
|
25602
|
+
get dvCriterionMenuItems() {
|
|
25603
|
+
return getDataValidationCriterionMenuItems((type) => this.onCriterionTypeChanged(type));
|
|
25604
|
+
}
|
|
25605
|
+
get selectedCriterionName() {
|
|
25606
|
+
const selectedType = this.state.rule.criterion.type;
|
|
25607
|
+
return dataValidationEvaluatorRegistry.get(selectedType).name;
|
|
25608
|
+
}
|
|
25609
|
+
get defaultDataValidationRule() {
|
|
25610
|
+
const sheetId = this.env.model.getters.getActiveSheetId();
|
|
25611
|
+
const ranges = this.env.model.getters
|
|
25612
|
+
.getSelectedZones()
|
|
25613
|
+
.map((zone) => zoneToXc(this.env.model.getters.getUnboundedZone(sheetId, zone)));
|
|
25614
|
+
return {
|
|
25615
|
+
id: this.env.model.uuidGenerator.uuidv4(),
|
|
25616
|
+
criterion: { type: "textContains", values: [""] },
|
|
25617
|
+
ranges,
|
|
25618
|
+
};
|
|
25619
|
+
}
|
|
25620
|
+
get criterionComponent() {
|
|
25621
|
+
return dataValidationPanelCriteriaRegistry.get(this.state.rule.criterion.type).component;
|
|
25622
|
+
}
|
|
25623
|
+
}
|
|
25624
|
+
|
|
25625
|
+
css /* scss */ `
|
|
25626
|
+
.o-find-and-replace {
|
|
25627
|
+
outline: none;
|
|
25628
|
+
height: 100%;
|
|
25629
|
+
.o-input-search-container {
|
|
25630
|
+
display: flex;
|
|
25631
|
+
.o-input-with-count {
|
|
25632
|
+
flex-grow: 1;
|
|
25633
|
+
width: auto;
|
|
25634
|
+
}
|
|
25635
|
+
.o-input-without-count {
|
|
25636
|
+
width: 100%;
|
|
25637
|
+
}
|
|
25638
|
+
.o-input-count {
|
|
25639
|
+
width: fit-content;
|
|
25640
|
+
padding: 4px 0 4px 4px;
|
|
25641
|
+
}
|
|
25642
|
+
}
|
|
25643
|
+
|
|
25644
|
+
.o-matches-count div {
|
|
25645
|
+
text-overflow: ellipsis;
|
|
25646
|
+
overflow: hidden;
|
|
25647
|
+
white-space: nowrap;
|
|
25648
|
+
}
|
|
25649
|
+
}
|
|
25650
|
+
`;
|
|
25651
|
+
class FindAndReplacePanel extends Component {
|
|
25652
|
+
static template = "o-spreadsheet-FindAndReplacePanel";
|
|
25653
|
+
static components = { SelectionInput, Section, Checkbox };
|
|
25654
|
+
static props = {
|
|
25655
|
+
onCloseSidePanel: Function,
|
|
25656
|
+
};
|
|
25657
|
+
debounceTimeoutId;
|
|
25658
|
+
initialShowFormulaState = false;
|
|
25659
|
+
dataRange = "";
|
|
25660
|
+
searchInput = useRef("searchInput");
|
|
25661
|
+
replaceInput = useRef("replaceInput");
|
|
25662
|
+
get hasSearchResult() {
|
|
25663
|
+
return this.env.model.getters.getCurrentSelectedMatchIndex() !== null;
|
|
25664
|
+
}
|
|
25665
|
+
get pendingSearch() {
|
|
25666
|
+
return this.debounceTimeoutId !== undefined;
|
|
25667
|
+
}
|
|
25668
|
+
get searchOptions() {
|
|
25669
|
+
return this.env.model.getters.getSearchOptions();
|
|
25670
|
+
}
|
|
25671
|
+
get toSearch() {
|
|
25672
|
+
return this.searchInput.el?.value || "";
|
|
25673
|
+
}
|
|
25674
|
+
get toReplace() {
|
|
25675
|
+
return this.replaceInput.el?.value || "";
|
|
25676
|
+
}
|
|
25677
|
+
get allSheetsMatchesCount() {
|
|
25678
|
+
return _t("%s in all sheets", this.env.model.getters.getAllSheetMatchesCount());
|
|
25679
|
+
}
|
|
25680
|
+
get currentSheetMatchesCount() {
|
|
25681
|
+
return _t("%(matches)s in sheet %(sheetName)s", {
|
|
25682
|
+
matches: this.env.model.getters.getActiveSheetMatchesCount(),
|
|
25683
|
+
sheetName: this.env.model.getters.getSheetName(this.env.model.getters.getActiveSheetId()),
|
|
25684
|
+
});
|
|
25685
|
+
}
|
|
25686
|
+
get specificRangeMatchesCount() {
|
|
25687
|
+
const range = this.searchOptions.specificRange;
|
|
25688
|
+
if (!range) {
|
|
25689
|
+
return "";
|
|
25690
|
+
}
|
|
25691
|
+
const { _sheetId, _zone } = range;
|
|
25692
|
+
return _t("%(matches)s in range %(range)s of sheet %(sheetName)s", {
|
|
25693
|
+
matches: this.env.model.getters.getSpecificRangeMatchesCount().toString(),
|
|
25694
|
+
range: zoneToXc(_zone),
|
|
25695
|
+
sheetName: this.env.model.getters.getSheetName(_sheetId),
|
|
25696
|
+
});
|
|
25697
|
+
}
|
|
25698
|
+
setup() {
|
|
25699
|
+
this.initialShowFormulaState = this.env.model.getters.shouldShowFormulas();
|
|
25700
|
+
onMounted(() => this.searchInput.el?.focus());
|
|
25701
|
+
onWillUnmount(() => {
|
|
25702
|
+
clearTimeout(this.debounceTimeoutId);
|
|
25703
|
+
this.env.model.dispatch("CLEAR_SEARCH");
|
|
25704
|
+
this.env.model.dispatch("SET_FORMULA_VISIBILITY", { show: this.initialShowFormulaState });
|
|
25705
|
+
});
|
|
25706
|
+
useEffect(() => {
|
|
25707
|
+
const showFormula = this.env.model.getters.shouldShowFormulas();
|
|
25708
|
+
this.updateSearch({ searchFormulas: showFormula });
|
|
25709
|
+
}, () => [this.env.model.getters.shouldShowFormulas()]);
|
|
25710
|
+
}
|
|
25711
|
+
onFocusSearch() {
|
|
25712
|
+
this.updateDataRange();
|
|
25713
|
+
}
|
|
25714
|
+
onInput() {
|
|
25715
|
+
this.debouncedUpdateSearch();
|
|
25716
|
+
}
|
|
25717
|
+
onKeydownSearch(ev) {
|
|
25718
|
+
if (ev.key === "Enter") {
|
|
25719
|
+
ev.preventDefault();
|
|
25720
|
+
ev.stopPropagation();
|
|
25721
|
+
this.onSelectNextCell();
|
|
25722
|
+
}
|
|
25723
|
+
}
|
|
25724
|
+
onKeydownReplace(ev) {
|
|
25725
|
+
if (ev.key === "Enter") {
|
|
25726
|
+
ev.preventDefault();
|
|
25727
|
+
ev.stopPropagation();
|
|
25728
|
+
this.replace();
|
|
25729
|
+
}
|
|
25730
|
+
}
|
|
25731
|
+
searchFormulas(showFormula) {
|
|
25732
|
+
this.env.model.dispatch("SET_FORMULA_VISIBILITY", {
|
|
25733
|
+
show: showFormula,
|
|
25734
|
+
});
|
|
25735
|
+
this.updateSearch({ searchFormulas: showFormula });
|
|
25736
|
+
}
|
|
25737
|
+
searchExactMatch(exactMatch) {
|
|
25738
|
+
this.updateSearch({ exactMatch });
|
|
25739
|
+
}
|
|
25740
|
+
searchMatchCase(matchCase) {
|
|
25741
|
+
this.updateSearch({ matchCase });
|
|
25742
|
+
}
|
|
25743
|
+
changeSearchScope(ev) {
|
|
25744
|
+
const searchScope = ev.target.value;
|
|
25745
|
+
this.updateSearch({ searchScope });
|
|
25746
|
+
}
|
|
25747
|
+
onSearchRangeChanged(ranges) {
|
|
25748
|
+
this.dataRange = ranges[0];
|
|
25749
|
+
}
|
|
25750
|
+
updateDataRange() {
|
|
25751
|
+
if (!this.dataRange) {
|
|
25752
|
+
return;
|
|
25753
|
+
}
|
|
25754
|
+
if (this.searchOptions.searchScope === "specificRange") {
|
|
25755
|
+
const specificRange = this.env.model.getters.getRangeFromSheetXC(this.env.model.getters.getActiveSheetId(), this.dataRange).rangeData;
|
|
25756
|
+
this.updateSearch({ specificRange });
|
|
25757
|
+
}
|
|
25758
|
+
}
|
|
25759
|
+
onSelectPreviousCell() {
|
|
25760
|
+
this.env.model.dispatch("SELECT_SEARCH_PREVIOUS_MATCH");
|
|
25761
|
+
}
|
|
25762
|
+
onSelectNextCell() {
|
|
25763
|
+
this.env.model.dispatch("SELECT_SEARCH_NEXT_MATCH");
|
|
25764
|
+
}
|
|
25765
|
+
updateSearch(updateSearchOptions) {
|
|
25766
|
+
const searchOptions = {
|
|
25767
|
+
...this.env.model.getters.getSearchOptions(),
|
|
25768
|
+
...updateSearchOptions,
|
|
25769
|
+
};
|
|
25770
|
+
this.env.model.dispatch("UPDATE_SEARCH", {
|
|
25771
|
+
toSearch: this.toSearch,
|
|
25772
|
+
searchOptions,
|
|
25773
|
+
});
|
|
25774
|
+
}
|
|
25775
|
+
debouncedUpdateSearch() {
|
|
25776
|
+
clearTimeout(this.debounceTimeoutId);
|
|
25777
|
+
this.debounceTimeoutId = setTimeout(() => {
|
|
25778
|
+
this.updateSearch();
|
|
25779
|
+
this.debounceTimeoutId = undefined;
|
|
25780
|
+
}, 200);
|
|
25781
|
+
}
|
|
25782
|
+
replace() {
|
|
25783
|
+
this.env.model.dispatch("REPLACE_SEARCH", {
|
|
25784
|
+
replaceWith: this.toReplace,
|
|
25785
|
+
});
|
|
25786
|
+
}
|
|
25787
|
+
replaceAll() {
|
|
25788
|
+
this.env.model.dispatch("REPLACE_ALL_SEARCH", {
|
|
25789
|
+
replaceWith: this.toReplace,
|
|
25790
|
+
});
|
|
25791
|
+
}
|
|
25792
|
+
}
|
|
25793
|
+
|
|
25794
|
+
css /* scss */ `
|
|
25795
|
+
.o-more-formats-panel {
|
|
25796
|
+
.format-preview {
|
|
25797
|
+
height: 48px;
|
|
25798
|
+
background-color: white;
|
|
25799
|
+
|
|
25800
|
+
&:hover {
|
|
25801
|
+
background-color: rgba(0, 0, 0, 0.08);
|
|
25802
|
+
}
|
|
25803
|
+
}
|
|
25804
|
+
.check-icon {
|
|
25805
|
+
width: 24px;
|
|
25755
25806
|
}
|
|
25756
25807
|
}
|
|
25757
25808
|
`;
|
|
25758
|
-
|
|
25759
|
-
|
|
25760
|
-
|
|
25809
|
+
const DATE_FORMAT_ACTIONS = createActions([
|
|
25810
|
+
formatNumberFullDateTime,
|
|
25811
|
+
formatNumberFullWeekDayAndMonth,
|
|
25812
|
+
formatNumberDayAndFullMonth,
|
|
25813
|
+
formatNumberShortWeekDay,
|
|
25814
|
+
formatNumberDayAndShortMonth,
|
|
25815
|
+
formatNumberFullMonth,
|
|
25816
|
+
formatNumberShortMonth,
|
|
25817
|
+
formatNumberDate,
|
|
25818
|
+
formatNumberTime,
|
|
25819
|
+
formatNumberDateTime,
|
|
25820
|
+
formatNumberDuration,
|
|
25821
|
+
]);
|
|
25822
|
+
class MoreFormatsPanel extends Component {
|
|
25823
|
+
static template = "o-spreadsheet-MoreFormatsPanel";
|
|
25761
25824
|
static props = {
|
|
25762
|
-
|
|
25763
|
-
onExit: Function,
|
|
25825
|
+
onCloseSidePanel: Function,
|
|
25764
25826
|
};
|
|
25765
|
-
|
|
25827
|
+
get dateFormatsActions() {
|
|
25828
|
+
return DATE_FORMAT_ACTIONS;
|
|
25829
|
+
}
|
|
25830
|
+
}
|
|
25831
|
+
|
|
25832
|
+
css /* scss */ `
|
|
25833
|
+
.o-checkbox-selection {
|
|
25834
|
+
height: 150px;
|
|
25835
|
+
}
|
|
25836
|
+
`;
|
|
25837
|
+
class RemoveDuplicatesPanel extends Component {
|
|
25838
|
+
static template = "o-spreadsheet-RemoveDuplicatesPanel";
|
|
25839
|
+
static components = { ValidationMessages, Section, Checkbox };
|
|
25840
|
+
state = useState({
|
|
25841
|
+
hasHeader: false,
|
|
25842
|
+
columns: {},
|
|
25843
|
+
});
|
|
25766
25844
|
setup() {
|
|
25767
|
-
|
|
25845
|
+
onWillUpdateProps(() => this.updateColumns());
|
|
25846
|
+
}
|
|
25847
|
+
toggleHasHeader() {
|
|
25848
|
+
this.state.hasHeader = !this.state.hasHeader;
|
|
25849
|
+
}
|
|
25850
|
+
toggleAllColumns() {
|
|
25851
|
+
const newState = !this.isEveryColumnSelected;
|
|
25852
|
+
for (const index in this.state.columns) {
|
|
25853
|
+
this.state.columns[index] = newState;
|
|
25854
|
+
}
|
|
25855
|
+
}
|
|
25856
|
+
toggleColumn(colIndex) {
|
|
25857
|
+
this.state.columns[colIndex] = !this.state.columns[colIndex];
|
|
25858
|
+
}
|
|
25859
|
+
onRemoveDuplicates() {
|
|
25860
|
+
this.env.model.dispatch("REMOVE_DUPLICATES", {
|
|
25861
|
+
hasHeader: this.state.hasHeader,
|
|
25862
|
+
columns: this.getColsToAnalyze(),
|
|
25863
|
+
});
|
|
25864
|
+
}
|
|
25865
|
+
getColLabel(colKey) {
|
|
25866
|
+
const col = parseInt(colKey);
|
|
25867
|
+
let colLabel = _t("Column %s", numberToLetters(col));
|
|
25868
|
+
if (this.state.hasHeader) {
|
|
25768
25869
|
const sheetId = this.env.model.getters.getActiveSheetId();
|
|
25769
|
-
this.
|
|
25770
|
-
|
|
25771
|
-
|
|
25772
|
-
|
|
25773
|
-
|
|
25870
|
+
const row = this.env.model.getters.getSelectedZone().top;
|
|
25871
|
+
const colHeader = this.env.model.getters.getEvaluatedCell({ sheetId, col, row });
|
|
25872
|
+
if (colHeader.type !== "empty") {
|
|
25873
|
+
colLabel += ` - ${colHeader.value}`;
|
|
25874
|
+
}
|
|
25774
25875
|
}
|
|
25876
|
+
return colLabel;
|
|
25775
25877
|
}
|
|
25776
|
-
|
|
25777
|
-
this.state.
|
|
25878
|
+
get isEveryColumnSelected() {
|
|
25879
|
+
return Object.values(this.state.columns).every((value) => value === true);
|
|
25778
25880
|
}
|
|
25779
|
-
|
|
25780
|
-
this.
|
|
25881
|
+
get errorMessages() {
|
|
25882
|
+
const cancelledReasons = this.env.model.canDispatch("REMOVE_DUPLICATES", {
|
|
25883
|
+
hasHeader: this.state.hasHeader,
|
|
25884
|
+
columns: this.getColsToAnalyze(),
|
|
25885
|
+
}).reasons;
|
|
25886
|
+
const errors = new Set();
|
|
25887
|
+
for (const reason of cancelledReasons) {
|
|
25888
|
+
errors.add(RemoveDuplicateTerms.Errors[reason] || RemoveDuplicateTerms.Errors.Unexpected);
|
|
25889
|
+
}
|
|
25890
|
+
return Array.from(errors);
|
|
25781
25891
|
}
|
|
25782
|
-
|
|
25783
|
-
this.
|
|
25892
|
+
get selectionStatisticalInformation() {
|
|
25893
|
+
const dimension = zoneToDimension(this.env.model.getters.getSelectedZone());
|
|
25894
|
+
return _t("%(row_count)s rows and %(column_count)s columns selected", {
|
|
25895
|
+
row_count: dimension.numberOfRows,
|
|
25896
|
+
column_count: dimension.numberOfCols,
|
|
25897
|
+
});
|
|
25784
25898
|
}
|
|
25785
|
-
|
|
25786
|
-
|
|
25787
|
-
this.state.rule.isBlocking = isBlocking === "true";
|
|
25899
|
+
get canConfirm() {
|
|
25900
|
+
return this.errorMessages.length === 0;
|
|
25788
25901
|
}
|
|
25789
|
-
|
|
25790
|
-
|
|
25791
|
-
|
|
25902
|
+
// ---------------------------------------------------------------------------
|
|
25903
|
+
// Private
|
|
25904
|
+
// ---------------------------------------------------------------------------
|
|
25905
|
+
updateColumns() {
|
|
25906
|
+
const zone = this.env.model.getters.getSelectedZone();
|
|
25907
|
+
const oldColumns = this.state.columns;
|
|
25908
|
+
const newColumns = {};
|
|
25909
|
+
for (let i = zone.left; i <= zone.right; i++) {
|
|
25910
|
+
newColumns[i] = i in oldColumns ? oldColumns[i] : true;
|
|
25792
25911
|
}
|
|
25793
|
-
this.
|
|
25794
|
-
this.props.onExit();
|
|
25912
|
+
this.state.columns = newColumns;
|
|
25795
25913
|
}
|
|
25796
|
-
|
|
25797
|
-
return
|
|
25798
|
-
.
|
|
25914
|
+
getColsToAnalyze() {
|
|
25915
|
+
return Object.keys(this.state.columns)
|
|
25916
|
+
.filter((colIndex) => this.state.columns[colIndex])
|
|
25917
|
+
.map((colIndex) => parseInt(colIndex));
|
|
25799
25918
|
}
|
|
25800
|
-
|
|
25801
|
-
|
|
25919
|
+
}
|
|
25920
|
+
|
|
25921
|
+
css /* scss */ `
|
|
25922
|
+
.o-locale-preview {
|
|
25923
|
+
color: dimgrey;
|
|
25924
|
+
}
|
|
25925
|
+
`;
|
|
25926
|
+
class SettingsPanel extends Component {
|
|
25927
|
+
static template = "o-spreadsheet-SettingsPanel";
|
|
25928
|
+
static components = { Section };
|
|
25929
|
+
static props = { onCloseSidePanel: Function };
|
|
25930
|
+
loadedLocales = [];
|
|
25931
|
+
setup() {
|
|
25932
|
+
onWillStart(() => this.loadLocales());
|
|
25933
|
+
}
|
|
25934
|
+
onLocaleChange(code) {
|
|
25935
|
+
const locale = this.loadedLocales.find((l) => l.code === code);
|
|
25936
|
+
if (!locale)
|
|
25937
|
+
return;
|
|
25938
|
+
this.env.model.dispatch("UPDATE_LOCALE", { locale });
|
|
25939
|
+
}
|
|
25940
|
+
async loadLocales() {
|
|
25941
|
+
this.loadedLocales = (await this.env.loadLocales())
|
|
25942
|
+
.filter(isValidLocale)
|
|
25943
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
25944
|
+
}
|
|
25945
|
+
get numberFormatPreview() {
|
|
25802
25946
|
const locale = this.env.model.getters.getLocale();
|
|
25803
|
-
|
|
25804
|
-
const criterionEvaluator = dataValidationEvaluatorRegistry.get(criterion.type);
|
|
25805
|
-
const sheetId = this.env.model.getters.getActiveSheetId();
|
|
25806
|
-
const values = criterion.values
|
|
25807
|
-
.slice(0, criterionEvaluator.numberOfValues(criterion))
|
|
25808
|
-
.map((value) => value?.trim())
|
|
25809
|
-
.filter((value) => value !== "" && value !== undefined)
|
|
25810
|
-
.map((value) => canonicalizeContent(value, locale));
|
|
25811
|
-
rule.criterion = { ...criterion, values };
|
|
25812
|
-
return {
|
|
25813
|
-
sheetId,
|
|
25814
|
-
ranges: this.state.rule.ranges.map((xc) => this.env.model.getters.getRangeDataFromXc(sheetId, xc)),
|
|
25815
|
-
rule,
|
|
25816
|
-
};
|
|
25947
|
+
return formatValue(1234567.89, { format: "#,##0.00", locale });
|
|
25817
25948
|
}
|
|
25818
|
-
get
|
|
25819
|
-
|
|
25949
|
+
get dateFormatPreview() {
|
|
25950
|
+
const locale = this.env.model.getters.getLocale();
|
|
25951
|
+
return formatValue(1.6, { format: locale.dateFormat, locale });
|
|
25820
25952
|
}
|
|
25821
|
-
get
|
|
25822
|
-
const
|
|
25823
|
-
|
|
25953
|
+
get dateTimeFormatPreview() {
|
|
25954
|
+
const locale = this.env.model.getters.getLocale();
|
|
25955
|
+
const dateTimeFormat = getDateTimeFormat(locale);
|
|
25956
|
+
return formatValue(1.6, { format: dateTimeFormat, locale });
|
|
25824
25957
|
}
|
|
25825
|
-
get
|
|
25826
|
-
|
|
25827
|
-
const ranges = this.env.model.getters
|
|
25828
|
-
.getSelectedZones()
|
|
25829
|
-
.map((zone) => zoneToXc(this.env.model.getters.getUnboundedZone(sheetId, zone)));
|
|
25830
|
-
return {
|
|
25831
|
-
id: this.env.model.uuidGenerator.uuidv4(),
|
|
25832
|
-
criterion: { type: "textContains", values: [""] },
|
|
25833
|
-
ranges,
|
|
25834
|
-
};
|
|
25958
|
+
get currentLocale() {
|
|
25959
|
+
return this.env.model.getters.getLocale();
|
|
25835
25960
|
}
|
|
25836
|
-
get
|
|
25837
|
-
|
|
25961
|
+
get supportedLocales() {
|
|
25962
|
+
const currentLocale = this.currentLocale;
|
|
25963
|
+
const localeInLoadedLocales = this.loadedLocales.find((l) => l.code === currentLocale.code);
|
|
25964
|
+
if (!localeInLoadedLocales) {
|
|
25965
|
+
const locales = [...this.loadedLocales, currentLocale].sort((a, b) => a.name.localeCompare(b.name));
|
|
25966
|
+
return locales;
|
|
25967
|
+
}
|
|
25968
|
+
else if (!deepEquals(currentLocale, localeInLoadedLocales)) {
|
|
25969
|
+
const index = this.loadedLocales.indexOf(localeInLoadedLocales);
|
|
25970
|
+
const locales = [...this.loadedLocales];
|
|
25971
|
+
locales[index] = currentLocale;
|
|
25972
|
+
locales.sort((a, b) => a.name.localeCompare(b.name));
|
|
25973
|
+
return locales;
|
|
25974
|
+
}
|
|
25975
|
+
return this.loadedLocales;
|
|
25976
|
+
}
|
|
25977
|
+
}
|
|
25978
|
+
|
|
25979
|
+
const SplitToColumnsInteractiveContent = {
|
|
25980
|
+
SplitIsDestructive: _t("This will overwrite data in the subsequent columns. Split anyway?"),
|
|
25981
|
+
};
|
|
25982
|
+
function interactiveSplitToColumns(env, separator, addNewColumns) {
|
|
25983
|
+
let result = env.model.dispatch("SPLIT_TEXT_INTO_COLUMNS", { separator, addNewColumns });
|
|
25984
|
+
if (result.isCancelledBecause("SplitWillOverwriteContent" /* CommandResult.SplitWillOverwriteContent */)) {
|
|
25985
|
+
env.askConfirmation(SplitToColumnsInteractiveContent.SplitIsDestructive, () => {
|
|
25986
|
+
result = env.model.dispatch("SPLIT_TEXT_INTO_COLUMNS", {
|
|
25987
|
+
separator,
|
|
25988
|
+
addNewColumns,
|
|
25989
|
+
force: true,
|
|
25990
|
+
});
|
|
25991
|
+
});
|
|
25992
|
+
}
|
|
25993
|
+
return result;
|
|
25994
|
+
}
|
|
25995
|
+
|
|
25996
|
+
const SEPARATORS = [
|
|
25997
|
+
{ name: _t("Detect automatically"), value: "auto" },
|
|
25998
|
+
{ name: _t("Custom separator"), value: "custom" },
|
|
25999
|
+
{ name: _t("Space"), value: " " },
|
|
26000
|
+
{ name: _t("Comma"), value: "," },
|
|
26001
|
+
{ name: _t("Semicolon"), value: ";" },
|
|
26002
|
+
{ name: _t("Line Break"), value: NEWLINE },
|
|
26003
|
+
];
|
|
26004
|
+
class SplitIntoColumnsPanel extends Component {
|
|
26005
|
+
static template = "o-spreadsheet-SplitIntoColumnsPanel";
|
|
26006
|
+
static components = { ValidationMessages, Section, Checkbox };
|
|
26007
|
+
static props = { onCloseSidePanel: Function };
|
|
26008
|
+
state = useState({ separatorValue: "auto", addNewColumns: false, customSeparator: "" });
|
|
26009
|
+
setup() {
|
|
26010
|
+
onWillUpdateProps(() => {
|
|
26011
|
+
// The feature makes no sense if we are editing a cell, because then the selection isn't active
|
|
26012
|
+
// Stop the edition when the panel is mounted, and close the panel if the user start editing a cell
|
|
26013
|
+
if (this.env.model.getters.getEditionMode() !== "inactive") {
|
|
26014
|
+
this.props.onCloseSidePanel();
|
|
26015
|
+
}
|
|
26016
|
+
});
|
|
26017
|
+
onMounted(() => {
|
|
26018
|
+
interactiveStopEdition(this.env);
|
|
26019
|
+
});
|
|
26020
|
+
}
|
|
26021
|
+
onSeparatorChange(value) {
|
|
26022
|
+
this.state.separatorValue = value;
|
|
26023
|
+
}
|
|
26024
|
+
updateCustomSeparator(ev) {
|
|
26025
|
+
if (!ev.target)
|
|
26026
|
+
return;
|
|
26027
|
+
this.state.customSeparator = ev.target.value;
|
|
26028
|
+
}
|
|
26029
|
+
updateAddNewColumnsCheckbox(addNewColumns) {
|
|
26030
|
+
this.state.addNewColumns = addNewColumns;
|
|
26031
|
+
}
|
|
26032
|
+
confirm() {
|
|
26033
|
+
const result = interactiveSplitToColumns(this.env, this.separatorValue, this.state.addNewColumns);
|
|
26034
|
+
if (result.isSuccessful) {
|
|
26035
|
+
this.props.onCloseSidePanel();
|
|
26036
|
+
}
|
|
26037
|
+
}
|
|
26038
|
+
get errorMessages() {
|
|
26039
|
+
const cancelledReasons = this.env.model.canDispatch("SPLIT_TEXT_INTO_COLUMNS", {
|
|
26040
|
+
separator: this.separatorValue,
|
|
26041
|
+
addNewColumns: this.state.addNewColumns,
|
|
26042
|
+
force: true,
|
|
26043
|
+
}).reasons;
|
|
26044
|
+
const errors = new Set();
|
|
26045
|
+
for (const reason of cancelledReasons) {
|
|
26046
|
+
switch (reason) {
|
|
26047
|
+
case "SplitWillOverwriteContent" /* CommandResult.SplitWillOverwriteContent */:
|
|
26048
|
+
case "EmptySplitSeparator" /* CommandResult.EmptySplitSeparator */:
|
|
26049
|
+
break;
|
|
26050
|
+
default:
|
|
26051
|
+
errors.add(SplitToColumnsTerms.Errors[reason] || SplitToColumnsTerms.Errors.Unexpected);
|
|
26052
|
+
}
|
|
26053
|
+
}
|
|
26054
|
+
return Array.from(errors);
|
|
26055
|
+
}
|
|
26056
|
+
get warningMessages() {
|
|
26057
|
+
const warnings = [];
|
|
26058
|
+
const cancelledReasons = this.env.model.canDispatch("SPLIT_TEXT_INTO_COLUMNS", {
|
|
26059
|
+
separator: this.separatorValue,
|
|
26060
|
+
addNewColumns: this.state.addNewColumns,
|
|
26061
|
+
force: false,
|
|
26062
|
+
}).reasons;
|
|
26063
|
+
if (cancelledReasons.includes("SplitWillOverwriteContent" /* CommandResult.SplitWillOverwriteContent */)) {
|
|
26064
|
+
warnings.push(SplitToColumnsTerms.Errors["SplitWillOverwriteContent" /* CommandResult.SplitWillOverwriteContent */]);
|
|
26065
|
+
}
|
|
26066
|
+
return warnings;
|
|
26067
|
+
}
|
|
26068
|
+
get separatorValue() {
|
|
26069
|
+
if (this.state.separatorValue === "custom") {
|
|
26070
|
+
return this.state.customSeparator;
|
|
26071
|
+
}
|
|
26072
|
+
else if (this.state.separatorValue === "auto") {
|
|
26073
|
+
return this.env.model.getters.getAutomaticSeparator();
|
|
26074
|
+
}
|
|
26075
|
+
return this.state.separatorValue;
|
|
26076
|
+
}
|
|
26077
|
+
get separators() {
|
|
26078
|
+
return SEPARATORS;
|
|
26079
|
+
}
|
|
26080
|
+
get isConfirmDisabled() {
|
|
26081
|
+
return !this.separatorValue || this.errorMessages.length > 0;
|
|
25838
26082
|
}
|
|
25839
26083
|
}
|
|
25840
26084
|
|
|
@@ -25955,6 +26199,10 @@ sidePanelRegistry.add("DataValidation", {
|
|
|
25955
26199
|
title: _t("Data validation"),
|
|
25956
26200
|
Body: DataValidationPanel,
|
|
25957
26201
|
});
|
|
26202
|
+
sidePanelRegistry.add("DataValidationEditor", {
|
|
26203
|
+
title: _t("Data validation"),
|
|
26204
|
+
Body: DataValidationEditor,
|
|
26205
|
+
});
|
|
25958
26206
|
sidePanelRegistry.add("MoreFormats", {
|
|
25959
26207
|
title: _t("More date formats"),
|
|
25960
26208
|
Body: MoreFormatsPanel,
|
|
@@ -27286,6 +27534,9 @@ class Composer extends Component {
|
|
|
27286
27534
|
const token = tokens.filter((token) => token.value.includes(currentSelectedText) &&
|
|
27287
27535
|
token.start <= currentSelection.start &&
|
|
27288
27536
|
token.end >= currentSelection.end)[0];
|
|
27537
|
+
if (!token) {
|
|
27538
|
+
return;
|
|
27539
|
+
}
|
|
27289
27540
|
if (token.type === "REFERENCE") {
|
|
27290
27541
|
this.env.model.dispatch("CHANGE_COMPOSER_CURSOR_SELECTION", {
|
|
27291
27542
|
start: token.start,
|
|
@@ -31179,6 +31430,19 @@ function fixXlsxUnicode(str) {
|
|
|
31179
31430
|
return String.fromCharCode(parseInt(code, 16));
|
|
31180
31431
|
});
|
|
31181
31432
|
}
|
|
31433
|
+
/** Get a header in the SheetData. Create the header if it doesn't exist in the SheetData */
|
|
31434
|
+
function getSheetDataHeader(sheetData, dimension, index) {
|
|
31435
|
+
if (dimension === "COL") {
|
|
31436
|
+
if (!sheetData.cols[index]) {
|
|
31437
|
+
sheetData.cols[index] = {};
|
|
31438
|
+
}
|
|
31439
|
+
return sheetData.cols[index];
|
|
31440
|
+
}
|
|
31441
|
+
if (!sheetData.rows[index]) {
|
|
31442
|
+
sheetData.rows[index] = {};
|
|
31443
|
+
}
|
|
31444
|
+
return sheetData.rows[index];
|
|
31445
|
+
}
|
|
31182
31446
|
|
|
31183
31447
|
const XLSX_DATE_FORMAT_REGEX = /^(yy|yyyy|m{1,5}|d{1,4}|h{1,2}|s{1,2}|am\/pm|a\/m|\s|-|\/|\.|:)+$/i;
|
|
31184
31448
|
/**
|
|
@@ -32035,6 +32299,8 @@ function convertSheets(data, warningManager) {
|
|
|
32035
32299
|
convertFormulasContent(sheet, data);
|
|
32036
32300
|
const sheetDims = getSheetDims(sheet);
|
|
32037
32301
|
const sheetOptions = sheet.sheetViews[0];
|
|
32302
|
+
const rowHeaderGroups = convertHeaderGroup(sheet, "ROW", sheetDims[1]);
|
|
32303
|
+
const colHeaderGroups = convertHeaderGroup(sheet, "COL", sheetDims[0]);
|
|
32038
32304
|
return {
|
|
32039
32305
|
id: sheet.sheetName,
|
|
32040
32306
|
areGridLinesVisible: sheetOptions ? sheetOptions.showGridLines : true,
|
|
@@ -32043,8 +32309,8 @@ function convertSheets(data, warningManager) {
|
|
|
32043
32309
|
rowNumber: sheetDims[1],
|
|
32044
32310
|
cells: convertCells(sheet, data, sheetDims, warningManager),
|
|
32045
32311
|
merges: sheet.merges,
|
|
32046
|
-
cols: convertCols(sheet, sheetDims[0]),
|
|
32047
|
-
rows: convertRows(sheet, sheetDims[1]),
|
|
32312
|
+
cols: convertCols(sheet, sheetDims[0], colHeaderGroups),
|
|
32313
|
+
rows: convertRows(sheet, sheetDims[1], rowHeaderGroups),
|
|
32048
32314
|
conditionalFormats: convertConditionalFormats(sheet.cfs, data.dxfs, warningManager),
|
|
32049
32315
|
figures: convertFigures(sheet),
|
|
32050
32316
|
isVisible: sheet.isVisible,
|
|
@@ -32052,10 +32318,11 @@ function convertSheets(data, warningManager) {
|
|
|
32052
32318
|
? { xSplit: sheetOptions.pane.xSplit, ySplit: sheetOptions.pane.ySplit }
|
|
32053
32319
|
: { xSplit: 0, ySplit: 0 },
|
|
32054
32320
|
filterTables: [],
|
|
32321
|
+
headerGroups: { COL: colHeaderGroups, ROW: rowHeaderGroups },
|
|
32055
32322
|
};
|
|
32056
32323
|
});
|
|
32057
32324
|
}
|
|
32058
|
-
function convertCols(sheet, numberOfCols) {
|
|
32325
|
+
function convertCols(sheet, numberOfCols, headerGroups) {
|
|
32059
32326
|
const cols = {};
|
|
32060
32327
|
// Excel begins indexes at 1
|
|
32061
32328
|
for (let i = 1; i < numberOfCols + 1; i++) {
|
|
@@ -32067,11 +32334,18 @@ function convertCols(sheet, numberOfCols) {
|
|
|
32067
32334
|
colSize = sheet.sheetFormat.defaultColWidth;
|
|
32068
32335
|
else
|
|
32069
32336
|
colSize = EXCEL_DEFAULT_COL_WIDTH;
|
|
32070
|
-
|
|
32337
|
+
// In xlsx there is no difference between hidden columns and columns inside a folded group.
|
|
32338
|
+
// But in o-spreadsheet folded columns are not considered hidden.
|
|
32339
|
+
const colIndex = i - 1;
|
|
32340
|
+
const isColFolded = headerGroups.some((group) => group.isFolded && group.start <= colIndex && colIndex <= group.end);
|
|
32341
|
+
cols[colIndex] = {
|
|
32342
|
+
size: convertWidthFromExcel(colSize),
|
|
32343
|
+
isHidden: !isColFolded && col?.hidden,
|
|
32344
|
+
};
|
|
32071
32345
|
}
|
|
32072
32346
|
return cols;
|
|
32073
32347
|
}
|
|
32074
|
-
function convertRows(sheet, numberOfRows) {
|
|
32348
|
+
function convertRows(sheet, numberOfRows, headerGroups) {
|
|
32075
32349
|
const rows = {};
|
|
32076
32350
|
// Excel begins indexes at 1
|
|
32077
32351
|
for (let i = 1; i < numberOfRows + 1; i++) {
|
|
@@ -32083,7 +32357,14 @@ function convertRows(sheet, numberOfRows) {
|
|
|
32083
32357
|
rowSize = sheet.sheetFormat.defaultRowHeight;
|
|
32084
32358
|
else
|
|
32085
32359
|
rowSize = EXCEL_DEFAULT_ROW_HEIGHT;
|
|
32086
|
-
|
|
32360
|
+
// In xlsx there is no difference between hidden rows and rows inside a folded group.
|
|
32361
|
+
// But in o-spreadsheet folded rows are not considered hidden.
|
|
32362
|
+
const rowIndex = i - 1;
|
|
32363
|
+
const isRowFolded = headerGroups.some((group) => group.isFolded && group.start <= rowIndex && rowIndex <= group.end);
|
|
32364
|
+
rows[rowIndex] = {
|
|
32365
|
+
size: convertHeightFromExcel(rowSize),
|
|
32366
|
+
isHidden: !isRowFolded && row?.hidden,
|
|
32367
|
+
};
|
|
32087
32368
|
}
|
|
32088
32369
|
return rows;
|
|
32089
32370
|
}
|
|
@@ -32187,6 +32468,57 @@ function getSheetDims(sheet) {
|
|
|
32187
32468
|
dims[1] = Math.max(dims[1], EXCEL_IMPORT_DEFAULT_NUMBER_OF_ROWS);
|
|
32188
32469
|
return dims;
|
|
32189
32470
|
}
|
|
32471
|
+
/**
|
|
32472
|
+
* Get the header groups from the XLS file.
|
|
32473
|
+
*
|
|
32474
|
+
* See ASCII art in HeaderGroupingPlugin.exportForExcel() for details on how the groups are defined in the xlsx.
|
|
32475
|
+
*/
|
|
32476
|
+
function convertHeaderGroup(sheet, dim, numberOfHeaders) {
|
|
32477
|
+
const outlineProperties = sheet?.sheetProperties?.outlinePr;
|
|
32478
|
+
const headerGroups = [];
|
|
32479
|
+
let currentLayer = 0;
|
|
32480
|
+
for (let i = 0; i < numberOfHeaders; i++) {
|
|
32481
|
+
const header = getHeader(sheet, dim, i);
|
|
32482
|
+
const headerLayer = header?.outlineLevel || 0;
|
|
32483
|
+
if (headerLayer > currentLayer) {
|
|
32484
|
+
// Whether the flag indicating if the group is collapsed is on the header before or after the group. Default is after.
|
|
32485
|
+
const collapseFlagAfter = (dim === "ROW" ? outlineProperties?.summaryBelow : outlineProperties?.summaryRight) ?? true;
|
|
32486
|
+
const group = computeHeaderGroup(sheet, dim, i, collapseFlagAfter);
|
|
32487
|
+
if (group) {
|
|
32488
|
+
headerGroups.push(group);
|
|
32489
|
+
}
|
|
32490
|
+
}
|
|
32491
|
+
currentLayer = headerLayer;
|
|
32492
|
+
}
|
|
32493
|
+
return headerGroups;
|
|
32494
|
+
}
|
|
32495
|
+
function computeHeaderGroup(sheet, dim, startIndex, collapseFlagAfter) {
|
|
32496
|
+
const startHeader = getHeader(sheet, dim, startIndex);
|
|
32497
|
+
const startLayer = startHeader?.outlineLevel;
|
|
32498
|
+
if (!startLayer || !startLayer) {
|
|
32499
|
+
return undefined;
|
|
32500
|
+
}
|
|
32501
|
+
let currentLayer = startLayer;
|
|
32502
|
+
let currentIndex = startIndex;
|
|
32503
|
+
let currentHeader = startHeader;
|
|
32504
|
+
while (currentHeader && currentLayer >= startLayer) {
|
|
32505
|
+
currentIndex++;
|
|
32506
|
+
currentHeader = getHeader(sheet, dim, currentIndex);
|
|
32507
|
+
currentLayer = currentHeader?.outlineLevel || 0;
|
|
32508
|
+
}
|
|
32509
|
+
const start = startIndex;
|
|
32510
|
+
const end = currentIndex - 1;
|
|
32511
|
+
const collapseFlagHeader = collapseFlagAfter
|
|
32512
|
+
? getHeader(sheet, dim, end + 1)
|
|
32513
|
+
: getHeader(sheet, dim, start - 1);
|
|
32514
|
+
const isFolded = collapseFlagHeader?.collapsed || false;
|
|
32515
|
+
return { start: start - 1, end: end - 1, isFolded }; // -1 because indices start at 1 in excel and 0 in o-spreadsheet
|
|
32516
|
+
}
|
|
32517
|
+
function getHeader(sheet, dim, index) {
|
|
32518
|
+
return "COL" === dim
|
|
32519
|
+
? sheet.cols.find((col) => col.min <= index && index <= col.max)
|
|
32520
|
+
: sheet.rows.find((row) => row.index === index);
|
|
32521
|
+
}
|
|
32190
32522
|
|
|
32191
32523
|
const TABLE_HEADER_STYLE = {
|
|
32192
32524
|
fillColor: "#000000",
|
|
@@ -33247,6 +33579,7 @@ class XlsxSheetExtractor extends XlsxBaseExtractor {
|
|
|
33247
33579
|
sheetName: this.extractSheetName(),
|
|
33248
33580
|
sheetViews: this.extractSheetViews(sheetElement),
|
|
33249
33581
|
sheetFormat: this.extractSheetFormat(sheetElement),
|
|
33582
|
+
sheetProperties: this.extractSheetProperties(sheetElement),
|
|
33250
33583
|
cols: this.extractCols(sheetElement),
|
|
33251
33584
|
rows: this.extractRows(sheetElement),
|
|
33252
33585
|
sharedFormulas: this.extractSharedFormulas(sheetElement),
|
|
@@ -33372,6 +33705,23 @@ class XlsxSheetExtractor extends XlsxBaseExtractor {
|
|
|
33372
33705
|
}).asNum(),
|
|
33373
33706
|
};
|
|
33374
33707
|
}
|
|
33708
|
+
extractSheetProperties(worksheet) {
|
|
33709
|
+
const propertiesElement = this.querySelector(worksheet, "sheetPr");
|
|
33710
|
+
if (!propertiesElement)
|
|
33711
|
+
return undefined;
|
|
33712
|
+
return {
|
|
33713
|
+
outlinePr: this.extractSheetOutlineProperties(propertiesElement),
|
|
33714
|
+
};
|
|
33715
|
+
}
|
|
33716
|
+
extractSheetOutlineProperties(sheetProperties) {
|
|
33717
|
+
const properties = this.querySelector(sheetProperties, "outlinePr");
|
|
33718
|
+
if (!properties)
|
|
33719
|
+
return undefined;
|
|
33720
|
+
return {
|
|
33721
|
+
summaryBelow: this.extractAttr(properties, "summaryBelow", { default: true }).asBool(),
|
|
33722
|
+
summaryRight: this.extractAttr(properties, "summaryRight", { default: true }).asBool(),
|
|
33723
|
+
};
|
|
33724
|
+
}
|
|
33375
33725
|
extractCols(worksheet) {
|
|
33376
33726
|
return this.mapOnElements({ parent: worksheet, query: "cols col" }, (colElement) => {
|
|
33377
33727
|
return {
|
|
@@ -33382,6 +33732,8 @@ class XlsxSheetExtractor extends XlsxBaseExtractor {
|
|
|
33382
33732
|
min: this.extractAttr(colElement, "min", { required: true })?.asNum(),
|
|
33383
33733
|
max: this.extractAttr(colElement, "max", { required: true })?.asNum(),
|
|
33384
33734
|
styleIndex: this.extractAttr(colElement, "style")?.asNum(),
|
|
33735
|
+
outlineLevel: this.extractAttr(colElement, "outlineLevel")?.asNum(),
|
|
33736
|
+
collapsed: this.extractAttr(colElement, "collapsed")?.asBool(),
|
|
33385
33737
|
};
|
|
33386
33738
|
});
|
|
33387
33739
|
}
|
|
@@ -33394,6 +33746,8 @@ class XlsxSheetExtractor extends XlsxBaseExtractor {
|
|
|
33394
33746
|
customHeight: this.extractAttr(rowElement, "customHeight")?.asBool(),
|
|
33395
33747
|
hidden: this.extractAttr(rowElement, "hidden")?.asBool(),
|
|
33396
33748
|
styleIndex: this.extractAttr(rowElement, "s")?.asNum(),
|
|
33749
|
+
outlineLevel: this.extractAttr(rowElement, "outlineLevel")?.asNum(),
|
|
33750
|
+
collapsed: this.extractAttr(rowElement, "collapsed")?.asBool(),
|
|
33397
33751
|
};
|
|
33398
33752
|
});
|
|
33399
33753
|
}
|
|
@@ -35927,6 +36281,26 @@ class CellPlugin extends CorePlugin {
|
|
|
35927
36281
|
format: "",
|
|
35928
36282
|
});
|
|
35929
36283
|
break;
|
|
36284
|
+
case "DELETE_CONTENT":
|
|
36285
|
+
this.clearZones(cmd.sheetId, cmd.target);
|
|
36286
|
+
break;
|
|
36287
|
+
}
|
|
36288
|
+
}
|
|
36289
|
+
clearZones(sheetId, zones) {
|
|
36290
|
+
for (let zone of zones) {
|
|
36291
|
+
for (let col = zone.left; col <= zone.right; col++) {
|
|
36292
|
+
for (let row = zone.top; row <= zone.bottom; row++) {
|
|
36293
|
+
const cell = this.getters.getCell({ sheetId, col, row });
|
|
36294
|
+
if (cell) {
|
|
36295
|
+
this.dispatch("UPDATE_CELL", {
|
|
36296
|
+
sheetId: sheetId,
|
|
36297
|
+
content: "",
|
|
36298
|
+
col,
|
|
36299
|
+
row,
|
|
36300
|
+
});
|
|
36301
|
+
}
|
|
36302
|
+
}
|
|
36303
|
+
}
|
|
35930
36304
|
}
|
|
35931
36305
|
}
|
|
35932
36306
|
/**
|
|
@@ -36988,6 +37362,28 @@ class DataValidationPlugin extends CorePlugin {
|
|
|
36988
37362
|
this.addDataValidationRule(cmd.sheetId, { ...cmd.rule, ranges });
|
|
36989
37363
|
break;
|
|
36990
37364
|
}
|
|
37365
|
+
case "DELETE_CONTENT": {
|
|
37366
|
+
const zones = cmd.target;
|
|
37367
|
+
const sheetId = cmd.sheetId;
|
|
37368
|
+
for (const zone of zones) {
|
|
37369
|
+
for (let row = zone.top; row <= zone.bottom; row++) {
|
|
37370
|
+
for (let col = zone.left; col <= zone.right; col++) {
|
|
37371
|
+
const dataValidation = this.getValidationRuleForCell({ sheetId, col, row });
|
|
37372
|
+
if (!dataValidation) {
|
|
37373
|
+
continue;
|
|
37374
|
+
}
|
|
37375
|
+
if (dataValidation.criterion.type === "isBoolean" ||
|
|
37376
|
+
(dataValidation.criterion.type === "isValueInList" &&
|
|
37377
|
+
!this.getters.getCell({ sheetId, col, row })?.content)) {
|
|
37378
|
+
const rules = this.rules[sheetId];
|
|
37379
|
+
const ranges = [this.getters.getRangeFromSheetXC(sheetId, toXC(col, row))];
|
|
37380
|
+
const adaptedRules = this.removeRangesFromRules(sheetId, ranges, rules);
|
|
37381
|
+
this.history.update("rules", sheetId, adaptedRules);
|
|
37382
|
+
}
|
|
37383
|
+
}
|
|
37384
|
+
}
|
|
37385
|
+
}
|
|
37386
|
+
}
|
|
36991
37387
|
}
|
|
36992
37388
|
}
|
|
36993
37389
|
getDataValidationRules(sheetId) {
|
|
@@ -37048,13 +37444,12 @@ class DataValidationPlugin extends CorePlugin {
|
|
|
37048
37444
|
setCenterStyleToBooleanCells(rule) {
|
|
37049
37445
|
for (const position of getCellPositionsInRanges(rule.ranges)) {
|
|
37050
37446
|
const cell = this.getters.getCell(position);
|
|
37051
|
-
const { sheetId, col, row } = position;
|
|
37052
37447
|
const style = {
|
|
37053
37448
|
...cell?.style,
|
|
37054
37449
|
align: cell?.style?.align ?? "center",
|
|
37055
37450
|
verticalAlign: cell?.style?.verticalAlign ?? "middle",
|
|
37056
37451
|
};
|
|
37057
|
-
this.dispatch("UPDATE_CELL", {
|
|
37452
|
+
this.dispatch("UPDATE_CELL", { ...position, style });
|
|
37058
37453
|
}
|
|
37059
37454
|
}
|
|
37060
37455
|
import(data) {
|
|
@@ -37892,7 +38287,7 @@ class HeaderVisibilityPlugin extends CorePlugin {
|
|
|
37892
38287
|
if (sheet.rows[row] === undefined) {
|
|
37893
38288
|
sheet.rows[row] = {};
|
|
37894
38289
|
}
|
|
37895
|
-
sheet.rows[row].isHidden
|
|
38290
|
+
sheet.rows[row].isHidden ||= this.hiddenHeaders[sheet.id]["ROW"][row];
|
|
37896
38291
|
}
|
|
37897
38292
|
}
|
|
37898
38293
|
if (sheet.cols === undefined) {
|
|
@@ -37903,7 +38298,7 @@ class HeaderVisibilityPlugin extends CorePlugin {
|
|
|
37903
38298
|
if (sheet.cols[col] === undefined) {
|
|
37904
38299
|
sheet.cols[col] = {};
|
|
37905
38300
|
}
|
|
37906
|
-
sheet.cols[col].isHidden
|
|
38301
|
+
sheet.cols[col].isHidden ||= this.hiddenHeaders[sheet.id]["COL"][col];
|
|
37907
38302
|
}
|
|
37908
38303
|
}
|
|
37909
38304
|
}
|
|
@@ -38861,9 +39256,9 @@ class RangeAdapter {
|
|
|
38861
39256
|
* Get a Xc string that represent a part of a range
|
|
38862
39257
|
*/
|
|
38863
39258
|
getRangePartString(range, part) {
|
|
38864
|
-
const colFixed = range.parts && range.parts[part]
|
|
39259
|
+
const colFixed = range.parts && range.parts[part]?.colFixed ? "$" : "";
|
|
38865
39260
|
const col = part === 0 ? numberToLetters(range.zone.left) : numberToLetters(range.zone.right);
|
|
38866
|
-
const rowFixed = range.parts && range.parts[part]
|
|
39261
|
+
const rowFixed = range.parts && range.parts[part]?.rowFixed ? "$" : "";
|
|
38867
39262
|
const row = part === 0 ? String(range.zone.top + 1) : String(range.zone.bottom + 1);
|
|
38868
39263
|
let str = "";
|
|
38869
39264
|
if (range.isFullCol) {
|
|
@@ -39003,9 +39398,6 @@ class SheetPlugin extends CorePlugin {
|
|
|
39003
39398
|
case "SET_GRID_LINES_VISIBILITY":
|
|
39004
39399
|
this.setGridLinesVisibility(cmd.sheetId, cmd.areGridLinesVisible);
|
|
39005
39400
|
break;
|
|
39006
|
-
case "DELETE_CONTENT":
|
|
39007
|
-
this.clearZones(cmd.sheetId, cmd.target);
|
|
39008
|
-
break;
|
|
39009
39401
|
case "CREATE_SHEET":
|
|
39010
39402
|
const sheet = this.createSheet(cmd.sheetId, cmd.name || this.getNextSheetName(), cmd.cols || 26, cmd.rows || 100, cmd.position);
|
|
39011
39403
|
this.history.update("sheetIdsMapName", sheet.name, sheet.id);
|
|
@@ -39372,23 +39764,6 @@ class SheetPlugin extends CorePlugin {
|
|
|
39372
39764
|
setGridLinesVisibility(sheetId, areGridLinesVisible) {
|
|
39373
39765
|
this.history.update("sheets", sheetId, "areGridLinesVisible", areGridLinesVisible);
|
|
39374
39766
|
}
|
|
39375
|
-
clearZones(sheetId, zones) {
|
|
39376
|
-
for (let zone of zones) {
|
|
39377
|
-
for (let col = zone.left; col <= zone.right; col++) {
|
|
39378
|
-
for (let row = zone.top; row <= zone.bottom; row++) {
|
|
39379
|
-
const cell = this.sheets[sheetId].rows[row].cells[col];
|
|
39380
|
-
if (cell) {
|
|
39381
|
-
this.dispatch("UPDATE_CELL", {
|
|
39382
|
-
sheetId: sheetId,
|
|
39383
|
-
content: "",
|
|
39384
|
-
col,
|
|
39385
|
-
row,
|
|
39386
|
-
});
|
|
39387
|
-
}
|
|
39388
|
-
}
|
|
39389
|
-
}
|
|
39390
|
-
}
|
|
39391
|
-
}
|
|
39392
39767
|
createSheet(id, name, colNumber, rowNumber, position) {
|
|
39393
39768
|
const sheet = {
|
|
39394
39769
|
id,
|
|
@@ -40178,6 +40553,41 @@ class HeaderGroupingPlugin extends CorePlugin {
|
|
|
40178
40553
|
sheet.headerGroups = this.groups[sheet.id];
|
|
40179
40554
|
}
|
|
40180
40555
|
}
|
|
40556
|
+
exportForExcel(data) {
|
|
40557
|
+
/**
|
|
40558
|
+
* Example of header groups in the XLSX file:
|
|
40559
|
+
*
|
|
40560
|
+
* 0. | <row index="1" outlineLevel="1">
|
|
40561
|
+
* 1. | | <row index="2" outlineLevel="2">
|
|
40562
|
+
* 2. | | <row index="3" outlineLevel="2">
|
|
40563
|
+
* 3. | |_ <row index="4" outlineLevel="2">
|
|
40564
|
+
* 4. |_ <row index="5" outlineLevel="1" collapsed="0">
|
|
40565
|
+
* 5. <row index="6" collapsed="0">
|
|
40566
|
+
*
|
|
40567
|
+
* The collapsed flag can be on the header before or after the group (or can be missing). Default is after.
|
|
40568
|
+
*/
|
|
40569
|
+
for (const sheet of data.sheets) {
|
|
40570
|
+
for (const dim of ["ROW", "COL"]) {
|
|
40571
|
+
const layers = this.getGroupsLayers(sheet.id, dim);
|
|
40572
|
+
for (let layerIndex = 0; layerIndex < layers.length; layerIndex++) {
|
|
40573
|
+
const layer = layers[layerIndex];
|
|
40574
|
+
for (const group of layer) {
|
|
40575
|
+
for (let headerIndex = group.start; headerIndex <= group.end; headerIndex++) {
|
|
40576
|
+
const header = getSheetDataHeader(sheet, dim, headerIndex);
|
|
40577
|
+
header.outlineLevel = layerIndex + 1;
|
|
40578
|
+
if (group.isFolded) {
|
|
40579
|
+
header.isHidden = true;
|
|
40580
|
+
}
|
|
40581
|
+
}
|
|
40582
|
+
if (group.isFolded) {
|
|
40583
|
+
const header = getSheetDataHeader(sheet, dim, group.end + 1);
|
|
40584
|
+
header.collapsed = true;
|
|
40585
|
+
}
|
|
40586
|
+
}
|
|
40587
|
+
}
|
|
40588
|
+
}
|
|
40589
|
+
}
|
|
40590
|
+
}
|
|
40181
40591
|
}
|
|
40182
40592
|
|
|
40183
40593
|
class SettingsPlugin extends CorePlugin {
|
|
@@ -40295,23 +40705,18 @@ class CompilationParametersBuilder {
|
|
|
40295
40705
|
* The `compute` of the formula's function must process it completely
|
|
40296
40706
|
*/
|
|
40297
40707
|
refFn(range, isMeta, functionName, paramNumber) {
|
|
40708
|
+
this.assertRangeValid(range);
|
|
40298
40709
|
if (isMeta) {
|
|
40299
40710
|
// Use zoneToXc of zone instead of getRangeString to avoid sending unbounded ranges
|
|
40300
40711
|
const sheetName = this.getters.getSheetName(range.sheetId);
|
|
40301
40712
|
return { value: getFullReference(sheetName, zoneToXc(range.zone)) };
|
|
40302
40713
|
}
|
|
40303
|
-
if (!isZoneValid(range.zone)) {
|
|
40304
|
-
throw new InvalidReferenceError();
|
|
40305
|
-
}
|
|
40306
40714
|
// if the formula definition could have accepted a range, we would pass through the _range function and not here
|
|
40307
40715
|
if (range.zone.bottom !== range.zone.top || range.zone.left !== range.zone.right) {
|
|
40308
40716
|
throw new EvaluationError(paramNumber
|
|
40309
40717
|
? _t("Function %s expects the parameter %s to be a single value or a single cell reference, not a range.", functionName.toString(), paramNumber.toString())
|
|
40310
40718
|
: _t("Function %s expects its parameters to be single values or single cell references, not ranges.", functionName.toString()));
|
|
40311
40719
|
}
|
|
40312
|
-
if (range.invalidSheetName) {
|
|
40313
|
-
throw new EvaluationError(_t("Invalid sheet name: %s", range.invalidSheetName));
|
|
40314
|
-
}
|
|
40315
40720
|
const position = { sheetId: range.sheetId, col: range.zone.left, row: range.zone.top };
|
|
40316
40721
|
return this.readCell(position);
|
|
40317
40722
|
}
|
|
@@ -40343,10 +40748,10 @@ class CompilationParametersBuilder {
|
|
|
40343
40748
|
* Note that each col is possibly sparse: it only contain the values of cells
|
|
40344
40749
|
* that are actually present in the grid.
|
|
40345
40750
|
*/
|
|
40346
|
-
range(
|
|
40347
|
-
|
|
40348
|
-
|
|
40349
|
-
|
|
40751
|
+
range(range) {
|
|
40752
|
+
this.assertRangeValid(range);
|
|
40753
|
+
const sheetId = range.sheetId;
|
|
40754
|
+
const zone = range.zone;
|
|
40350
40755
|
// Performance issue: Avoid fetching data on positions that are out of the spreadsheet
|
|
40351
40756
|
// e.g. A1:ZZZ9999 in a sheet with 10 cols and 10 rows should ignore everything past J10 and return a 10x10 array
|
|
40352
40757
|
const sheetZone = this.getters.getSheetZone(sheetId);
|
|
@@ -40374,6 +40779,14 @@ class CompilationParametersBuilder {
|
|
|
40374
40779
|
this.rangeCache[cacheKey] = matrix;
|
|
40375
40780
|
return matrix;
|
|
40376
40781
|
}
|
|
40782
|
+
assertRangeValid(range) {
|
|
40783
|
+
if (!isZoneValid(range.zone)) {
|
|
40784
|
+
throw new InvalidReferenceError();
|
|
40785
|
+
}
|
|
40786
|
+
if (range.invalidSheetName) {
|
|
40787
|
+
throw new EvaluationError(_t("Invalid sheet name: %s", range.invalidSheetName));
|
|
40788
|
+
}
|
|
40789
|
+
}
|
|
40377
40790
|
}
|
|
40378
40791
|
|
|
40379
40792
|
function quickselect(arr, k, left, right, compare) {
|
|
@@ -42504,11 +42917,25 @@ class EvaluationDataValidationPlugin extends UIPlugin {
|
|
|
42504
42917
|
}
|
|
42505
42918
|
switch (cmd.type) {
|
|
42506
42919
|
case "ADD_DATA_VALIDATION_RULE":
|
|
42920
|
+
const ranges = cmd.ranges.map((range) => this.getters.getRangeFromRangeData(range));
|
|
42921
|
+
if (cmd.rule.criterion.type === "isBoolean") {
|
|
42922
|
+
this.setContentToBooleanCells({ ...cmd.rule, ranges });
|
|
42923
|
+
}
|
|
42924
|
+
delete this.validationResults[cmd.sheetId];
|
|
42925
|
+
break;
|
|
42507
42926
|
case "REMOVE_DATA_VALIDATION_RULE":
|
|
42508
42927
|
delete this.validationResults[cmd.sheetId];
|
|
42509
42928
|
break;
|
|
42510
42929
|
}
|
|
42511
42930
|
}
|
|
42931
|
+
setContentToBooleanCells(rule) {
|
|
42932
|
+
for (const position of getCellPositionsInRanges(rule.ranges)) {
|
|
42933
|
+
const evaluatedCell = this.getters.getEvaluatedCell(position);
|
|
42934
|
+
if (evaluatedCell.type !== CellValueType.boolean) {
|
|
42935
|
+
this.dispatch("UPDATE_CELL", { ...position, content: "FALSE" });
|
|
42936
|
+
}
|
|
42937
|
+
}
|
|
42938
|
+
}
|
|
42512
42939
|
isDataValidationInvalid(cellPosition) {
|
|
42513
42940
|
return !this.getValidationResultForCell(cellPosition).isValid;
|
|
42514
42941
|
}
|
|
@@ -45061,6 +45488,11 @@ class ClipboardCellsState extends ClipboardCellsAbstractState {
|
|
|
45061
45488
|
pasteDataValidation(origin, target) {
|
|
45062
45489
|
const rule = this.getters.getValidationRuleForCell(origin);
|
|
45063
45490
|
if (!rule) {
|
|
45491
|
+
const targetRule = this.getters.getValidationRuleForCell(target);
|
|
45492
|
+
if (targetRule) {
|
|
45493
|
+
// Remove the data validation rule on the target cell
|
|
45494
|
+
this.adaptDataValidationRule(target.sheetId, targetRule, [], [toXC(target.col, target.row)]);
|
|
45495
|
+
}
|
|
45064
45496
|
return;
|
|
45065
45497
|
}
|
|
45066
45498
|
const xc = toXC(target.col, target.row);
|
|
@@ -45366,12 +45798,8 @@ class FindAndReplacePlugin extends UIPlugin {
|
|
|
45366
45798
|
case "ADD_COLUMNS_ROWS":
|
|
45367
45799
|
case "EVALUATE_CELLS":
|
|
45368
45800
|
case "UPDATE_CELL":
|
|
45369
|
-
this.isSearchDirty = true;
|
|
45370
|
-
break;
|
|
45371
45801
|
case "ACTIVATE_SHEET":
|
|
45372
|
-
|
|
45373
|
-
this.isSearchDirty = true;
|
|
45374
|
-
}
|
|
45802
|
+
this.isSearchDirty = true;
|
|
45375
45803
|
break;
|
|
45376
45804
|
}
|
|
45377
45805
|
}
|
|
@@ -46535,27 +46963,6 @@ class SelectionInputPlugin extends UIPlugin {
|
|
|
46535
46963
|
// ---------------------------------------------------------------------------
|
|
46536
46964
|
// Command Handling
|
|
46537
46965
|
// ---------------------------------------------------------------------------
|
|
46538
|
-
allowDispatch(cmd) {
|
|
46539
|
-
switch (cmd.type) {
|
|
46540
|
-
case "ADD_RANGE":
|
|
46541
|
-
case "ADD_EMPTY_RANGE":
|
|
46542
|
-
if (this.inputHasSingleRange && this.ranges.length === 1) {
|
|
46543
|
-
return "MaximumRangesReached" /* CommandResult.MaximumRangesReached */;
|
|
46544
|
-
}
|
|
46545
|
-
break;
|
|
46546
|
-
case "REMOVE_RANGE":
|
|
46547
|
-
if (this.ranges.length === 1) {
|
|
46548
|
-
return "MinimumRangesReached" /* CommandResult.MinimumRangesReached */;
|
|
46549
|
-
}
|
|
46550
|
-
break;
|
|
46551
|
-
case "CHANGE_RANGE":
|
|
46552
|
-
if (this.inputHasSingleRange && cmd.value.split(",").length > 1) {
|
|
46553
|
-
return "MaximumRangesReached" /* CommandResult.MaximumRangesReached */;
|
|
46554
|
-
}
|
|
46555
|
-
break;
|
|
46556
|
-
}
|
|
46557
|
-
return "Success" /* CommandResult.Success */;
|
|
46558
|
-
}
|
|
46559
46966
|
handleEvent(event) {
|
|
46560
46967
|
if (this.focusedRangeIndex === null) {
|
|
46561
46968
|
return;
|
|
@@ -46793,6 +47200,15 @@ class SelectionInputsManagerPlugin extends UIPlugin {
|
|
|
46793
47200
|
// Command Handling
|
|
46794
47201
|
// ---------------------------------------------------------------------------
|
|
46795
47202
|
allowDispatch(cmd) {
|
|
47203
|
+
switch (cmd.type) {
|
|
47204
|
+
case "FOCUS_RANGE":
|
|
47205
|
+
case "CHANGE_RANGE":
|
|
47206
|
+
case "ADD_EMPTY_RANGE":
|
|
47207
|
+
case "REMOVE_RANGE":
|
|
47208
|
+
if (!this.inputs[cmd.id]) {
|
|
47209
|
+
return "InvalidInputId" /* CommandResult.InvalidInputId */;
|
|
47210
|
+
}
|
|
47211
|
+
}
|
|
46796
47212
|
switch (cmd.type) {
|
|
46797
47213
|
case "FOCUS_RANGE":
|
|
46798
47214
|
const index = this.currentInput?.getIndex(cmd.rangeId);
|
|
@@ -46800,9 +47216,25 @@ class SelectionInputsManagerPlugin extends UIPlugin {
|
|
|
46800
47216
|
return "InputAlreadyFocused" /* CommandResult.InputAlreadyFocused */;
|
|
46801
47217
|
}
|
|
46802
47218
|
break;
|
|
46803
|
-
|
|
46804
|
-
|
|
46805
|
-
|
|
47219
|
+
case "ADD_RANGE":
|
|
47220
|
+
case "ADD_EMPTY_RANGE":
|
|
47221
|
+
const input = this.inputs[cmd.id];
|
|
47222
|
+
if (input.inputHasSingleRange && input.ranges.length === 1) {
|
|
47223
|
+
return "MaximumRangesReached" /* CommandResult.MaximumRangesReached */;
|
|
47224
|
+
}
|
|
47225
|
+
break;
|
|
47226
|
+
case "REMOVE_RANGE":
|
|
47227
|
+
if (this.inputs[cmd.id].ranges.length === 1) {
|
|
47228
|
+
return "MinimumRangesReached" /* CommandResult.MinimumRangesReached */;
|
|
47229
|
+
}
|
|
47230
|
+
break;
|
|
47231
|
+
case "CHANGE_RANGE": {
|
|
47232
|
+
const input = this.inputs[cmd.id];
|
|
47233
|
+
if (input.inputHasSingleRange && cmd.value.split(",").length > 1) {
|
|
47234
|
+
return "MaximumRangesReached" /* CommandResult.MaximumRangesReached */;
|
|
47235
|
+
}
|
|
47236
|
+
break;
|
|
47237
|
+
}
|
|
46806
47238
|
}
|
|
46807
47239
|
return "Success" /* CommandResult.Success */;
|
|
46808
47240
|
}
|
|
@@ -48932,9 +49364,10 @@ class EditionPlugin extends UIPlugin {
|
|
|
48932
49364
|
*/
|
|
48933
49365
|
getReferencedRanges() {
|
|
48934
49366
|
const editionSheetId = this.getters.getCurrentEditedCell().sheetId;
|
|
48935
|
-
|
|
49367
|
+
const referenceRanges = this.currentTokens
|
|
48936
49368
|
.filter((token) => token.type === "REFERENCE")
|
|
48937
49369
|
.map((token) => this.getters.getRangeFromSheetXC(editionSheetId, token.value));
|
|
49370
|
+
return referenceRanges.filter((range) => !range.invalidSheetName && !range.invalidXc);
|
|
48938
49371
|
}
|
|
48939
49372
|
getAutoCompleteDataValidationValues() {
|
|
48940
49373
|
if (this.mode === "inactive") {
|
|
@@ -50964,6 +51397,7 @@ const corePluginRegistry = new Registry()
|
|
|
50964
51397
|
.add("header grouping", HeaderGroupingPlugin)
|
|
50965
51398
|
.add("header visibility", HeaderVisibilityPlugin)
|
|
50966
51399
|
.add("filters", FiltersPlugin)
|
|
51400
|
+
.add("dataValidation", DataValidationPlugin)
|
|
50967
51401
|
.add("cell", CellPlugin)
|
|
50968
51402
|
.add("merge", MergePlugin)
|
|
50969
51403
|
.add("headerSize", HeaderSizePlugin)
|
|
@@ -50971,8 +51405,7 @@ const corePluginRegistry = new Registry()
|
|
|
50971
51405
|
.add("conditional formatting", ConditionalFormatPlugin)
|
|
50972
51406
|
.add("figures", FigurePlugin)
|
|
50973
51407
|
.add("chart", ChartPlugin)
|
|
50974
|
-
.add("image", ImagePlugin)
|
|
50975
|
-
.add("dataValidation", DataValidationPlugin);
|
|
51408
|
+
.add("image", ImagePlugin);
|
|
50976
51409
|
// Plugins which handle a specific feature, without handling any core commands
|
|
50977
51410
|
const featurePluginRegistry = new Registry()
|
|
50978
51411
|
.add("ui_sheet", SheetUIPlugin)
|
|
@@ -56005,6 +56438,12 @@ function addColumns(cols) {
|
|
|
56005
56438
|
["customWidth", 1],
|
|
56006
56439
|
["hidden", col.isHidden ? 1 : 0],
|
|
56007
56440
|
];
|
|
56441
|
+
if (col.outlineLevel) {
|
|
56442
|
+
attributes.push(["outlineLevel", col.outlineLevel]);
|
|
56443
|
+
}
|
|
56444
|
+
if (col.collapsed) {
|
|
56445
|
+
attributes.push(["collapsed", 1]);
|
|
56446
|
+
}
|
|
56008
56447
|
colNodes.push(escapeXml /*xml*/ `
|
|
56009
56448
|
<col ${formatAttributes(attributes)}/>
|
|
56010
56449
|
`);
|
|
@@ -56020,8 +56459,18 @@ function addRows(construct, data, sheet) {
|
|
|
56020
56459
|
for (let r = 0; r < sheet.rowNumber; r++) {
|
|
56021
56460
|
const rowAttrs = [["r", r + 1]];
|
|
56022
56461
|
const row = sheet.rows[r] || {};
|
|
56023
|
-
|
|
56024
|
-
|
|
56462
|
+
if (row.size && row.size !== DEFAULT_CELL_HEIGHT) {
|
|
56463
|
+
rowAttrs.push(["ht", convertHeightToExcel(row.size)], ["customHeight", 1]);
|
|
56464
|
+
}
|
|
56465
|
+
if (row.isHidden) {
|
|
56466
|
+
rowAttrs.push(["hidden", 1]);
|
|
56467
|
+
}
|
|
56468
|
+
if (row.outlineLevel) {
|
|
56469
|
+
rowAttrs.push(["outlineLevel", row.outlineLevel]);
|
|
56470
|
+
}
|
|
56471
|
+
if (row.collapsed) {
|
|
56472
|
+
rowAttrs.push(["collapsed", 1]);
|
|
56473
|
+
}
|
|
56025
56474
|
const cellNodes = [];
|
|
56026
56475
|
for (let c = 0; c < sheet.colNumber; c++) {
|
|
56027
56476
|
const xc = toXC(c, r);
|
|
@@ -56054,7 +56503,11 @@ function addRows(construct, data, sheet) {
|
|
|
56054
56503
|
`);
|
|
56055
56504
|
}
|
|
56056
56505
|
}
|
|
56057
|
-
if (cellNodes.length ||
|
|
56506
|
+
if (cellNodes.length ||
|
|
56507
|
+
row.size !== DEFAULT_CELL_HEIGHT ||
|
|
56508
|
+
row.isHidden ||
|
|
56509
|
+
row.outlineLevel ||
|
|
56510
|
+
row.collapsed) {
|
|
56058
56511
|
rowNodes.push(escapeXml /*xml*/ `
|
|
56059
56512
|
<row ${formatAttributes(rowAttrs)}>
|
|
56060
56513
|
${joinXmlNodes(cellNodes)}
|
|
@@ -56988,6 +57441,13 @@ const links = {
|
|
|
56988
57441
|
urlRepresentation,
|
|
56989
57442
|
};
|
|
56990
57443
|
const components = {
|
|
57444
|
+
Checkbox,
|
|
57445
|
+
Section,
|
|
57446
|
+
ChartColor,
|
|
57447
|
+
ChartDataSeries,
|
|
57448
|
+
ChartErrorSection,
|
|
57449
|
+
ChartLabelRange,
|
|
57450
|
+
ChartTitle,
|
|
56991
57451
|
ChartPanel,
|
|
56992
57452
|
ChartFigure,
|
|
56993
57453
|
ChartJsComponent,
|
|
@@ -57023,6 +57483,6 @@ const constants = {
|
|
|
57023
57483
|
export { AbstractChart, CellErrorType, CommandResult, CorePlugin, DispatchResult, EvaluationError, Model, Registry, Revision, SPREADSHEET_DIMENSIONS, Spreadsheet, UIPlugin, __info__, addFunction, astToFormula, compile, compileTokens, components, constants, convertAstNodes, coreTypes, findCellInNewZone, functionCache, helpers, hooks, invalidateCFEvaluationCommands, invalidateDependenciesCommands, invalidateEvaluationCommands, iterateAstNodes, links, load, parse, parseTokens, readonlyAllowedCommands, registries, setDefaultSheetViewSize, setTranslationMethod, tokenize };
|
|
57024
57484
|
|
|
57025
57485
|
|
|
57026
|
-
__info__.version = "17.2.0-alpha.
|
|
57027
|
-
__info__.date = "2024-01-
|
|
57028
|
-
__info__.hash = "
|
|
57486
|
+
__info__.version = "17.2.0-alpha.2";
|
|
57487
|
+
__info__.date = "2024-01-29T13:55:57.132Z";
|
|
57488
|
+
__info__.hash = "03c1335";
|