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