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