@odoo/o-spreadsheet 18.3.0-alpha.2 → 18.3.0-alpha.3
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 +1971 -844
- package/dist/o-spreadsheet.d.ts +319 -747
- package/dist/o-spreadsheet.esm.js +1971 -844
- package/dist/o-spreadsheet.iife.js +1971 -844
- package/dist/o-spreadsheet.iife.min.js +462 -471
- package/dist/o_spreadsheet.xml +210 -168
- 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 18.3.0-alpha.
|
|
6
|
-
* @date 2025-
|
|
7
|
-
* @hash
|
|
5
|
+
* @version 18.3.0-alpha.3
|
|
6
|
+
* @date 2025-03-07T10:41:05.411Z
|
|
7
|
+
* @hash f59f5f6
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
'use strict';
|
|
@@ -351,7 +351,6 @@ var ComponentsImportance;
|
|
|
351
351
|
ComponentsImportance[ComponentsImportance["ScrollBar"] = 15] = "ScrollBar";
|
|
352
352
|
ComponentsImportance[ComponentsImportance["GridPopover"] = 19] = "GridPopover";
|
|
353
353
|
ComponentsImportance[ComponentsImportance["GridComposer"] = 20] = "GridComposer";
|
|
354
|
-
ComponentsImportance[ComponentsImportance["Dropdown"] = 21] = "Dropdown";
|
|
355
354
|
ComponentsImportance[ComponentsImportance["IconPicker"] = 25] = "IconPicker";
|
|
356
355
|
ComponentsImportance[ComponentsImportance["TopBarComposer"] = 30] = "TopBarComposer";
|
|
357
356
|
ComponentsImportance[ComponentsImportance["Popover"] = 35] = "Popover";
|
|
@@ -861,7 +860,7 @@ function insertItemsAtIndex(array, items, index) {
|
|
|
861
860
|
}
|
|
862
861
|
function replaceItemAtIndex(array, newItem, index) {
|
|
863
862
|
const newArray = [...array];
|
|
864
|
-
newArray
|
|
863
|
+
newArray[index] = newItem;
|
|
865
864
|
return newArray;
|
|
866
865
|
}
|
|
867
866
|
function trimContent(content) {
|
|
@@ -3403,6 +3402,7 @@ var ClipboardMIMEType;
|
|
|
3403
3402
|
(function (ClipboardMIMEType) {
|
|
3404
3403
|
ClipboardMIMEType["PlainText"] = "text/plain";
|
|
3405
3404
|
ClipboardMIMEType["Html"] = "text/html";
|
|
3405
|
+
ClipboardMIMEType["Image"] = "image";
|
|
3406
3406
|
})(ClipboardMIMEType || (ClipboardMIMEType = {}));
|
|
3407
3407
|
|
|
3408
3408
|
function isSheetDependent(cmd) {
|
|
@@ -6105,8 +6105,9 @@ function spreadRange(getters, dataSets) {
|
|
|
6105
6105
|
if (zone.bottom !== zone.top && zone.left != zone.right) {
|
|
6106
6106
|
if (zone.right) {
|
|
6107
6107
|
for (let j = zone.left; j <= zone.right; ++j) {
|
|
6108
|
+
const datasetOptions = j === zone.left ? dataSet : { yAxisId: dataSet.yAxisId };
|
|
6108
6109
|
postProcessedRanges.push({
|
|
6109
|
-
...
|
|
6110
|
+
...datasetOptions,
|
|
6110
6111
|
dataRange: `${sheetPrefix}${zoneToXc({
|
|
6111
6112
|
left: j,
|
|
6112
6113
|
right: j,
|
|
@@ -6118,8 +6119,9 @@ function spreadRange(getters, dataSets) {
|
|
|
6118
6119
|
}
|
|
6119
6120
|
else {
|
|
6120
6121
|
for (let j = zone.top; j <= zone.bottom; ++j) {
|
|
6122
|
+
const datasetOptions = j === zone.top ? dataSet : { yAxisId: dataSet.yAxisId };
|
|
6121
6123
|
postProcessedRanges.push({
|
|
6122
|
-
...
|
|
6124
|
+
...datasetOptions,
|
|
6123
6125
|
dataRange: `${sheetPrefix}${zoneToXc({
|
|
6124
6126
|
left: zone.left,
|
|
6125
6127
|
right: zone.right,
|
|
@@ -6558,6 +6560,17 @@ class UuidGenerator {
|
|
|
6558
6560
|
}
|
|
6559
6561
|
}
|
|
6560
6562
|
|
|
6563
|
+
const AllowedImageMimeTypes = [
|
|
6564
|
+
"image/avif",
|
|
6565
|
+
"image/bmp",
|
|
6566
|
+
"image/gif",
|
|
6567
|
+
"image/vnd.microsoft.icon",
|
|
6568
|
+
"image/jpeg",
|
|
6569
|
+
"image/png",
|
|
6570
|
+
"image/tiff",
|
|
6571
|
+
"image/webp",
|
|
6572
|
+
];
|
|
6573
|
+
|
|
6561
6574
|
function getClipboardDataPositions(sheetId, zones) {
|
|
6562
6575
|
const lefts = new Set(zones.map((z) => z.left));
|
|
6563
6576
|
const rights = new Set(zones.map((z) => z.right));
|
|
@@ -6604,21 +6617,28 @@ function getPasteZones(target, content) {
|
|
|
6604
6617
|
const width = content[0].length, height = content.length;
|
|
6605
6618
|
return target.map((t) => splitZoneForPaste(t, width, height)).flat();
|
|
6606
6619
|
}
|
|
6607
|
-
function parseOSClipboardContent(content) {
|
|
6608
|
-
|
|
6609
|
-
|
|
6610
|
-
|
|
6611
|
-
|
|
6620
|
+
function parseOSClipboardContent(content, clipboardId) {
|
|
6621
|
+
let spreadsheetContent = undefined;
|
|
6622
|
+
if (content[ClipboardMIMEType.Html]) {
|
|
6623
|
+
const htmlDocument = new DOMParser().parseFromString(content[ClipboardMIMEType.Html], "text/html");
|
|
6624
|
+
const oSheetClipboardData = htmlDocument
|
|
6625
|
+
.querySelector("div")
|
|
6626
|
+
?.getAttribute("data-osheet-clipboard");
|
|
6627
|
+
spreadsheetContent = oSheetClipboardData && JSON.parse(oSheetClipboardData);
|
|
6628
|
+
}
|
|
6629
|
+
let imageBlob = undefined;
|
|
6630
|
+
for (const type of AllowedImageMimeTypes) {
|
|
6631
|
+
if (content[type]) {
|
|
6632
|
+
imageBlob = content[type];
|
|
6633
|
+
break;
|
|
6634
|
+
}
|
|
6612
6635
|
}
|
|
6613
|
-
const
|
|
6614
|
-
const oSheetClipboardData = htmlDocument
|
|
6615
|
-
.querySelector("div")
|
|
6616
|
-
?.getAttribute("data-osheet-clipboard");
|
|
6617
|
-
const spreadsheetContent = oSheetClipboardData && JSON.parse(oSheetClipboardData);
|
|
6618
|
-
return {
|
|
6636
|
+
const osClipboardContent = {
|
|
6619
6637
|
text: content[ClipboardMIMEType.PlainText],
|
|
6620
6638
|
data: spreadsheetContent,
|
|
6639
|
+
imageBlob,
|
|
6621
6640
|
};
|
|
6641
|
+
return osClipboardContent;
|
|
6622
6642
|
}
|
|
6623
6643
|
|
|
6624
6644
|
class ClipboardHandler {
|
|
@@ -9681,6 +9701,91 @@ function getElementMargins(el) {
|
|
|
9681
9701
|
};
|
|
9682
9702
|
}
|
|
9683
9703
|
|
|
9704
|
+
class FunnelChartController extends window.Chart?.BarController {
|
|
9705
|
+
static id = "funnel";
|
|
9706
|
+
static defaults = { ...window.Chart?.BarController.defaults, dataElementType: "funnel" };
|
|
9707
|
+
/** Called at each chart render to update the elements of the chart (FunnelChartElement) with the updated data */
|
|
9708
|
+
updateElements(rects, start, count, mode) {
|
|
9709
|
+
super.updateElements(rects, start, count, mode);
|
|
9710
|
+
for (let i = start; i < start + count; i++) {
|
|
9711
|
+
const rect = rects[i];
|
|
9712
|
+
// Add the width of the next element to the element's props
|
|
9713
|
+
this.updateElement(rect, i, { nextElementWidth: rects[i + 1]?.width || 0 }, mode);
|
|
9714
|
+
}
|
|
9715
|
+
}
|
|
9716
|
+
}
|
|
9717
|
+
/**
|
|
9718
|
+
* Similar to a bar chart element, but it's a trapezoid rather than a rectangle. The top is of width
|
|
9719
|
+
* `width`, and the bottom is of width `nextElementWidth`.
|
|
9720
|
+
*/
|
|
9721
|
+
class FunnelChartElement extends window.Chart?.BarElement {
|
|
9722
|
+
static id = "funnel";
|
|
9723
|
+
/** Overwrite this to draw a trapezoid rather then a rectangle */
|
|
9724
|
+
draw(ctx) {
|
|
9725
|
+
ctx.save();
|
|
9726
|
+
const { x, y, width, height, nextElementWidth, base, options } = this.getProps([
|
|
9727
|
+
"x",
|
|
9728
|
+
"y",
|
|
9729
|
+
"width",
|
|
9730
|
+
"height",
|
|
9731
|
+
"nextElementWidth",
|
|
9732
|
+
"base",
|
|
9733
|
+
"options",
|
|
9734
|
+
]);
|
|
9735
|
+
const offset = (width - nextElementWidth) / 2;
|
|
9736
|
+
const startX = Math.min(x, base);
|
|
9737
|
+
const startY = y - height / 2;
|
|
9738
|
+
ctx.fillStyle = options.backgroundColor;
|
|
9739
|
+
ctx.beginPath();
|
|
9740
|
+
ctx.moveTo(startX, startY);
|
|
9741
|
+
ctx.lineTo(startX + width, startY);
|
|
9742
|
+
ctx.lineTo(startX + width - offset, startY + height);
|
|
9743
|
+
ctx.lineTo(startX + offset, startY + height);
|
|
9744
|
+
ctx.closePath();
|
|
9745
|
+
ctx.fill();
|
|
9746
|
+
if (options.borderWidth) {
|
|
9747
|
+
ctx.strokeStyle = options.borderColor;
|
|
9748
|
+
ctx.lineWidth = options.borderWidth;
|
|
9749
|
+
ctx.stroke();
|
|
9750
|
+
}
|
|
9751
|
+
ctx.restore();
|
|
9752
|
+
}
|
|
9753
|
+
/** Check if the mouse is inside the trapezoid */
|
|
9754
|
+
inRange(mouseX, mouseY, useFinalPosition) {
|
|
9755
|
+
const { x, y, width, height, nextElementWidth, base } = this.getProps(["x", "y", "width", "height", "nextElementWidth", "base"], useFinalPosition);
|
|
9756
|
+
const startX = Math.min(x, base);
|
|
9757
|
+
const startY = y - height / 2;
|
|
9758
|
+
if (mouseY < startY || mouseY > startY + height) {
|
|
9759
|
+
return false;
|
|
9760
|
+
}
|
|
9761
|
+
const offset = (width - nextElementWidth) / 2;
|
|
9762
|
+
const left = startX + (offset * (mouseY - startY)) / height;
|
|
9763
|
+
const right = startX + width - (offset * (mouseY - startY)) / height;
|
|
9764
|
+
if (mouseX < left || mouseX > right) {
|
|
9765
|
+
return false;
|
|
9766
|
+
}
|
|
9767
|
+
return true;
|
|
9768
|
+
}
|
|
9769
|
+
}
|
|
9770
|
+
/**
|
|
9771
|
+
* Position the tooltip inside the trapezoid.
|
|
9772
|
+
* The default position for tooltips of bar elements is at the end of rectangle, which is not ideal for trapezoids.
|
|
9773
|
+
*/
|
|
9774
|
+
const funnelTooltipPositioner = function (elements) {
|
|
9775
|
+
if (!elements.length) {
|
|
9776
|
+
return { x: 0, y: 0 };
|
|
9777
|
+
}
|
|
9778
|
+
const element = elements[0].element;
|
|
9779
|
+
const { x, y, base, width, height } = element.getProps(["x", "y", "width", "height", "base"]);
|
|
9780
|
+
const startX = Math.min(x, base);
|
|
9781
|
+
const startY = y - height / 2;
|
|
9782
|
+
return {
|
|
9783
|
+
x: startX + (width * 2) / 3,
|
|
9784
|
+
y: startY + height / 2,
|
|
9785
|
+
};
|
|
9786
|
+
};
|
|
9787
|
+
window.Chart.Tooltip.positioners.funnelTooltipPositioner = funnelTooltipPositioner;
|
|
9788
|
+
|
|
9684
9789
|
const TREND_LINE_XAXIS_ID = "x1";
|
|
9685
9790
|
/**
|
|
9686
9791
|
* This file contains helpers that are common to different charts (mainly
|
|
@@ -10061,6 +10166,9 @@ const chartShowValuesPlugin = {
|
|
|
10061
10166
|
? drawHorizontalBarChartValues(chart, options, ctx)
|
|
10062
10167
|
: drawLineOrBarOrRadarChartValues(chart, options, ctx);
|
|
10063
10168
|
break;
|
|
10169
|
+
case "funnel":
|
|
10170
|
+
drawHorizontalBarChartValues(chart, options, ctx);
|
|
10171
|
+
break;
|
|
10064
10172
|
}
|
|
10065
10173
|
ctx.restore();
|
|
10066
10174
|
},
|
|
@@ -10227,6 +10335,7 @@ function getNextNonEmptyBar(bars, startIndex) {
|
|
|
10227
10335
|
|
|
10228
10336
|
window.Chart?.register(waterfallLinesPlugin);
|
|
10229
10337
|
window.Chart?.register(chartShowValuesPlugin);
|
|
10338
|
+
window.Chart?.register(FunnelChartController, FunnelChartElement);
|
|
10230
10339
|
css /* scss */ `
|
|
10231
10340
|
.o-spreadsheet {
|
|
10232
10341
|
.o-chart-custom-tooltip {
|
|
@@ -22331,7 +22440,7 @@ autofillRulesRegistry
|
|
|
22331
22440
|
condition: (cell) => !cell.isFormula &&
|
|
22332
22441
|
evaluateLiteral(cell, { locale: DEFAULT_LOCALE }).type === CellValueType.text &&
|
|
22333
22442
|
alphaNumericValueRegExp.test(cell.content),
|
|
22334
|
-
generateRule: (cell, cells) => {
|
|
22443
|
+
generateRule: (cell, cells, direction) => {
|
|
22335
22444
|
const numberPostfix = parseInt(cell.content.match(numberPostfixRegExp)[0]);
|
|
22336
22445
|
const prefix = cell.content.match(stringPrefixRegExp)[0];
|
|
22337
22446
|
const numberPostfixLength = cell.content.length - prefix.length;
|
|
@@ -22339,7 +22448,10 @@ autofillRulesRegistry
|
|
|
22339
22448
|
alphaNumericValueRegExp.test(evaluatedCell.value)) // get consecutive alphanumeric cells, no matter what the prefix is
|
|
22340
22449
|
.filter((cell) => prefix === (cell.value ?? "").toString().match(stringPrefixRegExp)[0])
|
|
22341
22450
|
.map((cell) => parseInt((cell.value ?? "").toString().match(numberPostfixRegExp)[0]));
|
|
22342
|
-
|
|
22451
|
+
let increment = calculateIncrementBasedOnGroup(group);
|
|
22452
|
+
if (["up", "left"].includes(direction) && group.length === 1) {
|
|
22453
|
+
increment = -increment;
|
|
22454
|
+
}
|
|
22343
22455
|
return {
|
|
22344
22456
|
type: "ALPHANUMERIC_INCREMENT_MODIFIER",
|
|
22345
22457
|
prefix,
|
|
@@ -22402,10 +22514,13 @@ autofillRulesRegistry
|
|
|
22402
22514
|
.add("increment_number", {
|
|
22403
22515
|
condition: (cell) => !cell.isFormula &&
|
|
22404
22516
|
evaluateLiteral(cell, { locale: DEFAULT_LOCALE }).type === CellValueType.number,
|
|
22405
|
-
generateRule: (cell, cells) => {
|
|
22517
|
+
generateRule: (cell, cells, direction) => {
|
|
22406
22518
|
const group = getGroup(cell, cells, (evaluatedCell) => evaluatedCell.type === CellValueType.number &&
|
|
22407
22519
|
!isDateTimeFormat(evaluatedCell.format || "")).map((cell) => Number(cell.value));
|
|
22408
|
-
|
|
22520
|
+
let increment = calculateIncrementBasedOnGroup(group);
|
|
22521
|
+
if (["up", "left"].includes(direction) && group.length === 1) {
|
|
22522
|
+
increment = -increment;
|
|
22523
|
+
}
|
|
22409
22524
|
const evaluation = evaluateLiteral(cell, { locale: DEFAULT_LOCALE });
|
|
22410
22525
|
return {
|
|
22411
22526
|
type: "INCREMENT_MODIFIER",
|
|
@@ -22832,7 +22947,7 @@ const CHART_COMMON_OPTIONS = {
|
|
|
22832
22947
|
},
|
|
22833
22948
|
animation: false,
|
|
22834
22949
|
};
|
|
22835
|
-
function
|
|
22950
|
+
function chartToImageUrl(runtime, figure, type) {
|
|
22836
22951
|
// wrap the canvas in a div with a fixed size because chart.js would
|
|
22837
22952
|
// fill the whole page otherwise
|
|
22838
22953
|
const div = document.createElement("div");
|
|
@@ -22842,31 +22957,59 @@ function chartToImage(runtime, figure, type) {
|
|
|
22842
22957
|
div.append(canvas);
|
|
22843
22958
|
canvas.setAttribute("width", figure.width.toString());
|
|
22844
22959
|
canvas.setAttribute("height", figure.height.toString());
|
|
22960
|
+
let imageContent;
|
|
22845
22961
|
// we have to add the canvas to the DOM otherwise it won't be rendered
|
|
22846
22962
|
document.body.append(div);
|
|
22847
22963
|
if ("chartJsConfig" in runtime) {
|
|
22848
22964
|
const config = deepCopy(runtime.chartJsConfig);
|
|
22849
22965
|
config.plugins = [backgroundColorChartJSPlugin];
|
|
22850
22966
|
const chart = new window.Chart(canvas, config);
|
|
22851
|
-
|
|
22967
|
+
imageContent = chart.toBase64Image();
|
|
22852
22968
|
chart.destroy();
|
|
22853
|
-
div.remove();
|
|
22854
|
-
return imgContent;
|
|
22855
22969
|
}
|
|
22856
22970
|
else if (type === "scorecard") {
|
|
22857
22971
|
const design = getScorecardConfiguration(figure, runtime);
|
|
22858
22972
|
drawScoreChart(design, canvas);
|
|
22859
|
-
|
|
22860
|
-
div.remove();
|
|
22861
|
-
return imgContent;
|
|
22973
|
+
imageContent = canvas.toDataURL();
|
|
22862
22974
|
}
|
|
22863
22975
|
else if (type === "gauge") {
|
|
22864
22976
|
drawGaugeChart(canvas, runtime);
|
|
22865
|
-
|
|
22866
|
-
div.remove();
|
|
22867
|
-
return imgContent;
|
|
22977
|
+
imageContent = canvas.toDataURL();
|
|
22868
22978
|
}
|
|
22869
|
-
|
|
22979
|
+
div.remove();
|
|
22980
|
+
return imageContent;
|
|
22981
|
+
}
|
|
22982
|
+
async function chartToImageFile(runtime, figure, type) {
|
|
22983
|
+
// wrap the canvas in a div with a fixed size because chart.js would
|
|
22984
|
+
// fill the whole page otherwise
|
|
22985
|
+
const div = document.createElement("div");
|
|
22986
|
+
div.style.width = `${figure.width}px`;
|
|
22987
|
+
div.style.height = `${figure.height}px`;
|
|
22988
|
+
const canvas = document.createElement("canvas");
|
|
22989
|
+
div.append(canvas);
|
|
22990
|
+
canvas.setAttribute("width", figure.width.toString());
|
|
22991
|
+
canvas.setAttribute("height", figure.height.toString());
|
|
22992
|
+
// we have to add the canvas to the DOM otherwise it won't be rendered
|
|
22993
|
+
document.body.append(div);
|
|
22994
|
+
let chartBlob = null;
|
|
22995
|
+
if ("chartJsConfig" in runtime) {
|
|
22996
|
+
const config = deepCopy(runtime.chartJsConfig);
|
|
22997
|
+
config.plugins = [backgroundColorChartJSPlugin];
|
|
22998
|
+
const chart = new window.Chart(canvas, config);
|
|
22999
|
+
chartBlob = await new Promise((resolve) => canvas.toBlob(resolve, "image/png"));
|
|
23000
|
+
chart.destroy();
|
|
23001
|
+
}
|
|
23002
|
+
else if (type === "scorecard") {
|
|
23003
|
+
const design = getScorecardConfiguration(figure, runtime);
|
|
23004
|
+
drawScoreChart(design, canvas);
|
|
23005
|
+
chartBlob = await new Promise((resolve) => canvas.toBlob(resolve, "image/png"));
|
|
23006
|
+
}
|
|
23007
|
+
else if (type === "gauge") {
|
|
23008
|
+
drawGaugeChart(canvas, runtime);
|
|
23009
|
+
chartBlob = await new Promise((resolve) => canvas.toBlob(resolve, "image/png"));
|
|
23010
|
+
}
|
|
23011
|
+
div.remove();
|
|
23012
|
+
return chartBlob ? new File([chartBlob], "chart.png", { type: "image/png" }) : undefined;
|
|
22870
23013
|
}
|
|
22871
23014
|
/**
|
|
22872
23015
|
* Custom chart.js plugin to set the background color of the canvas
|
|
@@ -22949,6 +23092,7 @@ const CONTENT_TYPES = {
|
|
|
22949
23092
|
macroEnabledTemplateWorkbook: "application/vnd.ms-excel.template.macroEnabled.main+xml",
|
|
22950
23093
|
excelAddInWorkbook: "application/vnd.ms-excel.addin.macroEnabled.main+xml",
|
|
22951
23094
|
sheet: "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml",
|
|
23095
|
+
metadata: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheetMetadata+xml",
|
|
22952
23096
|
sharedStrings: "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml",
|
|
22953
23097
|
styles: "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml",
|
|
22954
23098
|
drawing: "application/vnd.openxmlformats-officedocument.drawing+xml",
|
|
@@ -22961,6 +23105,7 @@ const CONTENT_TYPES = {
|
|
|
22961
23105
|
const XLSX_RELATION_TYPE = {
|
|
22962
23106
|
document: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument",
|
|
22963
23107
|
sheet: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet",
|
|
23108
|
+
metadata: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sheetMetadata",
|
|
22964
23109
|
sharedStrings: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings",
|
|
22965
23110
|
styles: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles",
|
|
22966
23111
|
drawing: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing",
|
|
@@ -22970,6 +23115,7 @@ const XLSX_RELATION_TYPE = {
|
|
|
22970
23115
|
hyperlink: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink",
|
|
22971
23116
|
image: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
|
|
22972
23117
|
};
|
|
23118
|
+
const ARRAY_FORMULA_URI = "bdbb8cdc-fa1e-496e-a857-3c3f30c029c3";
|
|
22973
23119
|
const RELATIONSHIP_NSR = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
|
22974
23120
|
const HEIGHT_FACTOR = 0.75; // 100px => 75 u
|
|
22975
23121
|
/**
|
|
@@ -25391,29 +25537,34 @@ function convertPivotTableConfig(pivotTable) {
|
|
|
25391
25537
|
* In all the sheets, replace the table-only references in the formula cells with standard references.
|
|
25392
25538
|
*/
|
|
25393
25539
|
function convertTableFormulaReferences(convertedSheets, xlsxSheets) {
|
|
25394
|
-
for (let
|
|
25395
|
-
const tables = xlsxSheets.find((s) => s.sheetName ===
|
|
25540
|
+
for (let tableSheet of convertedSheets) {
|
|
25541
|
+
const tables = xlsxSheets.find((s) => s.sheetName === tableSheet.name).tables;
|
|
25396
25542
|
for (let table of tables) {
|
|
25397
25543
|
const tabRef = table.name + "[";
|
|
25398
|
-
for (let
|
|
25399
|
-
|
|
25400
|
-
|
|
25401
|
-
|
|
25402
|
-
|
|
25403
|
-
|
|
25404
|
-
|
|
25405
|
-
|
|
25406
|
-
|
|
25407
|
-
|
|
25408
|
-
|
|
25409
|
-
|
|
25544
|
+
for (let sheet of convertedSheets) {
|
|
25545
|
+
for (let xc in sheet.cells) {
|
|
25546
|
+
const cell = sheet.cells[xc];
|
|
25547
|
+
let cellContent = sheet.cells[xc];
|
|
25548
|
+
if (cell && cellContent && cellContent.startsWith("=")) {
|
|
25549
|
+
let refIndex;
|
|
25550
|
+
while ((refIndex = cellContent.indexOf(tabRef)) !== -1) {
|
|
25551
|
+
let endIndex = refIndex + tabRef.length;
|
|
25552
|
+
let openBrackets = 1;
|
|
25553
|
+
while (openBrackets > 0 && endIndex < cellContent.length) {
|
|
25554
|
+
if (cellContent[endIndex] === "[") {
|
|
25555
|
+
openBrackets++;
|
|
25556
|
+
}
|
|
25557
|
+
else if (cellContent[endIndex] === "]") {
|
|
25558
|
+
openBrackets--;
|
|
25559
|
+
}
|
|
25560
|
+
endIndex++;
|
|
25561
|
+
}
|
|
25562
|
+
let reference = cellContent.slice(refIndex + tabRef.length, endIndex - 1);
|
|
25563
|
+
const sheetPrefix = tableSheet.id === sheet.id ? "" : tableSheet.name + "!";
|
|
25564
|
+
const convertedRef = convertTableReference(sheetPrefix, reference, table, xc);
|
|
25565
|
+
cellContent =
|
|
25566
|
+
cellContent.slice(0, refIndex) + convertedRef + cellContent.slice(endIndex);
|
|
25410
25567
|
}
|
|
25411
|
-
reference = reference.slice(0, endIndex);
|
|
25412
|
-
const convertedRef = convertTableReference(reference, table, xc);
|
|
25413
|
-
cellContent =
|
|
25414
|
-
cellContent.slice(0, refIndex) +
|
|
25415
|
-
convertedRef +
|
|
25416
|
-
cellContent.slice(tabRef.length + refIndex + endIndex + 1);
|
|
25417
25568
|
}
|
|
25418
25569
|
sheet.cells[xc] = cellContent;
|
|
25419
25570
|
}
|
|
@@ -25422,11 +25573,17 @@ function convertTableFormulaReferences(convertedSheets, xlsxSheets) {
|
|
|
25422
25573
|
}
|
|
25423
25574
|
}
|
|
25424
25575
|
/**
|
|
25425
|
-
* Convert table-specific references in formulas into standard references.
|
|
25576
|
+
* Convert table-specific references in formulas into standard references. A table reference is composed of columns names,
|
|
25577
|
+
* and of keywords determining the rows of the table to reference.
|
|
25426
25578
|
*
|
|
25427
25579
|
* A reference in a table can have the form (only the part between brackets should be given to this function):
|
|
25428
25580
|
* - tableName[colName] : reference to the whole column "colName"
|
|
25581
|
+
* - tableName[#keyword] : reference to the whatever row the keyword refers to
|
|
25429
25582
|
* - tableName[[#keyword], [colName]] : reference to some of the element(s) of the column colName
|
|
25583
|
+
* - tableName[[#keyword], [colName]:[col2Name]] : reference to some of the element(s) of the columns colName to col2Name
|
|
25584
|
+
* - tableName[[#keyword1], [#keyword2], [colName]] : reference to all the rows referenced by the keywords in the column colName
|
|
25585
|
+
* - tableName[[#keyword1], [colName], [#keyword2]]: the keywords and colName can be in any order
|
|
25586
|
+
*
|
|
25430
25587
|
*
|
|
25431
25588
|
* The available keywords are :
|
|
25432
25589
|
* - #All : all the column (including totals)
|
|
@@ -25434,58 +25591,109 @@ function convertTableFormulaReferences(convertedSheets, xlsxSheets) {
|
|
|
25434
25591
|
* - #Headers : only the header of the column
|
|
25435
25592
|
* - #Totals : only the totals of the column
|
|
25436
25593
|
* - #This Row : only the element in the same row as the cell
|
|
25594
|
+
*
|
|
25595
|
+
* Note that the only valid combination of multiple keywords are #Data + #Totals and #Headers + #Data.
|
|
25437
25596
|
*/
|
|
25438
|
-
function convertTableReference(expr, table, cellXc) {
|
|
25439
|
-
|
|
25597
|
+
function convertTableReference(sheetPrefix, expr, table, cellXc) {
|
|
25598
|
+
// TODO: Ideally we'd want to make a real tokenizer, this simple approach won't work if for example the column name
|
|
25599
|
+
// contain # or , characters. But that's probably an edge case that we can ignore for now.
|
|
25600
|
+
const parts = expr.split(",").map((part) => part.trim());
|
|
25440
25601
|
const tableZone = toZone(table.ref);
|
|
25441
|
-
const
|
|
25442
|
-
|
|
25443
|
-
|
|
25444
|
-
|
|
25445
|
-
|
|
25446
|
-
|
|
25447
|
-
|
|
25448
|
-
|
|
25449
|
-
|
|
25450
|
-
|
|
25451
|
-
|
|
25602
|
+
const colIndexes = [];
|
|
25603
|
+
const rowIndexes = [];
|
|
25604
|
+
const foundKeywords = [];
|
|
25605
|
+
for (const part of parts) {
|
|
25606
|
+
if (removeBrackets(part).startsWith("#")) {
|
|
25607
|
+
const keyWord = removeBrackets(part);
|
|
25608
|
+
foundKeywords.push(keyWord);
|
|
25609
|
+
switch (keyWord) {
|
|
25610
|
+
case "#All":
|
|
25611
|
+
rowIndexes.push(tableZone.top, tableZone.bottom);
|
|
25612
|
+
break;
|
|
25613
|
+
case "#Data":
|
|
25614
|
+
const top = table.headerRowCount ? tableZone.top + table.headerRowCount : tableZone.top;
|
|
25615
|
+
const bottom = table.totalsRowCount
|
|
25616
|
+
? tableZone.bottom - table.totalsRowCount
|
|
25617
|
+
: tableZone.bottom;
|
|
25618
|
+
rowIndexes.push(top, bottom);
|
|
25619
|
+
break;
|
|
25620
|
+
case "#This Row":
|
|
25621
|
+
rowIndexes.push(toCartesian(cellXc).row);
|
|
25622
|
+
break;
|
|
25623
|
+
case "#Headers":
|
|
25624
|
+
if (!table.headerRowCount) {
|
|
25625
|
+
return CellErrorType.InvalidReference;
|
|
25626
|
+
}
|
|
25627
|
+
rowIndexes.push(tableZone.top);
|
|
25628
|
+
break;
|
|
25629
|
+
case "#Totals":
|
|
25630
|
+
if (!table.totalsRowCount) {
|
|
25631
|
+
return CellErrorType.InvalidReference;
|
|
25632
|
+
}
|
|
25633
|
+
rowIndexes.push(tableZone.bottom);
|
|
25634
|
+
break;
|
|
25635
|
+
}
|
|
25452
25636
|
}
|
|
25453
|
-
|
|
25454
|
-
|
|
25455
|
-
|
|
25456
|
-
|
|
25457
|
-
|
|
25458
|
-
|
|
25459
|
-
|
|
25460
|
-
|
|
25461
|
-
|
|
25462
|
-
|
|
25463
|
-
|
|
25464
|
-
|
|
25465
|
-
|
|
25466
|
-
|
|
25467
|
-
|
|
25468
|
-
|
|
25469
|
-
|
|
25470
|
-
if (!table.headerRowCount) {
|
|
25471
|
-
isReferencedZoneValid = false;
|
|
25472
|
-
}
|
|
25473
|
-
break;
|
|
25474
|
-
case "#Totals":
|
|
25475
|
-
refZone.top = refZone.bottom = tableZone.bottom;
|
|
25476
|
-
if (!table.totalsRowCount) {
|
|
25477
|
-
isReferencedZoneValid = false;
|
|
25637
|
+
else {
|
|
25638
|
+
const columns = part
|
|
25639
|
+
.split(":")
|
|
25640
|
+
.map((part) => part.trim())
|
|
25641
|
+
.map(removeBrackets);
|
|
25642
|
+
if (colIndexes.length) {
|
|
25643
|
+
return CellErrorType.InvalidReference;
|
|
25644
|
+
}
|
|
25645
|
+
const colRelativeIndex = table.cols.findIndex((col) => col.name === columns[0]);
|
|
25646
|
+
if (colRelativeIndex === -1) {
|
|
25647
|
+
return CellErrorType.InvalidReference;
|
|
25648
|
+
}
|
|
25649
|
+
colIndexes.push(colRelativeIndex + tableZone.left);
|
|
25650
|
+
if (columns[1]) {
|
|
25651
|
+
const colRelativeIndex2 = table.cols.findIndex((col) => col.name === columns[1]);
|
|
25652
|
+
if (colRelativeIndex2 === -1) {
|
|
25653
|
+
return CellErrorType.InvalidReference;
|
|
25478
25654
|
}
|
|
25479
|
-
|
|
25655
|
+
colIndexes.push(colRelativeIndex2 + tableZone.left);
|
|
25656
|
+
}
|
|
25480
25657
|
}
|
|
25481
|
-
const colRef = refElements[1].slice(1, refElements[1].length - 1);
|
|
25482
|
-
const colRelativeIndex = table.cols.findIndex((col) => col.name === colRef);
|
|
25483
|
-
refZone.left = refZone.right = colRelativeIndex + tableZone.left;
|
|
25484
25658
|
}
|
|
25485
|
-
if (!
|
|
25659
|
+
if (!areKeywordsCompatible(foundKeywords)) {
|
|
25486
25660
|
return CellErrorType.InvalidReference;
|
|
25487
25661
|
}
|
|
25488
|
-
|
|
25662
|
+
if (rowIndexes.length === 0) {
|
|
25663
|
+
const top = table.headerRowCount ? tableZone.top + table.headerRowCount : tableZone.top;
|
|
25664
|
+
const bottom = table.totalsRowCount
|
|
25665
|
+
? tableZone.bottom - table.totalsRowCount
|
|
25666
|
+
: tableZone.bottom;
|
|
25667
|
+
rowIndexes.push(top, bottom);
|
|
25668
|
+
}
|
|
25669
|
+
if (colIndexes.length === 0) {
|
|
25670
|
+
colIndexes.push(tableZone.left, tableZone.right);
|
|
25671
|
+
}
|
|
25672
|
+
const refZone = {
|
|
25673
|
+
top: Math.min(...rowIndexes),
|
|
25674
|
+
left: Math.min(...colIndexes),
|
|
25675
|
+
bottom: Math.max(...rowIndexes),
|
|
25676
|
+
right: Math.max(...colIndexes),
|
|
25677
|
+
};
|
|
25678
|
+
return sheetPrefix + zoneToXc(refZone);
|
|
25679
|
+
}
|
|
25680
|
+
function removeBrackets(str) {
|
|
25681
|
+
return str.startsWith("[") && str.endsWith("]") ? str.slice(1, str.length - 1) : str;
|
|
25682
|
+
}
|
|
25683
|
+
function areKeywordsCompatible(keywords) {
|
|
25684
|
+
if (keywords.length < 2) {
|
|
25685
|
+
return true;
|
|
25686
|
+
}
|
|
25687
|
+
else if (keywords.length > 2) {
|
|
25688
|
+
return false;
|
|
25689
|
+
}
|
|
25690
|
+
else if (keywords.includes("#Data") && keywords.includes("#Totals")) {
|
|
25691
|
+
return true;
|
|
25692
|
+
}
|
|
25693
|
+
else if (keywords.includes("#Headers") && keywords.includes("#Data")) {
|
|
25694
|
+
return true;
|
|
25695
|
+
}
|
|
25696
|
+
return false;
|
|
25489
25697
|
}
|
|
25490
25698
|
|
|
25491
25699
|
// -------------------------------------
|
|
@@ -27936,28 +28144,46 @@ function interactivePaste(env, target, pasteOption) {
|
|
|
27936
28144
|
const result = env.model.dispatch("PASTE", { target, pasteOption });
|
|
27937
28145
|
handlePasteResult(env, result);
|
|
27938
28146
|
}
|
|
27939
|
-
function interactivePasteFromOS(env, target,
|
|
28147
|
+
async function interactivePasteFromOS(env, target, parsedClipboardContent, pasteOption) {
|
|
27940
28148
|
let result;
|
|
27941
28149
|
// We do not trust the clipboard content to be accurate and comprehensive.
|
|
27942
28150
|
// Therefore, to ensure reliability, we handle unexpected errors that may
|
|
27943
28151
|
// arise from content that would not be suitable for the current version.
|
|
27944
28152
|
try {
|
|
28153
|
+
const clipboarContent = parsedClipboardContent;
|
|
28154
|
+
if (parsedClipboardContent.imageBlob) {
|
|
28155
|
+
try {
|
|
28156
|
+
const imageData = await env.imageProvider?.uploadFile(parsedClipboardContent.imageBlob);
|
|
28157
|
+
clipboarContent.imageData = imageData;
|
|
28158
|
+
}
|
|
28159
|
+
catch (e) {
|
|
28160
|
+
const msg = _t("An error occurred while uploading the image. %s", e.message);
|
|
28161
|
+
console.error(e);
|
|
28162
|
+
env.raiseError(msg);
|
|
28163
|
+
}
|
|
28164
|
+
delete parsedClipboardContent.imageBlob;
|
|
28165
|
+
}
|
|
27945
28166
|
result = env.model.dispatch("PASTE_FROM_OS_CLIPBOARD", {
|
|
27946
28167
|
target,
|
|
27947
|
-
clipboardContent,
|
|
28168
|
+
clipboardContent: parsedClipboardContent,
|
|
27948
28169
|
pasteOption,
|
|
27949
28170
|
});
|
|
27950
28171
|
}
|
|
27951
28172
|
catch (error) {
|
|
27952
|
-
const parsedSpreadsheetContent =
|
|
28173
|
+
const parsedSpreadsheetContent = parsedClipboardContent.data;
|
|
27953
28174
|
if (parsedSpreadsheetContent?.version !== CURRENT_VERSION) {
|
|
27954
28175
|
env.raiseError(_t("An unexpected error occurred while pasting content.\
|
|
27955
28176
|
This is probably due to a spreadsheet version mismatch."));
|
|
27956
28177
|
}
|
|
28178
|
+
else {
|
|
28179
|
+
env.raiseError(_t("An unexpected error occurred while pasting content.\
|
|
28180
|
+
Additional information can be found in the browser console."));
|
|
28181
|
+
console.error(error);
|
|
28182
|
+
}
|
|
27957
28183
|
result = env.model.dispatch("PASTE_FROM_OS_CLIPBOARD", {
|
|
27958
28184
|
target,
|
|
27959
28185
|
clipboardContent: {
|
|
27960
|
-
text:
|
|
28186
|
+
text: parsedClipboardContent.text,
|
|
27961
28187
|
},
|
|
27962
28188
|
pasteOption,
|
|
27963
28189
|
});
|
|
@@ -28656,8 +28882,8 @@ function isLuxonTimeAdapterInstalled() {
|
|
|
28656
28882
|
if (!window.Chart) {
|
|
28657
28883
|
return false;
|
|
28658
28884
|
}
|
|
28659
|
-
// @ts-ignore
|
|
28660
28885
|
const adapter = new window.Chart._adapters._date({});
|
|
28886
|
+
// @ts-ignore
|
|
28661
28887
|
const isInstalled = adapter._id === "luxon";
|
|
28662
28888
|
if (!isInstalled && !missingTimeAdapterAlreadyWarned) {
|
|
28663
28889
|
missingTimeAdapterAlreadyWarned = true;
|
|
@@ -29086,6 +29312,31 @@ function getGeoChartDatasets(definition, args) {
|
|
|
29086
29312
|
}
|
|
29087
29313
|
return [dataset];
|
|
29088
29314
|
}
|
|
29315
|
+
function getFunnelChartDatasets(definition, args) {
|
|
29316
|
+
const dataSetsValues = args.dataSetsValues[0];
|
|
29317
|
+
const labels = args.labels;
|
|
29318
|
+
if (!dataSetsValues) {
|
|
29319
|
+
return [];
|
|
29320
|
+
}
|
|
29321
|
+
let { label: datasetLabel, data } = dataSetsValues;
|
|
29322
|
+
datasetLabel = definition.dataSets?.[0].label || datasetLabel;
|
|
29323
|
+
const dataset = {
|
|
29324
|
+
label: datasetLabel,
|
|
29325
|
+
data: data.map((value) => (value <= 0 ? [0, 0] : [-value, value])),
|
|
29326
|
+
backgroundColor: getFunnelLabelColors(labels, definition.funnelColors),
|
|
29327
|
+
yAxisID: "y",
|
|
29328
|
+
xAxisID: "x",
|
|
29329
|
+
barPercentage: 1,
|
|
29330
|
+
categoryPercentage: 1,
|
|
29331
|
+
borderColor: definition.background || BACKGROUND_CHART_COLOR,
|
|
29332
|
+
borderWidth: 3,
|
|
29333
|
+
};
|
|
29334
|
+
return [dataset];
|
|
29335
|
+
}
|
|
29336
|
+
function getFunnelLabelColors(labels, colors) {
|
|
29337
|
+
const colorGenerator = new ColorGenerator(labels.length, colors);
|
|
29338
|
+
return labels.map(() => colorGenerator.next());
|
|
29339
|
+
}
|
|
29089
29340
|
function getTrendingLineDataSet(dataset, config, data) {
|
|
29090
29341
|
const defaultBorderColor = colorToRGBA(dataset.backgroundColor);
|
|
29091
29342
|
defaultBorderColor.a = 1;
|
|
@@ -29519,6 +29770,38 @@ function getGeoChartScales(definition, args) {
|
|
|
29519
29770
|
},
|
|
29520
29771
|
};
|
|
29521
29772
|
}
|
|
29773
|
+
function getFunnelChartScales(definition, args) {
|
|
29774
|
+
const dataSet = args.dataSetsValues[0];
|
|
29775
|
+
return {
|
|
29776
|
+
x: {
|
|
29777
|
+
display: false,
|
|
29778
|
+
},
|
|
29779
|
+
y: {
|
|
29780
|
+
grid: { offset: false }, // bar charts grid is offset by default
|
|
29781
|
+
ticks: {
|
|
29782
|
+
callback: function (tickValue) {
|
|
29783
|
+
return truncateLabel(this.getLabelForValue(tickValue));
|
|
29784
|
+
},
|
|
29785
|
+
},
|
|
29786
|
+
border: { display: false },
|
|
29787
|
+
},
|
|
29788
|
+
percentages: {
|
|
29789
|
+
position: "right",
|
|
29790
|
+
border: { display: false },
|
|
29791
|
+
ticks: {
|
|
29792
|
+
callback: function (tickValue, index, ticks) {
|
|
29793
|
+
const value = dataSet.data?.[index];
|
|
29794
|
+
const baseValue = dataSet.data?.[0];
|
|
29795
|
+
if (!baseValue || value === undefined) {
|
|
29796
|
+
return "";
|
|
29797
|
+
}
|
|
29798
|
+
return formatValue(value / baseValue, { format: "0%", locale: args.locale });
|
|
29799
|
+
},
|
|
29800
|
+
},
|
|
29801
|
+
grid: { display: false },
|
|
29802
|
+
},
|
|
29803
|
+
};
|
|
29804
|
+
}
|
|
29522
29805
|
function getGeoChartProjection(projection) {
|
|
29523
29806
|
if (projection === "conicConformal") {
|
|
29524
29807
|
return window.ChartGeo.geoConicConformal().rotate([100, 0]); // Centered on the US
|
|
@@ -29882,6 +30165,23 @@ function getGeoChartTooltip(definition, args) {
|
|
|
29882
30165
|
},
|
|
29883
30166
|
};
|
|
29884
30167
|
}
|
|
30168
|
+
function getFunnelChartTooltip(definition, args) {
|
|
30169
|
+
return {
|
|
30170
|
+
enabled: false,
|
|
30171
|
+
external: customTooltipHandler,
|
|
30172
|
+
position: "funnelTooltipPositioner",
|
|
30173
|
+
callbacks: {
|
|
30174
|
+
title: () => "",
|
|
30175
|
+
beforeLabel: (tooltipItem) => tooltipItem.label,
|
|
30176
|
+
label: function (tooltipItem) {
|
|
30177
|
+
const yLabel = tooltipItem.parsed.x;
|
|
30178
|
+
const axisId = tooltipItem.dataset.xAxisID;
|
|
30179
|
+
const yLabelStr = formatChartDatasetValue(args.axisFormats, args.locale)(yLabel, axisId);
|
|
30180
|
+
return yLabelStr;
|
|
30181
|
+
},
|
|
30182
|
+
},
|
|
30183
|
+
};
|
|
30184
|
+
}
|
|
29885
30185
|
function calculatePercentage(dataset, dataIndex) {
|
|
29886
30186
|
const numericData = dataset.filter((value) => typeof value === "number");
|
|
29887
30187
|
const total = numericData.reduce((sum, value) => sum + value, 0);
|
|
@@ -29952,6 +30252,10 @@ var CHART_RUNTIME_HELPERS = /*#__PURE__*/Object.freeze({
|
|
|
29952
30252
|
getComboChartDatasets: getComboChartDatasets,
|
|
29953
30253
|
getComboChartLegend: getComboChartLegend,
|
|
29954
30254
|
getData: getData,
|
|
30255
|
+
getFunnelChartDatasets: getFunnelChartDatasets,
|
|
30256
|
+
getFunnelChartScales: getFunnelChartScales,
|
|
30257
|
+
getFunnelChartTooltip: getFunnelChartTooltip,
|
|
30258
|
+
getFunnelLabelColors: getFunnelLabelColors,
|
|
29955
30259
|
getGeoChartData: getGeoChartData,
|
|
29956
30260
|
getGeoChartDatasets: getGeoChartDatasets,
|
|
29957
30261
|
getGeoChartScales: getGeoChartScales,
|
|
@@ -30290,6 +30594,146 @@ function createComboChartRuntime(chart, getters) {
|
|
|
30290
30594
|
return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
|
|
30291
30595
|
}
|
|
30292
30596
|
|
|
30597
|
+
class FunnelChart extends AbstractChart {
|
|
30598
|
+
dataSets;
|
|
30599
|
+
labelRange;
|
|
30600
|
+
background;
|
|
30601
|
+
legendPosition;
|
|
30602
|
+
aggregated;
|
|
30603
|
+
type = "funnel";
|
|
30604
|
+
dataSetsHaveTitle;
|
|
30605
|
+
dataSetDesign;
|
|
30606
|
+
axesDesign;
|
|
30607
|
+
horizontal = true;
|
|
30608
|
+
showValues;
|
|
30609
|
+
funnelColors;
|
|
30610
|
+
constructor(definition, sheetId, getters) {
|
|
30611
|
+
super(definition, sheetId, getters);
|
|
30612
|
+
this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
|
|
30613
|
+
this.labelRange = createValidRange(getters, sheetId, definition.labelRange);
|
|
30614
|
+
this.background = definition.background;
|
|
30615
|
+
this.legendPosition = definition.legendPosition;
|
|
30616
|
+
this.aggregated = definition.aggregated;
|
|
30617
|
+
this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
|
|
30618
|
+
this.dataSetDesign = definition.dataSets;
|
|
30619
|
+
this.axesDesign = definition.axesDesign;
|
|
30620
|
+
this.showValues = definition.showValues;
|
|
30621
|
+
this.horizontal = true;
|
|
30622
|
+
this.funnelColors = definition.funnelColors;
|
|
30623
|
+
}
|
|
30624
|
+
static transformDefinition(definition, executed) {
|
|
30625
|
+
return transformChartDefinitionWithDataSetsWithZone(definition, executed);
|
|
30626
|
+
}
|
|
30627
|
+
static validateChartDefinition(validator, definition) {
|
|
30628
|
+
return validator.checkValidations(definition, checkDataset, checkLabelRange);
|
|
30629
|
+
}
|
|
30630
|
+
static getDefinitionFromContextCreation(context) {
|
|
30631
|
+
return {
|
|
30632
|
+
background: context.background,
|
|
30633
|
+
dataSets: context.range ?? [],
|
|
30634
|
+
dataSetsHaveTitle: context.dataSetsHaveTitle ?? false,
|
|
30635
|
+
aggregated: context.aggregated ?? false,
|
|
30636
|
+
legendPosition: "none",
|
|
30637
|
+
title: context.title || { text: "" },
|
|
30638
|
+
type: "funnel",
|
|
30639
|
+
labelRange: context.auxiliaryRange || undefined,
|
|
30640
|
+
showValues: context.showValues,
|
|
30641
|
+
axesDesign: context.axesDesign,
|
|
30642
|
+
funnelColors: context.funnelColors,
|
|
30643
|
+
horizontal: true,
|
|
30644
|
+
};
|
|
30645
|
+
}
|
|
30646
|
+
getContextCreation() {
|
|
30647
|
+
const range = [];
|
|
30648
|
+
for (const [i, dataSet] of this.dataSets.entries()) {
|
|
30649
|
+
range.push({
|
|
30650
|
+
...this.dataSetDesign?.[i],
|
|
30651
|
+
dataRange: this.getters.getRangeString(dataSet.dataRange, this.sheetId),
|
|
30652
|
+
});
|
|
30653
|
+
}
|
|
30654
|
+
return {
|
|
30655
|
+
...this,
|
|
30656
|
+
range,
|
|
30657
|
+
auxiliaryRange: this.labelRange
|
|
30658
|
+
? this.getters.getRangeString(this.labelRange, this.sheetId)
|
|
30659
|
+
: undefined,
|
|
30660
|
+
};
|
|
30661
|
+
}
|
|
30662
|
+
duplicateInDuplicatedSheet(newSheetId) {
|
|
30663
|
+
const dataSets = duplicateDataSetsInDuplicatedSheet(this.sheetId, newSheetId, this.dataSets);
|
|
30664
|
+
const labelRange = duplicateLabelRangeInDuplicatedSheet(this.sheetId, newSheetId, this.labelRange);
|
|
30665
|
+
const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange, newSheetId);
|
|
30666
|
+
return new FunnelChart(definition, newSheetId, this.getters);
|
|
30667
|
+
}
|
|
30668
|
+
copyInSheetId(sheetId) {
|
|
30669
|
+
const definition = this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange, sheetId);
|
|
30670
|
+
return new FunnelChart(definition, sheetId, this.getters);
|
|
30671
|
+
}
|
|
30672
|
+
getDefinition() {
|
|
30673
|
+
return this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange);
|
|
30674
|
+
}
|
|
30675
|
+
getDefinitionWithSpecificDataSets(dataSets, labelRange, targetSheetId) {
|
|
30676
|
+
const ranges = [];
|
|
30677
|
+
for (const [i, dataSet] of dataSets.entries()) {
|
|
30678
|
+
ranges.push({
|
|
30679
|
+
...this.dataSetDesign?.[i],
|
|
30680
|
+
dataRange: this.getters.getRangeString(dataSet.dataRange, targetSheetId || this.sheetId),
|
|
30681
|
+
});
|
|
30682
|
+
}
|
|
30683
|
+
return {
|
|
30684
|
+
type: "funnel",
|
|
30685
|
+
dataSetsHaveTitle: dataSets.length ? Boolean(dataSets[0].labelCell) : false,
|
|
30686
|
+
background: this.background,
|
|
30687
|
+
dataSets: ranges,
|
|
30688
|
+
legendPosition: this.legendPosition,
|
|
30689
|
+
labelRange: labelRange
|
|
30690
|
+
? this.getters.getRangeString(labelRange, targetSheetId || this.sheetId)
|
|
30691
|
+
: undefined,
|
|
30692
|
+
title: this.title,
|
|
30693
|
+
aggregated: this.aggregated,
|
|
30694
|
+
horizontal: this.horizontal,
|
|
30695
|
+
axesDesign: this.axesDesign,
|
|
30696
|
+
showValues: this.showValues,
|
|
30697
|
+
funnelColors: this.funnelColors,
|
|
30698
|
+
};
|
|
30699
|
+
}
|
|
30700
|
+
getDefinitionForExcel() {
|
|
30701
|
+
return undefined;
|
|
30702
|
+
}
|
|
30703
|
+
updateRanges(applyChange) {
|
|
30704
|
+
const { dataSets, labelRange, isStale } = updateChartRangesWithDataSets(this.getters, applyChange, this.dataSets, this.labelRange);
|
|
30705
|
+
if (!isStale) {
|
|
30706
|
+
return this;
|
|
30707
|
+
}
|
|
30708
|
+
const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange);
|
|
30709
|
+
return new FunnelChart(definition, this.sheetId, this.getters);
|
|
30710
|
+
}
|
|
30711
|
+
}
|
|
30712
|
+
function createFunnelChartRuntime(chart, getters) {
|
|
30713
|
+
const definition = chart.getDefinition();
|
|
30714
|
+
const chartData = getBarChartData(definition, chart.dataSets, chart.labelRange, getters);
|
|
30715
|
+
const config = {
|
|
30716
|
+
type: "funnel",
|
|
30717
|
+
data: {
|
|
30718
|
+
labels: chartData.labels,
|
|
30719
|
+
datasets: getFunnelChartDatasets(definition, chartData),
|
|
30720
|
+
},
|
|
30721
|
+
options: {
|
|
30722
|
+
...CHART_COMMON_OPTIONS,
|
|
30723
|
+
indexAxis: "y",
|
|
30724
|
+
layout: getChartLayout(),
|
|
30725
|
+
scales: getFunnelChartScales(definition, chartData),
|
|
30726
|
+
plugins: {
|
|
30727
|
+
title: getChartTitle(definition),
|
|
30728
|
+
legend: { display: false },
|
|
30729
|
+
tooltip: getFunnelChartTooltip(definition, chartData),
|
|
30730
|
+
chartShowValuesPlugin: getChartShowValues(definition, chartData),
|
|
30731
|
+
},
|
|
30732
|
+
},
|
|
30733
|
+
};
|
|
30734
|
+
return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
|
|
30735
|
+
}
|
|
30736
|
+
|
|
30293
30737
|
function isDataRangeValid(definition) {
|
|
30294
30738
|
return definition.dataRange && !rangeReference.test(definition.dataRange)
|
|
30295
30739
|
? "InvalidGaugeDataRange" /* CommandResult.InvalidGaugeDataRange */
|
|
@@ -31696,6 +32140,15 @@ chartRegistry.add("geo", {
|
|
|
31696
32140
|
getChartDefinitionFromContextCreation: GeoChart.getDefinitionFromContextCreation,
|
|
31697
32141
|
sequence: 90,
|
|
31698
32142
|
});
|
|
32143
|
+
chartRegistry.add("funnel", {
|
|
32144
|
+
match: (type) => type === "funnel",
|
|
32145
|
+
createChart: (definition, sheetId, getters) => new FunnelChart(definition, sheetId, getters),
|
|
32146
|
+
getChartRuntime: createFunnelChartRuntime,
|
|
32147
|
+
validateChartDefinition: FunnelChart.validateChartDefinition,
|
|
32148
|
+
transformDefinition: FunnelChart.transformDefinition,
|
|
32149
|
+
getChartDefinitionFromContextCreation: FunnelChart.getDefinitionFromContextCreation,
|
|
32150
|
+
sequence: 100,
|
|
32151
|
+
});
|
|
31699
32152
|
const chartComponentRegistry = new Registry();
|
|
31700
32153
|
chartComponentRegistry.add("line", ChartJsComponent);
|
|
31701
32154
|
chartComponentRegistry.add("bar", ChartJsComponent);
|
|
@@ -31708,6 +32161,7 @@ chartComponentRegistry.add("waterfall", ChartJsComponent);
|
|
|
31708
32161
|
chartComponentRegistry.add("pyramid", ChartJsComponent);
|
|
31709
32162
|
chartComponentRegistry.add("radar", ChartJsComponent);
|
|
31710
32163
|
chartComponentRegistry.add("geo", ChartJsComponent);
|
|
32164
|
+
chartComponentRegistry.add("funnel", ChartJsComponent);
|
|
31711
32165
|
const chartCategories = {
|
|
31712
32166
|
line: _t("Line"),
|
|
31713
32167
|
column: _t("Column"),
|
|
@@ -31874,6 +32328,13 @@ chartSubtypeRegistry
|
|
|
31874
32328
|
chartType: "geo",
|
|
31875
32329
|
category: "misc",
|
|
31876
32330
|
preview: "o-spreadsheet-ChartPreview.GEO_CHART",
|
|
32331
|
+
})
|
|
32332
|
+
.add("funnel", {
|
|
32333
|
+
displayName: _t("Funnel"),
|
|
32334
|
+
chartSubtype: "funnel",
|
|
32335
|
+
chartType: "funnel",
|
|
32336
|
+
category: "misc",
|
|
32337
|
+
preview: "o-spreadsheet-ChartPreview.FUNNEL_CHART",
|
|
31877
32338
|
});
|
|
31878
32339
|
|
|
31879
32340
|
/**
|
|
@@ -31934,6 +32395,391 @@ class ImageFigure extends owl.Component {
|
|
|
31934
32395
|
}
|
|
31935
32396
|
}
|
|
31936
32397
|
|
|
32398
|
+
const macRegex = /Mac/i;
|
|
32399
|
+
const MODIFIER_KEYS = ["Shift", "Control", "Alt", "Meta"];
|
|
32400
|
+
/**
|
|
32401
|
+
* Return true if the event was triggered from
|
|
32402
|
+
* a child element.
|
|
32403
|
+
*/
|
|
32404
|
+
function isChildEvent(parent, ev) {
|
|
32405
|
+
if (!parent)
|
|
32406
|
+
return false;
|
|
32407
|
+
return !!ev.target && parent.contains(ev.target);
|
|
32408
|
+
}
|
|
32409
|
+
function gridOverlayPosition() {
|
|
32410
|
+
const spreadsheetElement = document.querySelector(".o-grid-overlay");
|
|
32411
|
+
if (spreadsheetElement) {
|
|
32412
|
+
const { top, left } = spreadsheetElement?.getBoundingClientRect();
|
|
32413
|
+
return { top, left };
|
|
32414
|
+
}
|
|
32415
|
+
throw new Error("Can't find spreadsheet position");
|
|
32416
|
+
}
|
|
32417
|
+
function getBoundingRectAsPOJO(el) {
|
|
32418
|
+
const rect = el.getBoundingClientRect();
|
|
32419
|
+
return {
|
|
32420
|
+
x: rect.x,
|
|
32421
|
+
y: rect.y,
|
|
32422
|
+
width: rect.width,
|
|
32423
|
+
height: rect.height,
|
|
32424
|
+
};
|
|
32425
|
+
}
|
|
32426
|
+
/**
|
|
32427
|
+
* Iterate over all the children of `el` in the dom tree starting at `el`, depth first.
|
|
32428
|
+
*/
|
|
32429
|
+
function* iterateChildren(el) {
|
|
32430
|
+
yield el;
|
|
32431
|
+
if (el.hasChildNodes()) {
|
|
32432
|
+
for (let child of el.childNodes) {
|
|
32433
|
+
yield* iterateChildren(child);
|
|
32434
|
+
}
|
|
32435
|
+
}
|
|
32436
|
+
}
|
|
32437
|
+
function getOpenedMenus() {
|
|
32438
|
+
return Array.from(document.querySelectorAll(".o-spreadsheet .o-menu"));
|
|
32439
|
+
}
|
|
32440
|
+
function getCurrentSelection(el) {
|
|
32441
|
+
let { startElement, endElement, startSelectionOffset, endSelectionOffset } = getStartAndEndSelection(el);
|
|
32442
|
+
let startSizeBefore = findSelectionIndex(el, startElement, startSelectionOffset);
|
|
32443
|
+
let endSizeBefore = findSelectionIndex(el, endElement, endSelectionOffset);
|
|
32444
|
+
return {
|
|
32445
|
+
start: startSizeBefore,
|
|
32446
|
+
end: endSizeBefore,
|
|
32447
|
+
};
|
|
32448
|
+
}
|
|
32449
|
+
function getStartAndEndSelection(el) {
|
|
32450
|
+
const selection = document.getSelection();
|
|
32451
|
+
return {
|
|
32452
|
+
startElement: selection.anchorNode || el,
|
|
32453
|
+
startSelectionOffset: selection.anchorOffset,
|
|
32454
|
+
endElement: selection.focusNode || el,
|
|
32455
|
+
endSelectionOffset: selection.focusOffset,
|
|
32456
|
+
};
|
|
32457
|
+
}
|
|
32458
|
+
/**
|
|
32459
|
+
* Computes the text 'index' inside this.el based on the currently selected node and its offset.
|
|
32460
|
+
* The selected node is either a Text node or an Element node.
|
|
32461
|
+
*
|
|
32462
|
+
* case 1 -Text node:
|
|
32463
|
+
* the offset is the number of characters from the start of the node. We have to add this offset to the
|
|
32464
|
+
* content length of all previous nodes.
|
|
32465
|
+
*
|
|
32466
|
+
* case 2 - Element node:
|
|
32467
|
+
* the offset is the number of child nodes before the selected node. We have to add the content length of
|
|
32468
|
+
* all the nodes prior to the selected node as well as the content of the child node before the offset.
|
|
32469
|
+
*
|
|
32470
|
+
* See the MDN documentation for more details.
|
|
32471
|
+
* https://developer.mozilla.org/en-US/docs/Web/API/Range/startOffset
|
|
32472
|
+
* https://developer.mozilla.org/en-US/docs/Web/API/Range/endOffset
|
|
32473
|
+
*
|
|
32474
|
+
*/
|
|
32475
|
+
function findSelectionIndex(el, nodeToFind, nodeOffset) {
|
|
32476
|
+
let usedCharacters = 0;
|
|
32477
|
+
let it = iterateChildren(el);
|
|
32478
|
+
let current = it.next();
|
|
32479
|
+
let isFirstParagraph = true;
|
|
32480
|
+
while (!current.done && current.value !== nodeToFind) {
|
|
32481
|
+
if (!current.value.hasChildNodes()) {
|
|
32482
|
+
if (current.value.textContent) {
|
|
32483
|
+
usedCharacters += current.value.textContent.length;
|
|
32484
|
+
}
|
|
32485
|
+
}
|
|
32486
|
+
// One new paragraph = one new line character, except for the first paragraph
|
|
32487
|
+
if (current.value.nodeName === "P" ||
|
|
32488
|
+
(current.value.nodeName === "DIV" && current.value !== el) // On paste, the HTML may contain <div> instead of <p>
|
|
32489
|
+
) {
|
|
32490
|
+
if (isFirstParagraph) {
|
|
32491
|
+
isFirstParagraph = false;
|
|
32492
|
+
}
|
|
32493
|
+
else {
|
|
32494
|
+
usedCharacters++;
|
|
32495
|
+
}
|
|
32496
|
+
}
|
|
32497
|
+
current = it.next();
|
|
32498
|
+
}
|
|
32499
|
+
if (current.value !== nodeToFind) {
|
|
32500
|
+
/** This situation can happen if the code is called while the selection is not currently on the element.
|
|
32501
|
+
* In this case, we return 0 because we don't know the size of the text before the selection.
|
|
32502
|
+
*
|
|
32503
|
+
* A known occurrence is triggered since the introduction of commit d4663158 (PR #2038).
|
|
32504
|
+
*/
|
|
32505
|
+
return 0;
|
|
32506
|
+
}
|
|
32507
|
+
else {
|
|
32508
|
+
if (!current.value.hasChildNodes()) {
|
|
32509
|
+
usedCharacters += nodeOffset;
|
|
32510
|
+
}
|
|
32511
|
+
else {
|
|
32512
|
+
const children = [...current.value.childNodes].slice(0, nodeOffset);
|
|
32513
|
+
usedCharacters += children.reduce((acc, child, index) => {
|
|
32514
|
+
if (child.textContent !== null) {
|
|
32515
|
+
// need to account for paragraph nodes that implicitly add a new line
|
|
32516
|
+
// except for the last paragraph
|
|
32517
|
+
let chars = child.textContent.length;
|
|
32518
|
+
if (child.nodeName === "P" && index !== children.length - 1) {
|
|
32519
|
+
chars++;
|
|
32520
|
+
}
|
|
32521
|
+
return acc + chars;
|
|
32522
|
+
}
|
|
32523
|
+
else {
|
|
32524
|
+
return acc;
|
|
32525
|
+
}
|
|
32526
|
+
}, 0);
|
|
32527
|
+
}
|
|
32528
|
+
}
|
|
32529
|
+
if (nodeToFind.nodeName === "P" && !isFirstParagraph && nodeToFind.textContent === "") {
|
|
32530
|
+
usedCharacters++;
|
|
32531
|
+
}
|
|
32532
|
+
return usedCharacters;
|
|
32533
|
+
}
|
|
32534
|
+
const letterRegex = /^[a-zA-Z]$/;
|
|
32535
|
+
/**
|
|
32536
|
+
* Transform a keyboard event into a shortcut string that represent this event. The letters keys will be uppercased.
|
|
32537
|
+
*
|
|
32538
|
+
* @argument ev - The keyboard event to transform
|
|
32539
|
+
* @argument mode - Use either ev.key of ev.code to get the string shortcut
|
|
32540
|
+
*
|
|
32541
|
+
* @example
|
|
32542
|
+
* event : { ctrlKey: true, key: "a" } => "Ctrl+A"
|
|
32543
|
+
* event : { shift: true, alt: true, key: "Home" } => "Alt+Shift+Home"
|
|
32544
|
+
*/
|
|
32545
|
+
function keyboardEventToShortcutString(ev, mode = "key") {
|
|
32546
|
+
let keyDownString = "";
|
|
32547
|
+
if (!MODIFIER_KEYS.includes(ev.key)) {
|
|
32548
|
+
if (isCtrlKey(ev))
|
|
32549
|
+
keyDownString += "Ctrl+";
|
|
32550
|
+
if (ev.altKey)
|
|
32551
|
+
keyDownString += "Alt+";
|
|
32552
|
+
if (ev.shiftKey)
|
|
32553
|
+
keyDownString += "Shift+";
|
|
32554
|
+
}
|
|
32555
|
+
const key = mode === "key" ? ev.key : ev.code;
|
|
32556
|
+
keyDownString += letterRegex.test(key) ? key.toUpperCase() : key;
|
|
32557
|
+
return keyDownString;
|
|
32558
|
+
}
|
|
32559
|
+
function isMacOS() {
|
|
32560
|
+
return Boolean(macRegex.test(navigator.userAgent));
|
|
32561
|
+
}
|
|
32562
|
+
/**
|
|
32563
|
+
* @param {KeyboardEvent | MouseEvent} ev
|
|
32564
|
+
* @returns Returns true if the event was triggered with the "ctrl" modifier pressed.
|
|
32565
|
+
* On Mac, this is the "meta" or "command" key.
|
|
32566
|
+
*/
|
|
32567
|
+
function isCtrlKey(ev) {
|
|
32568
|
+
return isMacOS() ? ev.metaKey : ev.ctrlKey;
|
|
32569
|
+
}
|
|
32570
|
+
/**
|
|
32571
|
+
* @param {MouseEvent} ev - The mouse event.
|
|
32572
|
+
* @returns {boolean} Returns true if the event was triggered by a middle-click
|
|
32573
|
+
* or a Ctrl + Click (Cmd + Click on Mac).
|
|
32574
|
+
*/
|
|
32575
|
+
function isMiddleClickOrCtrlClick(ev) {
|
|
32576
|
+
return ev.button === 1 || (isCtrlKey(ev) && ev.button === 0);
|
|
32577
|
+
}
|
|
32578
|
+
async function convertImageToPng(imageUrl) {
|
|
32579
|
+
return new Promise((resolve, reject) => {
|
|
32580
|
+
const image = new Image();
|
|
32581
|
+
image.addEventListener("load", () => {
|
|
32582
|
+
const canvas = document.createElement("canvas");
|
|
32583
|
+
canvas.width = image.width;
|
|
32584
|
+
canvas.height = image.height;
|
|
32585
|
+
const ctx = canvas.getContext("2d");
|
|
32586
|
+
ctx?.drawImage(image, 0, 0);
|
|
32587
|
+
canvas.toBlob(resolve, "image/png");
|
|
32588
|
+
});
|
|
32589
|
+
image.addEventListener("error", reject);
|
|
32590
|
+
image.src = imageUrl;
|
|
32591
|
+
});
|
|
32592
|
+
}
|
|
32593
|
+
function downloadFile(dataUrl, fileName) {
|
|
32594
|
+
const a = document.createElement("a");
|
|
32595
|
+
a.href = dataUrl;
|
|
32596
|
+
a.download = fileName;
|
|
32597
|
+
document.body.appendChild(a);
|
|
32598
|
+
a.click();
|
|
32599
|
+
document.body.removeChild(a);
|
|
32600
|
+
}
|
|
32601
|
+
|
|
32602
|
+
/**
|
|
32603
|
+
* Create a function used to create a Chart based on the definition
|
|
32604
|
+
*/
|
|
32605
|
+
function chartFactory(getters) {
|
|
32606
|
+
const builders = chartRegistry.getAll().sort((a, b) => a.sequence - b.sequence);
|
|
32607
|
+
function createChart(id, definition, sheetId) {
|
|
32608
|
+
const builder = builders.find((builder) => builder.match(definition.type));
|
|
32609
|
+
if (!builder) {
|
|
32610
|
+
throw new Error(`No builder for this chart: ${definition.type}`);
|
|
32611
|
+
}
|
|
32612
|
+
return builder.createChart(definition, sheetId, getters);
|
|
32613
|
+
}
|
|
32614
|
+
return createChart;
|
|
32615
|
+
}
|
|
32616
|
+
/**
|
|
32617
|
+
* Create a function used to create a Chart Runtime based on the chart class
|
|
32618
|
+
* instance
|
|
32619
|
+
*/
|
|
32620
|
+
function chartRuntimeFactory(getters) {
|
|
32621
|
+
const builders = chartRegistry.getAll().sort((a, b) => a.sequence - b.sequence);
|
|
32622
|
+
function createRuntimeChart(chart) {
|
|
32623
|
+
const builder = builders.find((builder) => builder.match(chart.type));
|
|
32624
|
+
if (!builder) {
|
|
32625
|
+
throw new Error("No runtime builder for this chart.");
|
|
32626
|
+
}
|
|
32627
|
+
return builder.getChartRuntime(chart, getters);
|
|
32628
|
+
}
|
|
32629
|
+
return createRuntimeChart;
|
|
32630
|
+
}
|
|
32631
|
+
/**
|
|
32632
|
+
* Validate the chart definition given in arguments
|
|
32633
|
+
*/
|
|
32634
|
+
function validateChartDefinition(validator, definition) {
|
|
32635
|
+
const validators = chartRegistry.getAll().find((validator) => validator.match(definition.type));
|
|
32636
|
+
if (!validators) {
|
|
32637
|
+
throw new Error("Unknown chart type.");
|
|
32638
|
+
}
|
|
32639
|
+
return validators.validateChartDefinition(validator, definition);
|
|
32640
|
+
}
|
|
32641
|
+
/**
|
|
32642
|
+
* Get a new chart definition transformed with the executed command. This
|
|
32643
|
+
* functions will be called during operational transform process
|
|
32644
|
+
*/
|
|
32645
|
+
function transformDefinition(definition, executed) {
|
|
32646
|
+
const transformation = chartRegistry.getAll().find((factory) => factory.match(definition.type));
|
|
32647
|
+
if (!transformation) {
|
|
32648
|
+
throw new Error("Unknown chart type.");
|
|
32649
|
+
}
|
|
32650
|
+
return transformation.transformDefinition(definition, executed);
|
|
32651
|
+
}
|
|
32652
|
+
/**
|
|
32653
|
+
* Return a "smart" chart definition in the given zone. The definition is "smart" because it will
|
|
32654
|
+
* use the best type of chart to display the data of the zone.
|
|
32655
|
+
*
|
|
32656
|
+
* It will also try to find labels and datasets in the range, and try to find title for the datasets.
|
|
32657
|
+
*
|
|
32658
|
+
* The type of chart will be :
|
|
32659
|
+
* - If the zone is a single non-empty cell, returns a scorecard
|
|
32660
|
+
* - If the all the labels are numbers/date, returns a line chart
|
|
32661
|
+
* - Else returns a bar chart
|
|
32662
|
+
*/
|
|
32663
|
+
function getSmartChartDefinition(zone, getters) {
|
|
32664
|
+
const sheetId = getters.getActiveSheetId();
|
|
32665
|
+
let dataSetZone = zone;
|
|
32666
|
+
const singleColumn = zoneToDimension(zone).numberOfCols === 1;
|
|
32667
|
+
if (!singleColumn) {
|
|
32668
|
+
dataSetZone = { ...zone, left: zone.left + 1 };
|
|
32669
|
+
}
|
|
32670
|
+
const dataRange = zoneToXc(getters.getUnboundedZone(sheetId, dataSetZone));
|
|
32671
|
+
const dataSets = [{ dataRange, yAxisId: "y" }];
|
|
32672
|
+
const topLeftCell = getters.getCell({ sheetId, col: zone.left, row: zone.top });
|
|
32673
|
+
if (getZoneArea(zone) === 1 && topLeftCell?.content) {
|
|
32674
|
+
return {
|
|
32675
|
+
type: "scorecard",
|
|
32676
|
+
title: {},
|
|
32677
|
+
background: topLeftCell.style?.fillColor || undefined,
|
|
32678
|
+
keyValue: zoneToXc(zone),
|
|
32679
|
+
baselineMode: DEFAULT_SCORECARD_BASELINE_MODE,
|
|
32680
|
+
baselineColorUp: DEFAULT_SCORECARD_BASELINE_COLOR_UP,
|
|
32681
|
+
baselineColorDown: DEFAULT_SCORECARD_BASELINE_COLOR_DOWN,
|
|
32682
|
+
};
|
|
32683
|
+
}
|
|
32684
|
+
const cellsInFirstRow = getters.getEvaluatedCellsInZone(sheetId, {
|
|
32685
|
+
...dataSetZone,
|
|
32686
|
+
bottom: dataSetZone.top,
|
|
32687
|
+
});
|
|
32688
|
+
const dataSetsHaveTitle = !!cellsInFirstRow.find((cell) => cell.type !== CellValueType.empty && cell.type !== CellValueType.number);
|
|
32689
|
+
let labelRangeXc;
|
|
32690
|
+
if (!singleColumn) {
|
|
32691
|
+
labelRangeXc = zoneToXc(getters.getUnboundedZone(sheetId, { ...zone, right: zone.left }));
|
|
32692
|
+
}
|
|
32693
|
+
// Only display legend for several datasets.
|
|
32694
|
+
const newLegendPos = dataSetZone.right === dataSetZone.left ? "none" : "top";
|
|
32695
|
+
const lineChartDefinition = {
|
|
32696
|
+
title: {},
|
|
32697
|
+
dataSets,
|
|
32698
|
+
labelsAsText: false,
|
|
32699
|
+
stacked: false,
|
|
32700
|
+
aggregated: false,
|
|
32701
|
+
cumulative: false,
|
|
32702
|
+
labelRange: labelRangeXc,
|
|
32703
|
+
type: "line",
|
|
32704
|
+
dataSetsHaveTitle,
|
|
32705
|
+
legendPosition: newLegendPos,
|
|
32706
|
+
};
|
|
32707
|
+
const chart = new LineChart(lineChartDefinition, sheetId, getters);
|
|
32708
|
+
if (canChartParseLabels(lineChartDefinition, chart.dataSets, chart.labelRange, getters)) {
|
|
32709
|
+
return lineChartDefinition;
|
|
32710
|
+
}
|
|
32711
|
+
const _dataSets = createDataSets(getters, dataSets, sheetId, dataSetsHaveTitle);
|
|
32712
|
+
if (singleColumn &&
|
|
32713
|
+
getData(getters, _dataSets[0]).every((e) => typeof e === "string" && !isEvaluationError(e))) {
|
|
32714
|
+
return {
|
|
32715
|
+
title: {},
|
|
32716
|
+
dataSets: [{ dataRange }],
|
|
32717
|
+
aggregated: true,
|
|
32718
|
+
labelRange: dataRange,
|
|
32719
|
+
type: "pie",
|
|
32720
|
+
legendPosition: "top",
|
|
32721
|
+
dataSetsHaveTitle: false,
|
|
32722
|
+
};
|
|
32723
|
+
}
|
|
32724
|
+
return {
|
|
32725
|
+
title: {},
|
|
32726
|
+
dataSets,
|
|
32727
|
+
labelRange: labelRangeXc,
|
|
32728
|
+
type: "bar",
|
|
32729
|
+
stacked: false,
|
|
32730
|
+
aggregated: false,
|
|
32731
|
+
dataSetsHaveTitle,
|
|
32732
|
+
legendPosition: newLegendPos,
|
|
32733
|
+
};
|
|
32734
|
+
}
|
|
32735
|
+
|
|
32736
|
+
var CHART_HELPERS = /*#__PURE__*/Object.freeze({
|
|
32737
|
+
__proto__: null,
|
|
32738
|
+
AbstractChart: AbstractChart,
|
|
32739
|
+
BarChart: BarChart,
|
|
32740
|
+
CHART_AXIS_CHOICES: CHART_AXIS_CHOICES,
|
|
32741
|
+
CHART_COMMON_OPTIONS: CHART_COMMON_OPTIONS,
|
|
32742
|
+
GaugeChart: GaugeChart,
|
|
32743
|
+
LineChart: LineChart,
|
|
32744
|
+
PieChart: PieChart,
|
|
32745
|
+
ScorecardChart: ScorecardChart$1,
|
|
32746
|
+
TREND_LINE_XAXIS_ID: TREND_LINE_XAXIS_ID,
|
|
32747
|
+
WaterfallChart: WaterfallChart,
|
|
32748
|
+
adaptChartRange: adaptChartRange,
|
|
32749
|
+
chartFactory: chartFactory,
|
|
32750
|
+
chartFontColor: chartFontColor,
|
|
32751
|
+
chartMutedFontColor: chartMutedFontColor,
|
|
32752
|
+
chartRuntimeFactory: chartRuntimeFactory,
|
|
32753
|
+
chartToImageFile: chartToImageFile,
|
|
32754
|
+
chartToImageUrl: chartToImageUrl,
|
|
32755
|
+
checkDataset: checkDataset,
|
|
32756
|
+
checkLabelRange: checkLabelRange,
|
|
32757
|
+
createBarChartRuntime: createBarChartRuntime,
|
|
32758
|
+
createDataSets: createDataSets,
|
|
32759
|
+
createGaugeChartRuntime: createGaugeChartRuntime,
|
|
32760
|
+
createLineChartRuntime: createLineChartRuntime,
|
|
32761
|
+
createPieChartRuntime: createPieChartRuntime,
|
|
32762
|
+
createScorecardChartRuntime: createScorecardChartRuntime,
|
|
32763
|
+
createWaterfallChartRuntime: createWaterfallChartRuntime,
|
|
32764
|
+
drawScoreChart: drawScoreChart,
|
|
32765
|
+
duplicateDataSetsInDuplicatedSheet: duplicateDataSetsInDuplicatedSheet,
|
|
32766
|
+
duplicateLabelRangeInDuplicatedSheet: duplicateLabelRangeInDuplicatedSheet,
|
|
32767
|
+
formatChartDatasetValue: formatChartDatasetValue,
|
|
32768
|
+
formatTickValue: formatTickValue,
|
|
32769
|
+
getChartPositionAtCenterOfViewport: getChartPositionAtCenterOfViewport,
|
|
32770
|
+
getDefinedAxis: getDefinedAxis,
|
|
32771
|
+
getPieColors: getPieColors,
|
|
32772
|
+
getSmartChartDefinition: getSmartChartDefinition,
|
|
32773
|
+
shouldRemoveFirstLabel: shouldRemoveFirstLabel,
|
|
32774
|
+
toExcelDataset: toExcelDataset,
|
|
32775
|
+
toExcelLabelRange: toExcelLabelRange,
|
|
32776
|
+
transformChartDefinitionWithDataSetsWithZone: transformChartDefinitionWithDataSetsWithZone,
|
|
32777
|
+
transformDefinition: transformDefinition,
|
|
32778
|
+
truncateLabel: truncateLabel,
|
|
32779
|
+
updateChartRangesWithDataSets: updateChartRangesWithDataSets,
|
|
32780
|
+
validateChartDefinition: validateChartDefinition
|
|
32781
|
+
});
|
|
32782
|
+
|
|
31937
32783
|
function centerFigurePosition(getters, size) {
|
|
31938
32784
|
const { x: offsetCorrectionX, y: offsetCorrectionY } = getters.getMainViewportCoordinates();
|
|
31939
32785
|
const { scrollX, scrollY } = getters.getActiveSheetScrollInfo();
|
|
@@ -31991,6 +32837,39 @@ function getChartMenu(figureId, onFigureDeleted, env) {
|
|
|
31991
32837
|
},
|
|
31992
32838
|
getCopyMenuItem(figureId, env),
|
|
31993
32839
|
getCutMenuItem(figureId, env),
|
|
32840
|
+
{
|
|
32841
|
+
id: "copy_as_image",
|
|
32842
|
+
name: _t("Copy as image"),
|
|
32843
|
+
icon: "o-spreadsheet-Icon.COPY_AS_IMAGE",
|
|
32844
|
+
sequence: 4,
|
|
32845
|
+
execute: async () => {
|
|
32846
|
+
const figureSheetId = env.model.getters.getFigureSheetId(figureId);
|
|
32847
|
+
const figure = env.model.getters.getFigure(figureSheetId, figureId);
|
|
32848
|
+
const chartType = env.model.getters.getChartType(figureId);
|
|
32849
|
+
const runtime = env.model.getters.getChartRuntime(figureId);
|
|
32850
|
+
const imageUrl = chartToImageUrl(runtime, figure, chartType);
|
|
32851
|
+
const innerHTML = `<img src="${xmlEscape(imageUrl)}" />`;
|
|
32852
|
+
const blob = await chartToImageFile(runtime, figure, chartType);
|
|
32853
|
+
env.clipboard.write({
|
|
32854
|
+
"text/html": innerHTML,
|
|
32855
|
+
"image/png": blob,
|
|
32856
|
+
});
|
|
32857
|
+
},
|
|
32858
|
+
},
|
|
32859
|
+
{
|
|
32860
|
+
id: "download",
|
|
32861
|
+
name: _t("Download"),
|
|
32862
|
+
icon: "o-spreadsheet-Icon.DOWNLOAD",
|
|
32863
|
+
sequence: 6,
|
|
32864
|
+
execute: async () => {
|
|
32865
|
+
const figureSheetId = env.model.getters.getFigureSheetId(figureId);
|
|
32866
|
+
const figure = env.model.getters.getFigure(figureSheetId, figureId);
|
|
32867
|
+
const chartType = env.model.getters.getChartType(figureId);
|
|
32868
|
+
const runtime = env.model.getters.getChartRuntime(figureId);
|
|
32869
|
+
const url = chartToImageUrl(runtime, figure, chartType);
|
|
32870
|
+
downloadFile(url, "chart");
|
|
32871
|
+
},
|
|
32872
|
+
},
|
|
31994
32873
|
getDeleteMenuItem(figureId, onFigureDeleted, env),
|
|
31995
32874
|
];
|
|
31996
32875
|
return createActions(menuItemSpecs);
|
|
@@ -32021,6 +32900,17 @@ function getImageMenuRegistry(figureId, onFigureDeleted, env) {
|
|
|
32021
32900
|
},
|
|
32022
32901
|
icon: "o-spreadsheet-Icon.REFRESH",
|
|
32023
32902
|
},
|
|
32903
|
+
{
|
|
32904
|
+
id: "download",
|
|
32905
|
+
name: _t("Download"),
|
|
32906
|
+
sequence: 6,
|
|
32907
|
+
execute: async () => {
|
|
32908
|
+
env.model.dispatch("SELECT_FIGURE", { id: figureId });
|
|
32909
|
+
const path = env.model.getters.getImagePath(figureId);
|
|
32910
|
+
downloadFile(path, "image");
|
|
32911
|
+
},
|
|
32912
|
+
icon: "o-spreadsheet-Icon.DOWNLOAD",
|
|
32913
|
+
},
|
|
32024
32914
|
getDeleteMenuItem(figureId, onFigureDeleted, env),
|
|
32025
32915
|
];
|
|
32026
32916
|
return createActions(menuItemSpecs);
|
|
@@ -32034,7 +32924,8 @@ function getCopyMenuItem(figureId, env) {
|
|
|
32034
32924
|
execute: async () => {
|
|
32035
32925
|
env.model.dispatch("SELECT_FIGURE", { id: figureId });
|
|
32036
32926
|
env.model.dispatch("COPY");
|
|
32037
|
-
await env.
|
|
32927
|
+
const osClipboardContent = await env.model.getters.getClipboardTextAndImageContent();
|
|
32928
|
+
await env.clipboard.write(osClipboardContent);
|
|
32038
32929
|
},
|
|
32039
32930
|
icon: "o-spreadsheet-Icon.CLIPBOARD",
|
|
32040
32931
|
};
|
|
@@ -32048,7 +32939,7 @@ function getCutMenuItem(figureId, env) {
|
|
|
32048
32939
|
execute: async () => {
|
|
32049
32940
|
env.model.dispatch("SELECT_FIGURE", { id: figureId });
|
|
32050
32941
|
env.model.dispatch("CUT");
|
|
32051
|
-
await env.clipboard.write(env.model.getters.
|
|
32942
|
+
await env.clipboard.write(await env.model.getters.getClipboardTextAndImageContent());
|
|
32052
32943
|
},
|
|
32053
32944
|
icon: "o-spreadsheet-Icon.CUT",
|
|
32054
32945
|
};
|
|
@@ -32479,6 +33370,7 @@ class Popover extends owl.Component {
|
|
|
32479
33370
|
onPopoverHidden: { type: Function, optional: true },
|
|
32480
33371
|
onPopoverMoved: { type: Function, optional: true },
|
|
32481
33372
|
zIndex: { type: Number, optional: true },
|
|
33373
|
+
class: { type: String, optional: true },
|
|
32482
33374
|
slots: Object,
|
|
32483
33375
|
};
|
|
32484
33376
|
static defaultProps = {
|
|
@@ -32511,10 +33403,6 @@ class Popover extends owl.Component {
|
|
|
32511
33403
|
this.currentDisplayValue = newDisplay;
|
|
32512
33404
|
if (!anchor)
|
|
32513
33405
|
return;
|
|
32514
|
-
el.style.top = "";
|
|
32515
|
-
el.style.left = "";
|
|
32516
|
-
el.style["max-height"] = "";
|
|
32517
|
-
el.style["max-width"] = "";
|
|
32518
33406
|
const propsMaxSize = { width: this.props.maxWidth, height: this.props.maxHeight };
|
|
32519
33407
|
let elDims = {
|
|
32520
33408
|
width: el.getBoundingClientRect().width,
|
|
@@ -33059,187 +33947,6 @@ const FilterMenuPopoverBuilder = {
|
|
|
33059
33947
|
},
|
|
33060
33948
|
};
|
|
33061
33949
|
|
|
33062
|
-
const macRegex = /Mac/i;
|
|
33063
|
-
const MODIFIER_KEYS = ["Shift", "Control", "Alt", "Meta"];
|
|
33064
|
-
/**
|
|
33065
|
-
* Return true if the event was triggered from
|
|
33066
|
-
* a child element.
|
|
33067
|
-
*/
|
|
33068
|
-
function isChildEvent(parent, ev) {
|
|
33069
|
-
if (!parent)
|
|
33070
|
-
return false;
|
|
33071
|
-
return !!ev.target && parent.contains(ev.target);
|
|
33072
|
-
}
|
|
33073
|
-
function gridOverlayPosition() {
|
|
33074
|
-
const spreadsheetElement = document.querySelector(".o-grid-overlay");
|
|
33075
|
-
if (spreadsheetElement) {
|
|
33076
|
-
const { top, left } = spreadsheetElement?.getBoundingClientRect();
|
|
33077
|
-
return { top, left };
|
|
33078
|
-
}
|
|
33079
|
-
throw new Error("Can't find spreadsheet position");
|
|
33080
|
-
}
|
|
33081
|
-
function getBoundingRectAsPOJO(el) {
|
|
33082
|
-
const rect = el.getBoundingClientRect();
|
|
33083
|
-
return {
|
|
33084
|
-
x: rect.x,
|
|
33085
|
-
y: rect.y,
|
|
33086
|
-
width: rect.width,
|
|
33087
|
-
height: rect.height,
|
|
33088
|
-
};
|
|
33089
|
-
}
|
|
33090
|
-
/**
|
|
33091
|
-
* Iterate over all the children of `el` in the dom tree starting at `el`, depth first.
|
|
33092
|
-
*/
|
|
33093
|
-
function* iterateChildren(el) {
|
|
33094
|
-
yield el;
|
|
33095
|
-
if (el.hasChildNodes()) {
|
|
33096
|
-
for (let child of el.childNodes) {
|
|
33097
|
-
yield* iterateChildren(child);
|
|
33098
|
-
}
|
|
33099
|
-
}
|
|
33100
|
-
}
|
|
33101
|
-
function getOpenedMenus() {
|
|
33102
|
-
return Array.from(document.querySelectorAll(".o-spreadsheet .o-menu"));
|
|
33103
|
-
}
|
|
33104
|
-
function getCurrentSelection(el) {
|
|
33105
|
-
let { startElement, endElement, startSelectionOffset, endSelectionOffset } = getStartAndEndSelection(el);
|
|
33106
|
-
let startSizeBefore = findSelectionIndex(el, startElement, startSelectionOffset);
|
|
33107
|
-
let endSizeBefore = findSelectionIndex(el, endElement, endSelectionOffset);
|
|
33108
|
-
return {
|
|
33109
|
-
start: startSizeBefore,
|
|
33110
|
-
end: endSizeBefore,
|
|
33111
|
-
};
|
|
33112
|
-
}
|
|
33113
|
-
function getStartAndEndSelection(el) {
|
|
33114
|
-
const selection = document.getSelection();
|
|
33115
|
-
return {
|
|
33116
|
-
startElement: selection.anchorNode || el,
|
|
33117
|
-
startSelectionOffset: selection.anchorOffset,
|
|
33118
|
-
endElement: selection.focusNode || el,
|
|
33119
|
-
endSelectionOffset: selection.focusOffset,
|
|
33120
|
-
};
|
|
33121
|
-
}
|
|
33122
|
-
/**
|
|
33123
|
-
* Computes the text 'index' inside this.el based on the currently selected node and its offset.
|
|
33124
|
-
* The selected node is either a Text node or an Element node.
|
|
33125
|
-
*
|
|
33126
|
-
* case 1 -Text node:
|
|
33127
|
-
* the offset is the number of characters from the start of the node. We have to add this offset to the
|
|
33128
|
-
* content length of all previous nodes.
|
|
33129
|
-
*
|
|
33130
|
-
* case 2 - Element node:
|
|
33131
|
-
* the offset is the number of child nodes before the selected node. We have to add the content length of
|
|
33132
|
-
* all the nodes prior to the selected node as well as the content of the child node before the offset.
|
|
33133
|
-
*
|
|
33134
|
-
* See the MDN documentation for more details.
|
|
33135
|
-
* https://developer.mozilla.org/en-US/docs/Web/API/Range/startOffset
|
|
33136
|
-
* https://developer.mozilla.org/en-US/docs/Web/API/Range/endOffset
|
|
33137
|
-
*
|
|
33138
|
-
*/
|
|
33139
|
-
function findSelectionIndex(el, nodeToFind, nodeOffset) {
|
|
33140
|
-
let usedCharacters = 0;
|
|
33141
|
-
let it = iterateChildren(el);
|
|
33142
|
-
let current = it.next();
|
|
33143
|
-
let isFirstParagraph = true;
|
|
33144
|
-
while (!current.done && current.value !== nodeToFind) {
|
|
33145
|
-
if (!current.value.hasChildNodes()) {
|
|
33146
|
-
if (current.value.textContent) {
|
|
33147
|
-
usedCharacters += current.value.textContent.length;
|
|
33148
|
-
}
|
|
33149
|
-
}
|
|
33150
|
-
// One new paragraph = one new line character, except for the first paragraph
|
|
33151
|
-
if (current.value.nodeName === "P" ||
|
|
33152
|
-
(current.value.nodeName === "DIV" && current.value !== el) // On paste, the HTML may contain <div> instead of <p>
|
|
33153
|
-
) {
|
|
33154
|
-
if (isFirstParagraph) {
|
|
33155
|
-
isFirstParagraph = false;
|
|
33156
|
-
}
|
|
33157
|
-
else {
|
|
33158
|
-
usedCharacters++;
|
|
33159
|
-
}
|
|
33160
|
-
}
|
|
33161
|
-
current = it.next();
|
|
33162
|
-
}
|
|
33163
|
-
if (current.value !== nodeToFind) {
|
|
33164
|
-
/** This situation can happen if the code is called while the selection is not currently on the element.
|
|
33165
|
-
* In this case, we return 0 because we don't know the size of the text before the selection.
|
|
33166
|
-
*
|
|
33167
|
-
* A known occurrence is triggered since the introduction of commit d4663158 (PR #2038).
|
|
33168
|
-
*/
|
|
33169
|
-
return 0;
|
|
33170
|
-
}
|
|
33171
|
-
else {
|
|
33172
|
-
if (!current.value.hasChildNodes()) {
|
|
33173
|
-
usedCharacters += nodeOffset;
|
|
33174
|
-
}
|
|
33175
|
-
else {
|
|
33176
|
-
const children = [...current.value.childNodes].slice(0, nodeOffset);
|
|
33177
|
-
usedCharacters += children.reduce((acc, child, index) => {
|
|
33178
|
-
if (child.textContent !== null) {
|
|
33179
|
-
// need to account for paragraph nodes that implicitly add a new line
|
|
33180
|
-
// except for the last paragraph
|
|
33181
|
-
let chars = child.textContent.length;
|
|
33182
|
-
if (child.nodeName === "P" && index !== children.length - 1) {
|
|
33183
|
-
chars++;
|
|
33184
|
-
}
|
|
33185
|
-
return acc + chars;
|
|
33186
|
-
}
|
|
33187
|
-
else {
|
|
33188
|
-
return acc;
|
|
33189
|
-
}
|
|
33190
|
-
}, 0);
|
|
33191
|
-
}
|
|
33192
|
-
}
|
|
33193
|
-
if (nodeToFind.nodeName === "P" && !isFirstParagraph && nodeToFind.textContent === "") {
|
|
33194
|
-
usedCharacters++;
|
|
33195
|
-
}
|
|
33196
|
-
return usedCharacters;
|
|
33197
|
-
}
|
|
33198
|
-
const letterRegex = /^[a-zA-Z]$/;
|
|
33199
|
-
/**
|
|
33200
|
-
* Transform a keyboard event into a shortcut string that represent this event. The letters keys will be uppercased.
|
|
33201
|
-
*
|
|
33202
|
-
* @argument ev - The keyboard event to transform
|
|
33203
|
-
* @argument mode - Use either ev.key of ev.code to get the string shortcut
|
|
33204
|
-
*
|
|
33205
|
-
* @example
|
|
33206
|
-
* event : { ctrlKey: true, key: "a" } => "Ctrl+A"
|
|
33207
|
-
* event : { shift: true, alt: true, key: "Home" } => "Alt+Shift+Home"
|
|
33208
|
-
*/
|
|
33209
|
-
function keyboardEventToShortcutString(ev, mode = "key") {
|
|
33210
|
-
let keyDownString = "";
|
|
33211
|
-
if (!MODIFIER_KEYS.includes(ev.key)) {
|
|
33212
|
-
if (isCtrlKey(ev))
|
|
33213
|
-
keyDownString += "Ctrl+";
|
|
33214
|
-
if (ev.altKey)
|
|
33215
|
-
keyDownString += "Alt+";
|
|
33216
|
-
if (ev.shiftKey)
|
|
33217
|
-
keyDownString += "Shift+";
|
|
33218
|
-
}
|
|
33219
|
-
const key = mode === "key" ? ev.key : ev.code;
|
|
33220
|
-
keyDownString += letterRegex.test(key) ? key.toUpperCase() : key;
|
|
33221
|
-
return keyDownString;
|
|
33222
|
-
}
|
|
33223
|
-
function isMacOS() {
|
|
33224
|
-
return Boolean(macRegex.test(navigator.userAgent));
|
|
33225
|
-
}
|
|
33226
|
-
/**
|
|
33227
|
-
* @param {KeyboardEvent | MouseEvent} ev
|
|
33228
|
-
* @returns Returns true if the event was triggered with the "ctrl" modifier pressed.
|
|
33229
|
-
* On Mac, this is the "meta" or "command" key.
|
|
33230
|
-
*/
|
|
33231
|
-
function isCtrlKey(ev) {
|
|
33232
|
-
return isMacOS() ? ev.metaKey : ev.ctrlKey;
|
|
33233
|
-
}
|
|
33234
|
-
/**
|
|
33235
|
-
* @param {MouseEvent} ev - The mouse event.
|
|
33236
|
-
* @returns {boolean} Returns true if the event was triggered by a middle-click
|
|
33237
|
-
* or a Ctrl + Click (Cmd + Click on Mac).
|
|
33238
|
-
*/
|
|
33239
|
-
function isMiddleClickOrCtrlClick(ev) {
|
|
33240
|
-
return ev.button === 1 || (isCtrlKey(ev) && ev.button === 0);
|
|
33241
|
-
}
|
|
33242
|
-
|
|
33243
33950
|
const LINK_TOOLTIP_HEIGHT = 32;
|
|
33244
33951
|
const LINK_TOOLTIP_WIDTH = 220;
|
|
33245
33952
|
css /* scss */ `
|
|
@@ -33909,186 +34616,6 @@ cellPopoverRegistry
|
|
|
33909
34616
|
.add("LinkEditor", LinkEditorPopoverBuilder)
|
|
33910
34617
|
.add("FilterMenu", FilterMenuPopoverBuilder);
|
|
33911
34618
|
|
|
33912
|
-
/**
|
|
33913
|
-
* Create a function used to create a Chart based on the definition
|
|
33914
|
-
*/
|
|
33915
|
-
function chartFactory(getters) {
|
|
33916
|
-
const builders = chartRegistry.getAll().sort((a, b) => a.sequence - b.sequence);
|
|
33917
|
-
function createChart(id, definition, sheetId) {
|
|
33918
|
-
const builder = builders.find((builder) => builder.match(definition.type));
|
|
33919
|
-
if (!builder) {
|
|
33920
|
-
throw new Error(`No builder for this chart: ${definition.type}`);
|
|
33921
|
-
}
|
|
33922
|
-
return builder.createChart(definition, sheetId, getters);
|
|
33923
|
-
}
|
|
33924
|
-
return createChart;
|
|
33925
|
-
}
|
|
33926
|
-
/**
|
|
33927
|
-
* Create a function used to create a Chart Runtime based on the chart class
|
|
33928
|
-
* instance
|
|
33929
|
-
*/
|
|
33930
|
-
function chartRuntimeFactory(getters) {
|
|
33931
|
-
const builders = chartRegistry.getAll().sort((a, b) => a.sequence - b.sequence);
|
|
33932
|
-
function createRuntimeChart(chart) {
|
|
33933
|
-
const builder = builders.find((builder) => builder.match(chart.type));
|
|
33934
|
-
if (!builder) {
|
|
33935
|
-
throw new Error("No runtime builder for this chart.");
|
|
33936
|
-
}
|
|
33937
|
-
return builder.getChartRuntime(chart, getters);
|
|
33938
|
-
}
|
|
33939
|
-
return createRuntimeChart;
|
|
33940
|
-
}
|
|
33941
|
-
/**
|
|
33942
|
-
* Validate the chart definition given in arguments
|
|
33943
|
-
*/
|
|
33944
|
-
function validateChartDefinition(validator, definition) {
|
|
33945
|
-
const validators = chartRegistry.getAll().find((validator) => validator.match(definition.type));
|
|
33946
|
-
if (!validators) {
|
|
33947
|
-
throw new Error("Unknown chart type.");
|
|
33948
|
-
}
|
|
33949
|
-
return validators.validateChartDefinition(validator, definition);
|
|
33950
|
-
}
|
|
33951
|
-
/**
|
|
33952
|
-
* Get a new chart definition transformed with the executed command. This
|
|
33953
|
-
* functions will be called during operational transform process
|
|
33954
|
-
*/
|
|
33955
|
-
function transformDefinition(definition, executed) {
|
|
33956
|
-
const transformation = chartRegistry.getAll().find((factory) => factory.match(definition.type));
|
|
33957
|
-
if (!transformation) {
|
|
33958
|
-
throw new Error("Unknown chart type.");
|
|
33959
|
-
}
|
|
33960
|
-
return transformation.transformDefinition(definition, executed);
|
|
33961
|
-
}
|
|
33962
|
-
/**
|
|
33963
|
-
* Return a "smart" chart definition in the given zone. The definition is "smart" because it will
|
|
33964
|
-
* use the best type of chart to display the data of the zone.
|
|
33965
|
-
*
|
|
33966
|
-
* It will also try to find labels and datasets in the range, and try to find title for the datasets.
|
|
33967
|
-
*
|
|
33968
|
-
* The type of chart will be :
|
|
33969
|
-
* - If the zone is a single non-empty cell, returns a scorecard
|
|
33970
|
-
* - If the all the labels are numbers/date, returns a line chart
|
|
33971
|
-
* - Else returns a bar chart
|
|
33972
|
-
*/
|
|
33973
|
-
function getSmartChartDefinition(zone, getters) {
|
|
33974
|
-
const sheetId = getters.getActiveSheetId();
|
|
33975
|
-
let dataSetZone = zone;
|
|
33976
|
-
const singleColumn = zoneToDimension(zone).numberOfCols === 1;
|
|
33977
|
-
if (!singleColumn) {
|
|
33978
|
-
dataSetZone = { ...zone, left: zone.left + 1 };
|
|
33979
|
-
}
|
|
33980
|
-
const dataRange = zoneToXc(getters.getUnboundedZone(sheetId, dataSetZone));
|
|
33981
|
-
const dataSets = [{ dataRange, yAxisId: "y" }];
|
|
33982
|
-
const topLeftCell = getters.getCell({ sheetId, col: zone.left, row: zone.top });
|
|
33983
|
-
if (getZoneArea(zone) === 1 && topLeftCell?.content) {
|
|
33984
|
-
return {
|
|
33985
|
-
type: "scorecard",
|
|
33986
|
-
title: {},
|
|
33987
|
-
background: topLeftCell.style?.fillColor || undefined,
|
|
33988
|
-
keyValue: zoneToXc(zone),
|
|
33989
|
-
baselineMode: DEFAULT_SCORECARD_BASELINE_MODE,
|
|
33990
|
-
baselineColorUp: DEFAULT_SCORECARD_BASELINE_COLOR_UP,
|
|
33991
|
-
baselineColorDown: DEFAULT_SCORECARD_BASELINE_COLOR_DOWN,
|
|
33992
|
-
};
|
|
33993
|
-
}
|
|
33994
|
-
const cellsInFirstRow = getters.getEvaluatedCellsInZone(sheetId, {
|
|
33995
|
-
...dataSetZone,
|
|
33996
|
-
bottom: dataSetZone.top,
|
|
33997
|
-
});
|
|
33998
|
-
const dataSetsHaveTitle = !!cellsInFirstRow.find((cell) => cell.type !== CellValueType.empty && cell.type !== CellValueType.number);
|
|
33999
|
-
let labelRangeXc;
|
|
34000
|
-
if (!singleColumn) {
|
|
34001
|
-
labelRangeXc = zoneToXc(getters.getUnboundedZone(sheetId, { ...zone, right: zone.left }));
|
|
34002
|
-
}
|
|
34003
|
-
// Only display legend for several datasets.
|
|
34004
|
-
const newLegendPos = dataSetZone.right === dataSetZone.left ? "none" : "top";
|
|
34005
|
-
const lineChartDefinition = {
|
|
34006
|
-
title: {},
|
|
34007
|
-
dataSets,
|
|
34008
|
-
labelsAsText: false,
|
|
34009
|
-
stacked: false,
|
|
34010
|
-
aggregated: false,
|
|
34011
|
-
cumulative: false,
|
|
34012
|
-
labelRange: labelRangeXc,
|
|
34013
|
-
type: "line",
|
|
34014
|
-
dataSetsHaveTitle,
|
|
34015
|
-
legendPosition: newLegendPos,
|
|
34016
|
-
};
|
|
34017
|
-
const chart = new LineChart(lineChartDefinition, sheetId, getters);
|
|
34018
|
-
if (canChartParseLabels(lineChartDefinition, chart.dataSets, chart.labelRange, getters)) {
|
|
34019
|
-
return lineChartDefinition;
|
|
34020
|
-
}
|
|
34021
|
-
const _dataSets = createDataSets(getters, dataSets, sheetId, dataSetsHaveTitle);
|
|
34022
|
-
if (singleColumn &&
|
|
34023
|
-
getData(getters, _dataSets[0]).every((e) => typeof e === "string" && !isEvaluationError(e))) {
|
|
34024
|
-
return {
|
|
34025
|
-
title: {},
|
|
34026
|
-
dataSets: [{ dataRange }],
|
|
34027
|
-
aggregated: true,
|
|
34028
|
-
labelRange: dataRange,
|
|
34029
|
-
type: "pie",
|
|
34030
|
-
legendPosition: "top",
|
|
34031
|
-
dataSetsHaveTitle: false,
|
|
34032
|
-
};
|
|
34033
|
-
}
|
|
34034
|
-
return {
|
|
34035
|
-
title: {},
|
|
34036
|
-
dataSets,
|
|
34037
|
-
labelRange: labelRangeXc,
|
|
34038
|
-
type: "bar",
|
|
34039
|
-
stacked: false,
|
|
34040
|
-
aggregated: false,
|
|
34041
|
-
dataSetsHaveTitle,
|
|
34042
|
-
legendPosition: newLegendPos,
|
|
34043
|
-
};
|
|
34044
|
-
}
|
|
34045
|
-
|
|
34046
|
-
var CHART_HELPERS = /*#__PURE__*/Object.freeze({
|
|
34047
|
-
__proto__: null,
|
|
34048
|
-
AbstractChart: AbstractChart,
|
|
34049
|
-
BarChart: BarChart,
|
|
34050
|
-
CHART_AXIS_CHOICES: CHART_AXIS_CHOICES,
|
|
34051
|
-
CHART_COMMON_OPTIONS: CHART_COMMON_OPTIONS,
|
|
34052
|
-
GaugeChart: GaugeChart,
|
|
34053
|
-
LineChart: LineChart,
|
|
34054
|
-
PieChart: PieChart,
|
|
34055
|
-
ScorecardChart: ScorecardChart$1,
|
|
34056
|
-
TREND_LINE_XAXIS_ID: TREND_LINE_XAXIS_ID,
|
|
34057
|
-
WaterfallChart: WaterfallChart,
|
|
34058
|
-
adaptChartRange: adaptChartRange,
|
|
34059
|
-
chartFactory: chartFactory,
|
|
34060
|
-
chartFontColor: chartFontColor,
|
|
34061
|
-
chartMutedFontColor: chartMutedFontColor,
|
|
34062
|
-
chartRuntimeFactory: chartRuntimeFactory,
|
|
34063
|
-
chartToImage: chartToImage,
|
|
34064
|
-
checkDataset: checkDataset,
|
|
34065
|
-
checkLabelRange: checkLabelRange,
|
|
34066
|
-
createBarChartRuntime: createBarChartRuntime,
|
|
34067
|
-
createDataSets: createDataSets,
|
|
34068
|
-
createGaugeChartRuntime: createGaugeChartRuntime,
|
|
34069
|
-
createLineChartRuntime: createLineChartRuntime,
|
|
34070
|
-
createPieChartRuntime: createPieChartRuntime,
|
|
34071
|
-
createScorecardChartRuntime: createScorecardChartRuntime,
|
|
34072
|
-
createWaterfallChartRuntime: createWaterfallChartRuntime,
|
|
34073
|
-
drawScoreChart: drawScoreChart,
|
|
34074
|
-
duplicateDataSetsInDuplicatedSheet: duplicateDataSetsInDuplicatedSheet,
|
|
34075
|
-
duplicateLabelRangeInDuplicatedSheet: duplicateLabelRangeInDuplicatedSheet,
|
|
34076
|
-
formatChartDatasetValue: formatChartDatasetValue,
|
|
34077
|
-
formatTickValue: formatTickValue,
|
|
34078
|
-
getChartPositionAtCenterOfViewport: getChartPositionAtCenterOfViewport,
|
|
34079
|
-
getDefinedAxis: getDefinedAxis,
|
|
34080
|
-
getPieColors: getPieColors,
|
|
34081
|
-
getSmartChartDefinition: getSmartChartDefinition,
|
|
34082
|
-
shouldRemoveFirstLabel: shouldRemoveFirstLabel,
|
|
34083
|
-
toExcelDataset: toExcelDataset,
|
|
34084
|
-
toExcelLabelRange: toExcelLabelRange,
|
|
34085
|
-
transformChartDefinitionWithDataSetsWithZone: transformChartDefinitionWithDataSetsWithZone,
|
|
34086
|
-
transformDefinition: transformDefinition,
|
|
34087
|
-
truncateLabel: truncateLabel,
|
|
34088
|
-
updateChartRangesWithDataSets: updateChartRangesWithDataSets,
|
|
34089
|
-
validateChartDefinition: validateChartDefinition
|
|
34090
|
-
});
|
|
34091
|
-
|
|
34092
34619
|
/**
|
|
34093
34620
|
* Create a table on the selected zone, with UI warnings to the user if the creation fails.
|
|
34094
34621
|
* If a single cell is selected, expand the selection to non-empty adjacent cells to create a table.
|
|
@@ -34143,11 +34670,12 @@ async function paste$1(env, pasteOption) {
|
|
|
34143
34670
|
const osClipboard = await env.clipboard.read();
|
|
34144
34671
|
switch (osClipboard.status) {
|
|
34145
34672
|
case "ok":
|
|
34146
|
-
const
|
|
34147
|
-
const
|
|
34673
|
+
const clipboardId = env.model.getters.getClipboardId();
|
|
34674
|
+
const osClipboardContent = parseOSClipboardContent(osClipboard.content);
|
|
34675
|
+
const osClipboardId = osClipboardContent.data?.clipboardId;
|
|
34148
34676
|
const target = env.model.getters.getSelectedZones();
|
|
34149
|
-
if (
|
|
34150
|
-
interactivePasteFromOS(env, target,
|
|
34677
|
+
if (clipboardId !== osClipboardId) {
|
|
34678
|
+
await interactivePasteFromOS(env, target, osClipboardContent, pasteOption);
|
|
34151
34679
|
}
|
|
34152
34680
|
else {
|
|
34153
34681
|
interactivePaste(env, target, pasteOption);
|
|
@@ -34507,7 +35035,7 @@ async function requestImage(env) {
|
|
|
34507
35035
|
}
|
|
34508
35036
|
catch {
|
|
34509
35037
|
env.raiseError(_t("An unexpected error occurred during the image transfer"));
|
|
34510
|
-
return
|
|
35038
|
+
return;
|
|
34511
35039
|
}
|
|
34512
35040
|
}
|
|
34513
35041
|
const CREATE_IMAGE = async (env) => {
|
|
@@ -34516,7 +35044,7 @@ const CREATE_IMAGE = async (env) => {
|
|
|
34516
35044
|
const figureId = env.model.uuidGenerator.smallUuid();
|
|
34517
35045
|
const image = await requestImage(env);
|
|
34518
35046
|
if (!image) {
|
|
34519
|
-
|
|
35047
|
+
return;
|
|
34520
35048
|
}
|
|
34521
35049
|
const size = getMaxFigureSize(env.model.getters, image.size);
|
|
34522
35050
|
const position = centerFigurePosition(env.model.getters, size);
|
|
@@ -34658,7 +35186,7 @@ const copy = {
|
|
|
34658
35186
|
isReadonlyAllowed: true,
|
|
34659
35187
|
execute: async (env) => {
|
|
34660
35188
|
env.model.dispatch("COPY");
|
|
34661
|
-
await env.clipboard.write(env.model.getters.
|
|
35189
|
+
await env.clipboard.write(await env.model.getters.getClipboardTextAndImageContent());
|
|
34662
35190
|
},
|
|
34663
35191
|
icon: "o-spreadsheet-Icon.CLIPBOARD",
|
|
34664
35192
|
};
|
|
@@ -34667,7 +35195,7 @@ const cut = {
|
|
|
34667
35195
|
description: "Ctrl+X",
|
|
34668
35196
|
execute: async (env) => {
|
|
34669
35197
|
interactiveCut(env);
|
|
34670
|
-
await env.clipboard.write(env.model.getters.
|
|
35198
|
+
await env.clipboard.write(await env.model.getters.getClipboardTextAndImageContent());
|
|
34671
35199
|
},
|
|
34672
35200
|
icon: "o-spreadsheet-Icon.CUT",
|
|
34673
35201
|
};
|
|
@@ -34704,7 +35232,7 @@ const findAndReplace = {
|
|
|
34704
35232
|
};
|
|
34705
35233
|
const deleteValues = {
|
|
34706
35234
|
name: _t("Delete values"),
|
|
34707
|
-
execute: (env) => env.model.dispatch("
|
|
35235
|
+
execute: (env) => env.model.dispatch("DELETE_UNFILTERED_CONTENT", {
|
|
34708
35236
|
sheetId: env.model.getters.getActiveSheetId(),
|
|
34709
35237
|
target: env.model.getters.getSelectedZones(),
|
|
34710
35238
|
}),
|
|
@@ -34806,32 +35334,6 @@ function toggleMerge(env) {
|
|
|
34806
35334
|
}
|
|
34807
35335
|
}
|
|
34808
35336
|
|
|
34809
|
-
var ACTION_EDIT = /*#__PURE__*/Object.freeze({
|
|
34810
|
-
__proto__: null,
|
|
34811
|
-
clearCols: clearCols,
|
|
34812
|
-
clearRows: clearRows,
|
|
34813
|
-
copy: copy,
|
|
34814
|
-
cut: cut,
|
|
34815
|
-
deleteCellShiftLeft: deleteCellShiftLeft,
|
|
34816
|
-
deleteCellShiftUp: deleteCellShiftUp,
|
|
34817
|
-
deleteCells: deleteCells,
|
|
34818
|
-
deleteCol: deleteCol,
|
|
34819
|
-
deleteCols: deleteCols,
|
|
34820
|
-
deleteRow: deleteRow,
|
|
34821
|
-
deleteRows: deleteRows,
|
|
34822
|
-
deleteTable: deleteTable,
|
|
34823
|
-
deleteValues: deleteValues,
|
|
34824
|
-
editTable: editTable,
|
|
34825
|
-
findAndReplace: findAndReplace,
|
|
34826
|
-
mergeCells: mergeCells,
|
|
34827
|
-
paste: paste,
|
|
34828
|
-
pasteSpecial: pasteSpecial,
|
|
34829
|
-
pasteSpecialFormat: pasteSpecialFormat,
|
|
34830
|
-
pasteSpecialValue: pasteSpecialValue,
|
|
34831
|
-
redo: redo,
|
|
34832
|
-
undo: undo
|
|
34833
|
-
});
|
|
34834
|
-
|
|
34835
35337
|
const insertRow = {
|
|
34836
35338
|
name: (env) => {
|
|
34837
35339
|
const number = getRowsNumber(env);
|
|
@@ -35475,21 +35977,6 @@ const reinsertStaticPivotMenu = {
|
|
|
35475
35977
|
isVisible: (env) => env.model.getters.getPivotIds().some((id) => env.model.getters.getPivot(id).isValid()),
|
|
35476
35978
|
};
|
|
35477
35979
|
|
|
35478
|
-
var ACTION_DATA = /*#__PURE__*/Object.freeze({
|
|
35479
|
-
__proto__: null,
|
|
35480
|
-
createRemoveFilter: createRemoveFilter,
|
|
35481
|
-
createRemoveFilterTool: createRemoveFilterTool,
|
|
35482
|
-
dataCleanup: dataCleanup,
|
|
35483
|
-
reinsertDynamicPivotMenu: reinsertDynamicPivotMenu,
|
|
35484
|
-
reinsertStaticPivotMenu: reinsertStaticPivotMenu,
|
|
35485
|
-
removeDuplicates: removeDuplicates,
|
|
35486
|
-
sortAscending: sortAscending,
|
|
35487
|
-
sortDescending: sortDescending,
|
|
35488
|
-
sortRange: sortRange,
|
|
35489
|
-
splitToColumns: splitToColumns,
|
|
35490
|
-
trimWhitespace: trimWhitespace
|
|
35491
|
-
});
|
|
35492
|
-
|
|
35493
35980
|
/**
|
|
35494
35981
|
* Create a format action specification for a given format.
|
|
35495
35982
|
* The format can be dynamically computed from the environment.
|
|
@@ -35632,7 +36119,7 @@ const formatNumberShortMonth = createFormatActionSpec({
|
|
|
35632
36119
|
format: "mmm yyyy",
|
|
35633
36120
|
descriptionValue: EXAMPLE_DATE,
|
|
35634
36121
|
});
|
|
35635
|
-
const
|
|
36122
|
+
const increaseDecimalPlaces = {
|
|
35636
36123
|
name: _t("Increase decimal places"),
|
|
35637
36124
|
icon: "o-spreadsheet-Icon.INCREASE_DECIMAL",
|
|
35638
36125
|
execute: (env) => env.model.dispatch("SET_DECIMAL", {
|
|
@@ -35641,7 +36128,7 @@ const incraseDecimalPlaces = {
|
|
|
35641
36128
|
step: 1,
|
|
35642
36129
|
}),
|
|
35643
36130
|
};
|
|
35644
|
-
const
|
|
36131
|
+
const decreaseDecimalPlaces = {
|
|
35645
36132
|
name: _t("Decrease decimal places"),
|
|
35646
36133
|
icon: "o-spreadsheet-Icon.DECRASE_DECIMAL",
|
|
35647
36134
|
execute: (env) => env.model.dispatch("SET_DECIMAL", {
|
|
@@ -35759,14 +36246,14 @@ const formatWrappingClip = {
|
|
|
35759
36246
|
isActive: (env) => getWrappingMode(env) === "clip",
|
|
35760
36247
|
icon: "o-spreadsheet-Icon.WRAPPING_CLIP",
|
|
35761
36248
|
};
|
|
35762
|
-
|
|
36249
|
+
({
|
|
35763
36250
|
name: _t("Text Color"),
|
|
35764
36251
|
icon: "o-spreadsheet-Icon.TEXT_COLOR",
|
|
35765
|
-
};
|
|
35766
|
-
|
|
36252
|
+
});
|
|
36253
|
+
({
|
|
35767
36254
|
name: _t("Fill Color"),
|
|
35768
36255
|
icon: "o-spreadsheet-Icon.FILL_COLOR",
|
|
35769
|
-
};
|
|
36256
|
+
});
|
|
35770
36257
|
const formatCF = {
|
|
35771
36258
|
name: _t("Conditional formatting"),
|
|
35772
36259
|
execute: OPEN_CF_SIDEPANEL_ACTION,
|
|
@@ -35868,60 +36355,6 @@ function getWrapModeIcon(env) {
|
|
|
35868
36355
|
}
|
|
35869
36356
|
}
|
|
35870
36357
|
|
|
35871
|
-
var ACTION_FORMAT = /*#__PURE__*/Object.freeze({
|
|
35872
|
-
__proto__: null,
|
|
35873
|
-
EXAMPLE_DATE: EXAMPLE_DATE,
|
|
35874
|
-
clearFormat: clearFormat,
|
|
35875
|
-
createFormatActionSpec: createFormatActionSpec,
|
|
35876
|
-
decraseDecimalPlaces: decraseDecimalPlaces,
|
|
35877
|
-
fillColor: fillColor,
|
|
35878
|
-
formatAlignment: formatAlignment,
|
|
35879
|
-
formatAlignmentBottom: formatAlignmentBottom,
|
|
35880
|
-
formatAlignmentCenter: formatAlignmentCenter,
|
|
35881
|
-
formatAlignmentHorizontal: formatAlignmentHorizontal,
|
|
35882
|
-
formatAlignmentLeft: formatAlignmentLeft,
|
|
35883
|
-
formatAlignmentMiddle: formatAlignmentMiddle,
|
|
35884
|
-
formatAlignmentRight: formatAlignmentRight,
|
|
35885
|
-
formatAlignmentTop: formatAlignmentTop,
|
|
35886
|
-
formatAlignmentVertical: formatAlignmentVertical,
|
|
35887
|
-
formatBold: formatBold,
|
|
35888
|
-
formatCF: formatCF,
|
|
35889
|
-
formatCustomCurrency: formatCustomCurrency,
|
|
35890
|
-
formatFontSize: formatFontSize,
|
|
35891
|
-
formatItalic: formatItalic,
|
|
35892
|
-
formatNumberAccounting: formatNumberAccounting,
|
|
35893
|
-
formatNumberAutomatic: formatNumberAutomatic,
|
|
35894
|
-
formatNumberCurrency: formatNumberCurrency,
|
|
35895
|
-
formatNumberCurrencyRounded: formatNumberCurrencyRounded,
|
|
35896
|
-
formatNumberDate: formatNumberDate,
|
|
35897
|
-
formatNumberDateTime: formatNumberDateTime,
|
|
35898
|
-
formatNumberDayAndFullMonth: formatNumberDayAndFullMonth,
|
|
35899
|
-
formatNumberDayAndShortMonth: formatNumberDayAndShortMonth,
|
|
35900
|
-
formatNumberDuration: formatNumberDuration,
|
|
35901
|
-
formatNumberFullDateTime: formatNumberFullDateTime,
|
|
35902
|
-
formatNumberFullMonth: formatNumberFullMonth,
|
|
35903
|
-
formatNumberFullQuarter: formatNumberFullQuarter,
|
|
35904
|
-
formatNumberFullWeekDayAndMonth: formatNumberFullWeekDayAndMonth,
|
|
35905
|
-
formatNumberNumber: formatNumberNumber,
|
|
35906
|
-
formatNumberPercent: formatNumberPercent,
|
|
35907
|
-
formatNumberPlainText: formatNumberPlainText,
|
|
35908
|
-
formatNumberQuarter: formatNumberQuarter,
|
|
35909
|
-
formatNumberShortMonth: formatNumberShortMonth,
|
|
35910
|
-
formatNumberShortWeekDay: formatNumberShortWeekDay,
|
|
35911
|
-
formatNumberTime: formatNumberTime,
|
|
35912
|
-
formatPercent: formatPercent,
|
|
35913
|
-
formatStrikethrough: formatStrikethrough,
|
|
35914
|
-
formatUnderline: formatUnderline,
|
|
35915
|
-
formatWrapping: formatWrapping,
|
|
35916
|
-
formatWrappingClip: formatWrappingClip,
|
|
35917
|
-
formatWrappingIcon: formatWrappingIcon,
|
|
35918
|
-
formatWrappingOverflow: formatWrappingOverflow,
|
|
35919
|
-
formatWrappingWrap: formatWrappingWrap,
|
|
35920
|
-
incraseDecimalPlaces: incraseDecimalPlaces,
|
|
35921
|
-
moreFormats: moreFormats,
|
|
35922
|
-
textColor: textColor
|
|
35923
|
-
});
|
|
35924
|
-
|
|
35925
36358
|
function interactiveFreezeColumnsRows(env, dimension, base) {
|
|
35926
36359
|
const sheetId = env.model.getters.getActiveSheetId();
|
|
35927
36360
|
const cmd = dimension === "COL" ? "FREEZE_COLUMNS" : "FREEZE_ROWS";
|
|
@@ -37875,6 +38308,11 @@ class SelectionInputStore extends SpreadsheetStore {
|
|
|
37875
38308
|
}
|
|
37876
38309
|
updateColors(colors) {
|
|
37877
38310
|
this.colors = colors;
|
|
38311
|
+
const colorGenerator = new ColorGenerator(this.ranges.length, this.colors);
|
|
38312
|
+
this.ranges = this.ranges.map((range) => ({
|
|
38313
|
+
...range,
|
|
38314
|
+
color: colorGenerator.next(),
|
|
38315
|
+
}));
|
|
37878
38316
|
}
|
|
37879
38317
|
confirm() {
|
|
37880
38318
|
for (const range of this.selectionInputs) {
|
|
@@ -37909,12 +38347,11 @@ class SelectionInputStore extends SpreadsheetStore {
|
|
|
37909
38347
|
* e.g. ["A1", "Sheet2!B3", "E12"]
|
|
37910
38348
|
*/
|
|
37911
38349
|
get selectionInputs() {
|
|
37912
|
-
const generator = new ColorGenerator(this.ranges.length, this.colors);
|
|
37913
38350
|
return this.ranges.map((input, index) => Object.assign({}, input, {
|
|
37914
38351
|
color: this.hasMainFocus &&
|
|
37915
38352
|
this.focusedRangeIndex !== null &&
|
|
37916
38353
|
this.getters.isRangeValid(input.xc)
|
|
37917
|
-
?
|
|
38354
|
+
? input.color
|
|
37918
38355
|
: null,
|
|
37919
38356
|
isFocused: this.hasMainFocus && this.focusedRangeIndex === index,
|
|
37920
38357
|
isValidRange: input.xc === "" || this.getters.isRangeValid(input.xc),
|
|
@@ -38184,10 +38621,10 @@ class SelectionInput extends owl.Component {
|
|
|
38184
38621
|
if (originalIndex === finalIndex) {
|
|
38185
38622
|
return;
|
|
38186
38623
|
}
|
|
38187
|
-
const
|
|
38188
|
-
|
|
38189
|
-
|
|
38190
|
-
this.props.onSelectionReordered?.(
|
|
38624
|
+
const indexes = range(0, draggableIds.length);
|
|
38625
|
+
indexes.splice(originalIndex, 1);
|
|
38626
|
+
indexes.splice(finalIndex, 0, originalIndex);
|
|
38627
|
+
this.props.onSelectionReordered?.(indexes);
|
|
38191
38628
|
this.props.onSelectionConfirmed?.();
|
|
38192
38629
|
this.store.confirm();
|
|
38193
38630
|
},
|
|
@@ -39014,8 +39451,12 @@ class FontSizeEditor extends owl.Component {
|
|
|
39014
39451
|
currentFontSize: Number,
|
|
39015
39452
|
onFontSizeChanged: Function,
|
|
39016
39453
|
onToggle: { type: Function, optional: true },
|
|
39454
|
+
onFocusInput: { type: Function, optional: true },
|
|
39017
39455
|
class: String,
|
|
39018
39456
|
};
|
|
39457
|
+
static defaultProps = {
|
|
39458
|
+
onFocusInput: () => { },
|
|
39459
|
+
};
|
|
39019
39460
|
static components = { Popover };
|
|
39020
39461
|
fontSizes = FONT_SIZES;
|
|
39021
39462
|
dropdown = owl.useState({ isOpen: false });
|
|
@@ -39708,6 +40149,36 @@ class ComboChartDesignPanel extends ChartWithAxisDesignPanel {
|
|
|
39708
40149
|
}
|
|
39709
40150
|
}
|
|
39710
40151
|
|
|
40152
|
+
class FunnelChartDesignPanel extends owl.Component {
|
|
40153
|
+
static template = "o-spreadsheet-FunnelChartDesignPanel";
|
|
40154
|
+
static components = {
|
|
40155
|
+
GeneralDesignEditor,
|
|
40156
|
+
SidePanelCollapsible,
|
|
40157
|
+
RoundColorPicker,
|
|
40158
|
+
Section,
|
|
40159
|
+
Checkbox,
|
|
40160
|
+
};
|
|
40161
|
+
static props = {
|
|
40162
|
+
figureId: String,
|
|
40163
|
+
definition: Object,
|
|
40164
|
+
updateChart: Function,
|
|
40165
|
+
canUpdateChart: Function,
|
|
40166
|
+
};
|
|
40167
|
+
getFunnelColorItems() {
|
|
40168
|
+
const runtime = this.env.model.getters.getChartRuntime(this.props.figureId);
|
|
40169
|
+
const labels = (runtime.chartJsConfig.data.labels || []);
|
|
40170
|
+
const colors = getFunnelLabelColors(labels, this.props.definition.funnelColors);
|
|
40171
|
+
return labels.map((label, index) => ({
|
|
40172
|
+
label: label || _t("Value %s", index + 1),
|
|
40173
|
+
color: colors[index],
|
|
40174
|
+
}));
|
|
40175
|
+
}
|
|
40176
|
+
updateFunnelItemColor(index, color) {
|
|
40177
|
+
const funnelColors = replaceItemAtIndex(this.props.definition.funnelColors || [], color, index);
|
|
40178
|
+
this.props.updateChart(this.props.figureId, { funnelColors });
|
|
40179
|
+
}
|
|
40180
|
+
}
|
|
40181
|
+
|
|
39711
40182
|
class GaugeChartConfigPanel extends owl.Component {
|
|
39712
40183
|
static template = "o-spreadsheet-GaugeChartConfigPanel";
|
|
39713
40184
|
static components = { ChartErrorSection, ChartDataSeries };
|
|
@@ -40195,8 +40666,7 @@ css /* scss */ `
|
|
|
40195
40666
|
}
|
|
40196
40667
|
|
|
40197
40668
|
.o-composer-assistant {
|
|
40198
|
-
|
|
40199
|
-
margin: 1px 4px;
|
|
40669
|
+
margin-top: 1px;
|
|
40200
40670
|
|
|
40201
40671
|
.o-semi-bold {
|
|
40202
40672
|
/* FIXME: to remove in favor of Bootstrap
|
|
@@ -40247,10 +40717,11 @@ class Composer extends owl.Component {
|
|
|
40247
40717
|
});
|
|
40248
40718
|
compositionActive = false;
|
|
40249
40719
|
spreadsheetRect = useSpreadsheetRect();
|
|
40250
|
-
get
|
|
40720
|
+
get assistantStyleProperties() {
|
|
40251
40721
|
const composerRect = this.composerRef.el.getBoundingClientRect();
|
|
40252
40722
|
const assistantStyle = {};
|
|
40253
|
-
|
|
40723
|
+
const minWidth = Math.min(this.props.rect?.width || Infinity, ASSISTANT_WIDTH);
|
|
40724
|
+
assistantStyle["min-width"] = `${minWidth}px`;
|
|
40254
40725
|
const proposals = this.autoCompleteState.provider?.proposals;
|
|
40255
40726
|
const proposalsHaveDescription = proposals?.some((proposal) => proposal.description);
|
|
40256
40727
|
if (this.functionDescriptionState.showDescription || proposalsHaveDescription) {
|
|
@@ -40274,13 +40745,29 @@ class Composer extends owl.Component {
|
|
|
40274
40745
|
}
|
|
40275
40746
|
}
|
|
40276
40747
|
else {
|
|
40277
|
-
assistantStyle["max-height"] = `${this.spreadsheetRect.height - composerRect.bottom}px`;
|
|
40748
|
+
assistantStyle["max-height"] = `${this.spreadsheetRect.height - composerRect.bottom - 1}px`; // -1: margin
|
|
40278
40749
|
if (composerRect.left + ASSISTANT_WIDTH + SCROLLBAR_WIDTH + CLOSE_ICON_RADIUS >
|
|
40279
40750
|
this.spreadsheetRect.width) {
|
|
40280
40751
|
assistantStyle.right = `${CLOSE_ICON_RADIUS}px`;
|
|
40281
40752
|
}
|
|
40282
40753
|
}
|
|
40283
|
-
return
|
|
40754
|
+
return assistantStyle;
|
|
40755
|
+
}
|
|
40756
|
+
get assistantStyle() {
|
|
40757
|
+
const allProperties = this.assistantStyleProperties;
|
|
40758
|
+
return cssPropertiesToCss({
|
|
40759
|
+
"max-height": allProperties["max-height"],
|
|
40760
|
+
width: allProperties["width"],
|
|
40761
|
+
"min-width": allProperties["min-width"],
|
|
40762
|
+
});
|
|
40763
|
+
}
|
|
40764
|
+
get assistantContainerStyle() {
|
|
40765
|
+
const allProperties = this.assistantStyleProperties;
|
|
40766
|
+
return cssPropertiesToCss({
|
|
40767
|
+
top: allProperties["top"],
|
|
40768
|
+
right: allProperties["right"],
|
|
40769
|
+
transform: allProperties["transform"],
|
|
40770
|
+
});
|
|
40284
40771
|
}
|
|
40285
40772
|
// we can't allow input events to be triggered while we remove and add back the content of the composer in processContent
|
|
40286
40773
|
shouldProcessInputEvents = false;
|
|
@@ -41465,6 +41952,10 @@ chartSidePanelComponentRegistry
|
|
|
41465
41952
|
.add("geo", {
|
|
41466
41953
|
configuration: GeoChartConfigPanel,
|
|
41467
41954
|
design: GeoChartDesignPanel,
|
|
41955
|
+
})
|
|
41956
|
+
.add("funnel", {
|
|
41957
|
+
configuration: GenericChartConfigPanel,
|
|
41958
|
+
design: FunnelChartDesignPanel,
|
|
41468
41959
|
});
|
|
41469
41960
|
|
|
41470
41961
|
css /* scss */ `
|
|
@@ -46761,9 +47252,7 @@ class PivotSidePanelStore extends SpreadsheetStore {
|
|
|
46761
47252
|
pivot: this.draft,
|
|
46762
47253
|
});
|
|
46763
47254
|
this.draft = null;
|
|
46764
|
-
if (!this.alreadyNotified &&
|
|
46765
|
-
!this.isDynamicPivotInViewport() &&
|
|
46766
|
-
this.isStaticPivotInViewport()) {
|
|
47255
|
+
if (!this.alreadyNotified && this.isUpdatedPivotVisibleInViewportOnlyAsStaticPivot()) {
|
|
46767
47256
|
const formulaId = this.getters.getPivotFormulaId(this.pivotId);
|
|
46768
47257
|
const pivotExample = `=PIVOT(${formulaId})`;
|
|
46769
47258
|
this.alreadyNotified = true;
|
|
@@ -46819,26 +47308,33 @@ class PivotSidePanelStore extends SpreadsheetStore {
|
|
|
46819
47308
|
this.applyUpdate();
|
|
46820
47309
|
}
|
|
46821
47310
|
}
|
|
46822
|
-
|
|
46823
|
-
|
|
46824
|
-
|
|
46825
|
-
|
|
46826
|
-
|
|
46827
|
-
|
|
46828
|
-
|
|
46829
|
-
return false;
|
|
46830
|
-
}
|
|
46831
|
-
isStaticPivotInViewport() {
|
|
47311
|
+
/**
|
|
47312
|
+
* @returns true if the updated pivot is visible in the viewport only as a
|
|
47313
|
+
* static pivot and not as a dynamic pivot
|
|
47314
|
+
*/
|
|
47315
|
+
isUpdatedPivotVisibleInViewportOnlyAsStaticPivot() {
|
|
47316
|
+
let staticPivotCount = 0;
|
|
47317
|
+
const updatedPivotFormulaId = this.getters.getPivotFormulaId(this.pivotId);
|
|
46832
47318
|
for (const position of this.getters.getVisibleCellPositions()) {
|
|
46833
47319
|
const cell = this.getters.getCell(position);
|
|
46834
47320
|
if (cell?.isFormula) {
|
|
46835
47321
|
const pivotFunction = getFirstPivotFunction(cell.compiledFormula.tokens);
|
|
46836
|
-
|
|
46837
|
-
|
|
47322
|
+
const pivotFormulaId = pivotFunction?.args[0]?.value;
|
|
47323
|
+
if (pivotFunction && updatedPivotFormulaId === pivotFormulaId.toString()) {
|
|
47324
|
+
if (pivotFunction.functionName === "PIVOT") {
|
|
47325
|
+
// if we have at least one dynamic pivot visible inserted the viewport
|
|
47326
|
+
// we return false
|
|
47327
|
+
return false;
|
|
47328
|
+
}
|
|
47329
|
+
else {
|
|
47330
|
+
staticPivotCount++;
|
|
47331
|
+
}
|
|
46838
47332
|
}
|
|
46839
47333
|
}
|
|
46840
47334
|
}
|
|
46841
|
-
return
|
|
47335
|
+
// we return true if there are only static pivots visible inserted the viewport,
|
|
47336
|
+
// otherwise false
|
|
47337
|
+
return staticPivotCount > 0;
|
|
46842
47338
|
}
|
|
46843
47339
|
addDefaultDateTimeGranularity(fields, definition) {
|
|
46844
47340
|
const { columns, rows } = definition;
|
|
@@ -47014,6 +47510,7 @@ class RemoveDuplicatesPanel extends owl.Component {
|
|
|
47014
47510
|
columns: {},
|
|
47015
47511
|
});
|
|
47016
47512
|
setup() {
|
|
47513
|
+
owl.onMounted(() => this.updateColumns());
|
|
47017
47514
|
owl.onWillUpdateProps(() => this.updateColumns());
|
|
47018
47515
|
}
|
|
47019
47516
|
toggleHasHeader() {
|
|
@@ -52314,13 +52811,13 @@ class Grid extends owl.Component {
|
|
|
52314
52811
|
: this.onComposerContentFocused();
|
|
52315
52812
|
},
|
|
52316
52813
|
Delete: () => {
|
|
52317
|
-
this.env.model.dispatch("
|
|
52814
|
+
this.env.model.dispatch("DELETE_UNFILTERED_CONTENT", {
|
|
52318
52815
|
sheetId: this.env.model.getters.getActiveSheetId(),
|
|
52319
52816
|
target: this.env.model.getters.getSelectedZones(),
|
|
52320
52817
|
});
|
|
52321
52818
|
},
|
|
52322
52819
|
Backspace: () => {
|
|
52323
|
-
this.env.model.dispatch("
|
|
52820
|
+
this.env.model.dispatch("DELETE_UNFILTERED_CONTENT", {
|
|
52324
52821
|
sheetId: this.env.model.getters.getActiveSheetId(),
|
|
52325
52822
|
target: this.env.model.getters.getSelectedZones(),
|
|
52326
52823
|
});
|
|
@@ -52663,11 +53160,8 @@ class Grid extends owl.Component {
|
|
|
52663
53160
|
else {
|
|
52664
53161
|
this.env.model.dispatch("COPY");
|
|
52665
53162
|
}
|
|
52666
|
-
const
|
|
52667
|
-
|
|
52668
|
-
for (const type in content) {
|
|
52669
|
-
clipboardData?.setData(type, content[type]);
|
|
52670
|
-
}
|
|
53163
|
+
const osContent = await this.env.model.getters.getClipboardTextAndImageContent();
|
|
53164
|
+
await this.env.clipboard.write(osContent);
|
|
52671
53165
|
ev.preventDefault();
|
|
52672
53166
|
}
|
|
52673
53167
|
async paste(ev) {
|
|
@@ -52679,21 +53173,27 @@ class Grid extends owl.Component {
|
|
|
52679
53173
|
if (!clipboardData) {
|
|
52680
53174
|
return;
|
|
52681
53175
|
}
|
|
53176
|
+
const image = [...clipboardData?.files]?.find((file) => AllowedImageMimeTypes.includes(file.type));
|
|
52682
53177
|
const osClipboard = {
|
|
52683
53178
|
content: {
|
|
52684
53179
|
[ClipboardMIMEType.PlainText]: clipboardData?.getData(ClipboardMIMEType.PlainText),
|
|
52685
53180
|
[ClipboardMIMEType.Html]: clipboardData?.getData(ClipboardMIMEType.Html),
|
|
52686
53181
|
},
|
|
52687
53182
|
};
|
|
53183
|
+
if (image) {
|
|
53184
|
+
// TODO: support import of multiple images
|
|
53185
|
+
osClipboard.content[image.type] = image;
|
|
53186
|
+
}
|
|
52688
53187
|
const target = this.env.model.getters.getSelectedZones();
|
|
52689
53188
|
const isCutOperation = this.env.model.getters.isCutOperation();
|
|
52690
|
-
const
|
|
52691
|
-
const
|
|
52692
|
-
|
|
53189
|
+
const clipboardId = this.env.model.getters.getClipboardId();
|
|
53190
|
+
const osClipboardContent = parseOSClipboardContent(osClipboard.content);
|
|
53191
|
+
const osClipboardId = osClipboardContent.data?.clipboardId;
|
|
53192
|
+
if (clipboardId === osClipboardId) {
|
|
52693
53193
|
interactivePaste(this.env, target);
|
|
52694
53194
|
}
|
|
52695
53195
|
else {
|
|
52696
|
-
interactivePasteFromOS(this.env, target,
|
|
53196
|
+
await interactivePasteFromOS(this.env, target, osClipboardContent);
|
|
52697
53197
|
}
|
|
52698
53198
|
if (isCutOperation) {
|
|
52699
53199
|
await this.env.clipboard.write({ [ClipboardMIMEType.PlainText]: "" });
|
|
@@ -54339,25 +54839,57 @@ class ConditionalFormatPlugin extends CorePlugin {
|
|
|
54339
54839
|
"getAdaptedCfRanges",
|
|
54340
54840
|
];
|
|
54341
54841
|
cfRules = {};
|
|
54342
|
-
|
|
54343
|
-
for (const
|
|
54344
|
-
|
|
54345
|
-
|
|
54346
|
-
|
|
54347
|
-
|
|
54348
|
-
|
|
54349
|
-
|
|
54350
|
-
|
|
54351
|
-
|
|
54352
|
-
|
|
54353
|
-
|
|
54354
|
-
|
|
54842
|
+
adaptCFFormulas(applyChange) {
|
|
54843
|
+
for (const sheetId in this.cfRules) {
|
|
54844
|
+
for (const rule of this.cfRules[sheetId]) {
|
|
54845
|
+
if (rule.rule.type === "DataBarRule" && rule.rule.rangeValues) {
|
|
54846
|
+
const change = applyChange(rule.rule.rangeValues);
|
|
54847
|
+
switch (change.changeType) {
|
|
54848
|
+
case "REMOVE":
|
|
54849
|
+
this.history.update("cfRules", sheetId, this.cfRules[sheetId].indexOf(rule), "rule",
|
|
54850
|
+
//@ts-expect-error
|
|
54851
|
+
"rangeValues", undefined);
|
|
54852
|
+
break;
|
|
54853
|
+
case "RESIZE":
|
|
54854
|
+
case "MOVE":
|
|
54855
|
+
case "CHANGE":
|
|
54856
|
+
this.history.update("cfRules", sheetId, this.cfRules[sheetId].indexOf(rule), "rule",
|
|
54857
|
+
//@ts-expect-error
|
|
54858
|
+
"rangeValues", change.range);
|
|
54859
|
+
break;
|
|
54860
|
+
}
|
|
54861
|
+
}
|
|
54862
|
+
else if (rule.rule.type === "CellIsRule") {
|
|
54863
|
+
for (let i = 0; i < rule.rule.values.length; i++) {
|
|
54355
54864
|
this.history.update("cfRules", sheetId, this.cfRules[sheetId].indexOf(rule), "rule",
|
|
54356
54865
|
//@ts-expect-error
|
|
54357
|
-
"
|
|
54358
|
-
|
|
54866
|
+
"values", i, this.getters.adaptFormulaStringDependencies(sheetId, rule.rule.values[i], applyChange));
|
|
54867
|
+
}
|
|
54868
|
+
}
|
|
54869
|
+
else if (rule.rule.type === "IconSetRule") {
|
|
54870
|
+
for (const inflectionPoint of ["lowerInflectionPoint", "upperInflectionPoint"]) {
|
|
54871
|
+
if (rule.rule[inflectionPoint].type === "formula") {
|
|
54872
|
+
this.history.update("cfRules", sheetId, this.cfRules[sheetId].indexOf(rule), "rule",
|
|
54873
|
+
//@ts-expect-error
|
|
54874
|
+
inflectionPoint, "value", this.getters.adaptFormulaStringDependencies(sheetId, rule.rule[inflectionPoint].value, applyChange));
|
|
54875
|
+
}
|
|
54876
|
+
}
|
|
54877
|
+
}
|
|
54878
|
+
else if (rule.rule.type === "ColorScaleRule") {
|
|
54879
|
+
for (const value of ["minimum", "maximum", "midpoint"]) {
|
|
54880
|
+
const ruleValue = rule.rule[value];
|
|
54881
|
+
if (ruleValue?.type === "formula" && ruleValue?.value) {
|
|
54882
|
+
this.history.update("cfRules", sheetId, this.cfRules[sheetId].indexOf(rule), "rule",
|
|
54883
|
+
//@ts-expect-error
|
|
54884
|
+
value, "value", this.getters.adaptFormulaStringDependencies(sheetId, ruleValue.value, applyChange));
|
|
54885
|
+
}
|
|
54886
|
+
}
|
|
54359
54887
|
}
|
|
54360
54888
|
}
|
|
54889
|
+
}
|
|
54890
|
+
}
|
|
54891
|
+
adaptCFRanges(sheetId, applyChange) {
|
|
54892
|
+
for (const rule of this.cfRules[sheetId]) {
|
|
54361
54893
|
for (const range of rule.ranges) {
|
|
54362
54894
|
const change = applyChange(range);
|
|
54363
54895
|
switch (change.changeType) {
|
|
@@ -54381,14 +54913,11 @@ class ConditionalFormatPlugin extends CorePlugin {
|
|
|
54381
54913
|
}
|
|
54382
54914
|
}
|
|
54383
54915
|
adaptRanges(applyChange, sheetId) {
|
|
54384
|
-
|
|
54385
|
-
|
|
54386
|
-
|
|
54387
|
-
else {
|
|
54388
|
-
for (const sheetId of Object.keys(this.cfRules)) {
|
|
54389
|
-
this.loopThroughRangesOfSheet(sheetId, applyChange);
|
|
54390
|
-
}
|
|
54916
|
+
const sheetIds = sheetId ? [sheetId] : Object.keys(this.cfRules);
|
|
54917
|
+
for (const sheetId of sheetIds) {
|
|
54918
|
+
this.adaptCFRanges(sheetId, applyChange);
|
|
54391
54919
|
}
|
|
54920
|
+
this.adaptCFFormulas(applyChange);
|
|
54392
54921
|
}
|
|
54393
54922
|
// ---------------------------------------------------------------------------
|
|
54394
54923
|
// Command Handling
|
|
@@ -54782,10 +55311,23 @@ class DataValidationPlugin extends CorePlugin {
|
|
|
54782
55311
|
adaptRanges(applyChange, sheetId) {
|
|
54783
55312
|
const sheetIds = sheetId ? [sheetId] : Object.keys(this.rules);
|
|
54784
55313
|
for (const sheetId of sheetIds) {
|
|
54785
|
-
this.
|
|
55314
|
+
this.adaptDVRanges(sheetId, applyChange);
|
|
55315
|
+
}
|
|
55316
|
+
this.adaptDVFormulas(applyChange);
|
|
55317
|
+
}
|
|
55318
|
+
adaptDVFormulas(applyChange) {
|
|
55319
|
+
for (const sheetId in this.rules) {
|
|
55320
|
+
const rules = this.rules[sheetId];
|
|
55321
|
+
for (let ruleIndex = rules.length - 1; ruleIndex >= 0; ruleIndex--) {
|
|
55322
|
+
const rule = this.rules[sheetId][ruleIndex];
|
|
55323
|
+
for (let valueIndex = 0; valueIndex < rule.criterion.values.length; valueIndex++) {
|
|
55324
|
+
const value = this.getters.adaptFormulaStringDependencies(sheetId, rule.criterion.values[valueIndex], applyChange);
|
|
55325
|
+
this.history.update("rules", sheetId, ruleIndex, "criterion", "values", valueIndex, value);
|
|
55326
|
+
}
|
|
55327
|
+
}
|
|
54786
55328
|
}
|
|
54787
55329
|
}
|
|
54788
|
-
|
|
55330
|
+
adaptDVRanges(sheetId, applyChange) {
|
|
54789
55331
|
const rules = this.rules[sheetId];
|
|
54790
55332
|
for (let ruleIndex = rules.length - 1; ruleIndex >= 0; ruleIndex--) {
|
|
54791
55333
|
const rule = this.rules[sheetId][ruleIndex];
|
|
@@ -60692,6 +61234,7 @@ class EvaluationPlugin extends CoreViewPlugin {
|
|
|
60692
61234
|
exportForExcel(data) {
|
|
60693
61235
|
for (const sheet of data.sheets) {
|
|
60694
61236
|
sheet.cellValues = {};
|
|
61237
|
+
sheet.formulaSpillRanges = {};
|
|
60695
61238
|
}
|
|
60696
61239
|
for (const position of this.evaluator.getEvaluatedPositions()) {
|
|
60697
61240
|
const evaluatedCell = this.evaluator.getEvaluatedCell(position);
|
|
@@ -60703,8 +61246,9 @@ class EvaluationPlugin extends CoreViewPlugin {
|
|
|
60703
61246
|
const exportedSheetData = data.sheets.find((sheet) => sheet.id === position.sheetId);
|
|
60704
61247
|
const formulaCell = this.getCorrespondingFormulaCell(position);
|
|
60705
61248
|
if (formulaCell) {
|
|
61249
|
+
const cell = this.getters.getCell(position);
|
|
60706
61250
|
isExported = isExportableToExcel(formulaCell.compiledFormula.tokens);
|
|
60707
|
-
isFormula = isExported;
|
|
61251
|
+
isFormula = isExported && cell?.content === formulaCell.content;
|
|
60708
61252
|
// If the cell contains a non-exported formula and that is evaluates to
|
|
60709
61253
|
// nothing* ,we don't export it.
|
|
60710
61254
|
// * non-falsy value are relevant and so are 0 and FALSE, which only leaves
|
|
@@ -60727,7 +61271,11 @@ class EvaluationPlugin extends CoreViewPlugin {
|
|
|
60727
61271
|
content = !isExported ? newContent : exportedCellData;
|
|
60728
61272
|
}
|
|
60729
61273
|
exportedSheetData.cells[xc] = content;
|
|
60730
|
-
exportedSheetData.cellValues[xc] = value;
|
|
61274
|
+
exportedSheetData.cellValues[xc] = evaluatedCell.type !== "error" ? value : undefined;
|
|
61275
|
+
const spillZone = this.getSpreadZone(position);
|
|
61276
|
+
if (spillZone) {
|
|
61277
|
+
exportedSheetData.formulaSpillRanges[xc] = this.getters.getRangeString(this.getters.getRangeFromZone(position.sheetId, spillZone), position.sheetId);
|
|
61278
|
+
}
|
|
60731
61279
|
}
|
|
60732
61280
|
}
|
|
60733
61281
|
/**
|
|
@@ -61022,7 +61570,7 @@ class EvaluationChartPlugin extends CoreViewPlugin {
|
|
|
61022
61570
|
}
|
|
61023
61571
|
const type = this.getters.getChartType(figureId);
|
|
61024
61572
|
const runtime = this.getters.getChartRuntime(figureId);
|
|
61025
|
-
const img =
|
|
61573
|
+
const img = chartToImageUrl(runtime, figure, type);
|
|
61026
61574
|
if (img) {
|
|
61027
61575
|
sheet.images.push({
|
|
61028
61576
|
...figure,
|
|
@@ -62955,7 +63503,7 @@ class AutofillPlugin extends UIPlugin {
|
|
|
62955
63503
|
getRule(cell, cells) {
|
|
62956
63504
|
const rules = autofillRulesRegistry.getAll().sort((a, b) => a.sequence - b.sequence);
|
|
62957
63505
|
const rule = rules.find((rule) => rule.condition(cell, cells));
|
|
62958
|
-
return rule && rule.generateRule(cell, cells);
|
|
63506
|
+
return rule && this.direction && rule.generateRule(cell, cells, this.direction);
|
|
62959
63507
|
}
|
|
62960
63508
|
/**
|
|
62961
63509
|
* Create the generator to be able to autofill the next cells.
|
|
@@ -65110,6 +65658,17 @@ class SheetUIPlugin extends UIPlugin {
|
|
|
65110
65658
|
sheetId: cmd.sheetId,
|
|
65111
65659
|
});
|
|
65112
65660
|
break;
|
|
65661
|
+
case "DELETE_UNFILTERED_CONTENT":
|
|
65662
|
+
const newTarget = [];
|
|
65663
|
+
for (const target of cmd.target) {
|
|
65664
|
+
const nonFilteredRows = range(target.top, target.bottom + 1).filter((row) => !this.getters.isRowFiltered(cmd.sheetId, row));
|
|
65665
|
+
const consecutiveRows = groupConsecutive(nonFilteredRows);
|
|
65666
|
+
for (const group of consecutiveRows) {
|
|
65667
|
+
newTarget.push({ ...target, top: group[0], bottom: group[group.length - 1] });
|
|
65668
|
+
}
|
|
65669
|
+
}
|
|
65670
|
+
this.dispatch("DELETE_CONTENT", { sheetId: cmd.sheetId, target: newTarget });
|
|
65671
|
+
break;
|
|
65113
65672
|
}
|
|
65114
65673
|
}
|
|
65115
65674
|
// ---------------------------------------------------------------------------
|
|
@@ -65749,6 +66308,7 @@ repeatLocalCommandTransformRegistry.add("AUTORESIZE_ROWS", repeatAutoResizeComma
|
|
|
65749
66308
|
repeatLocalCommandTransformRegistry.add("SORT_CELLS", repeatSortCellsCommand);
|
|
65750
66309
|
repeatLocalCommandTransformRegistry.add("SUM_SELECTION", genericRepeat);
|
|
65751
66310
|
repeatLocalCommandTransformRegistry.add("SET_DECIMAL", genericRepeat);
|
|
66311
|
+
repeatLocalCommandTransformRegistry.add("DELETE_UNFILTERED_CONTENT", genericRepeat);
|
|
65752
66312
|
function genericRepeat(getters, command) {
|
|
65753
66313
|
let transformedCommand = deepCopy(command);
|
|
65754
66314
|
for (const repeatTransform of genericRepeatsTransforms) {
|
|
@@ -66187,6 +66747,7 @@ class TableResizeUI extends UIPlugin {
|
|
|
66187
66747
|
}
|
|
66188
66748
|
}
|
|
66189
66749
|
|
|
66750
|
+
const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5 MB
|
|
66190
66751
|
/**
|
|
66191
66752
|
* Clipboard Plugin
|
|
66192
66753
|
*
|
|
@@ -66196,7 +66757,7 @@ class TableResizeUI extends UIPlugin {
|
|
|
66196
66757
|
class ClipboardPlugin extends UIPlugin {
|
|
66197
66758
|
static layers = ["Clipboard"];
|
|
66198
66759
|
static getters = [
|
|
66199
|
-
"
|
|
66760
|
+
"getClipboardTextAndImageContent",
|
|
66200
66761
|
"getClipboardId",
|
|
66201
66762
|
"getClipboardTextContent",
|
|
66202
66763
|
"isCutOperation",
|
|
@@ -66206,6 +66767,13 @@ class ClipboardPlugin extends UIPlugin {
|
|
|
66206
66767
|
copiedData;
|
|
66207
66768
|
_isCutOperation = false;
|
|
66208
66769
|
clipboardId = new UuidGenerator().uuidv4();
|
|
66770
|
+
fileStore;
|
|
66771
|
+
uuidGenerator;
|
|
66772
|
+
constructor(config) {
|
|
66773
|
+
super(config);
|
|
66774
|
+
this.fileStore = config.external.fileStore;
|
|
66775
|
+
this.uuidGenerator = new UuidGenerator();
|
|
66776
|
+
}
|
|
66209
66777
|
// ---------------------------------------------------------------------------
|
|
66210
66778
|
// Command Handling
|
|
66211
66779
|
// ---------------------------------------------------------------------------
|
|
@@ -66268,9 +66836,29 @@ class ClipboardPlugin extends UIPlugin {
|
|
|
66268
66836
|
break;
|
|
66269
66837
|
case "PASTE_FROM_OS_CLIPBOARD": {
|
|
66270
66838
|
this._isCutOperation = false;
|
|
66271
|
-
|
|
66272
|
-
|
|
66273
|
-
|
|
66839
|
+
const htmlData = cmd.clipboardContent.data;
|
|
66840
|
+
// TODO: support multiple image import
|
|
66841
|
+
if (cmd.clipboardContent.imageData) {
|
|
66842
|
+
const sheetId = this.getters.getActiveSheetId();
|
|
66843
|
+
const figureId = this.uuidGenerator.uuidv4();
|
|
66844
|
+
const definition = cmd.clipboardContent.imageData;
|
|
66845
|
+
// compute position based on current selection
|
|
66846
|
+
const { x, y } = this.getters.getVisibleRectWithoutHeaders(cmd.target[0]);
|
|
66847
|
+
const size = getMaxFigureSize(this.getters, definition.size);
|
|
66848
|
+
this.dispatch("CREATE_IMAGE", {
|
|
66849
|
+
definition,
|
|
66850
|
+
size,
|
|
66851
|
+
position: { x, y },
|
|
66852
|
+
sheetId,
|
|
66853
|
+
figureId,
|
|
66854
|
+
});
|
|
66855
|
+
}
|
|
66856
|
+
if (htmlData) {
|
|
66857
|
+
this.copiedData = htmlData;
|
|
66858
|
+
}
|
|
66859
|
+
else {
|
|
66860
|
+
this.copiedData = this.convertTextToClipboardData(cmd.clipboardContent.text ?? "");
|
|
66861
|
+
}
|
|
66274
66862
|
const pasteOption = cmd.pasteOption;
|
|
66275
66863
|
this.paste(cmd.target, this.copiedData, {
|
|
66276
66864
|
pasteOption,
|
|
@@ -66278,6 +66866,7 @@ class ClipboardPlugin extends UIPlugin {
|
|
|
66278
66866
|
isCutOperation: false,
|
|
66279
66867
|
});
|
|
66280
66868
|
this.status = "invisible";
|
|
66869
|
+
this.copiedData = undefined;
|
|
66281
66870
|
break;
|
|
66282
66871
|
}
|
|
66283
66872
|
case "PASTE": {
|
|
@@ -66570,11 +67159,17 @@ class ClipboardPlugin extends UIPlugin {
|
|
|
66570
67159
|
getClipboardId() {
|
|
66571
67160
|
return this.clipboardId;
|
|
66572
67161
|
}
|
|
66573
|
-
|
|
66574
|
-
|
|
67162
|
+
async getClipboardTextAndImageContent() {
|
|
67163
|
+
const file = await this.getImageContent();
|
|
67164
|
+
const mime = file?.type;
|
|
67165
|
+
const content = {
|
|
66575
67166
|
[ClipboardMIMEType.PlainText]: this.getPlainTextContent(),
|
|
66576
|
-
[ClipboardMIMEType.Html]: this.getHTMLContent(),
|
|
67167
|
+
[ClipboardMIMEType.Html]: await this.getHTMLContent(),
|
|
66577
67168
|
};
|
|
67169
|
+
if (mime && file) {
|
|
67170
|
+
content[mime] = file;
|
|
67171
|
+
}
|
|
67172
|
+
return content;
|
|
66578
67173
|
}
|
|
66579
67174
|
getSheetData() {
|
|
66580
67175
|
const data = {
|
|
@@ -66603,11 +67198,24 @@ class ClipboardPlugin extends UIPlugin {
|
|
|
66603
67198
|
})
|
|
66604
67199
|
.join("\n") || "\t");
|
|
66605
67200
|
}
|
|
66606
|
-
getHTMLContent() {
|
|
67201
|
+
async getHTMLContent() {
|
|
66607
67202
|
let innerHTML = "";
|
|
66608
67203
|
const cells = this.copiedData?.cells;
|
|
66609
67204
|
if (!cells) {
|
|
66610
|
-
|
|
67205
|
+
if (this.copiedData?.figureId) {
|
|
67206
|
+
const figureId = this.copiedData.figureId;
|
|
67207
|
+
const figureSheetId = this.getters.getFigureSheetId(figureId);
|
|
67208
|
+
const figure = this.getters.getFigure(figureSheetId, figureId);
|
|
67209
|
+
if (figure.tag == "image") {
|
|
67210
|
+
innerHTML = await this.craftImageHTML(figureId);
|
|
67211
|
+
}
|
|
67212
|
+
else {
|
|
67213
|
+
innerHTML = "\t";
|
|
67214
|
+
}
|
|
67215
|
+
}
|
|
67216
|
+
else {
|
|
67217
|
+
innerHTML = "\t";
|
|
67218
|
+
}
|
|
66611
67219
|
}
|
|
66612
67220
|
else if (cells.length === 1 && cells[0].length === 1) {
|
|
66613
67221
|
innerHTML = `${this.getters.getCellText(cells[0][0].position)}`;
|
|
@@ -66635,6 +67243,62 @@ class ClipboardPlugin extends UIPlugin {
|
|
|
66635
67243
|
const serializedData = JSON.stringify(this.getSheetData());
|
|
66636
67244
|
return `<div data-osheet-clipboard='${xmlEscape(serializedData)}'>${innerHTML}</div>`;
|
|
66637
67245
|
}
|
|
67246
|
+
readFileAsDataURL(blob) {
|
|
67247
|
+
return new Promise((resolve) => {
|
|
67248
|
+
const reader = new FileReader();
|
|
67249
|
+
reader.onload = () => resolve(reader.result);
|
|
67250
|
+
reader.readAsDataURL(blob);
|
|
67251
|
+
});
|
|
67252
|
+
}
|
|
67253
|
+
async craftImageHTML(figureId) {
|
|
67254
|
+
if (!this.fileStore) {
|
|
67255
|
+
return "\t";
|
|
67256
|
+
}
|
|
67257
|
+
const imageUrl = this.getters.getImage(figureId).path;
|
|
67258
|
+
const file = (await this.fileStore?.getFile(imageUrl)) || null;
|
|
67259
|
+
if (file) {
|
|
67260
|
+
const imageUrl = (await this.readFileAsDataURL(file));
|
|
67261
|
+
return `<img src="${xmlEscape(imageUrl)}" />`;
|
|
67262
|
+
}
|
|
67263
|
+
else {
|
|
67264
|
+
return "\t";
|
|
67265
|
+
}
|
|
67266
|
+
}
|
|
67267
|
+
async getImageContent() {
|
|
67268
|
+
const figureId = this.copiedData?.figureId;
|
|
67269
|
+
if (!figureId) {
|
|
67270
|
+
return;
|
|
67271
|
+
}
|
|
67272
|
+
const figureSheetId = this.getters.getFigureSheetId(figureId);
|
|
67273
|
+
const figure = this.getters.getFigure(figureSheetId, figureId);
|
|
67274
|
+
let file;
|
|
67275
|
+
if (figure.tag == "image") {
|
|
67276
|
+
if (!this.fileStore) {
|
|
67277
|
+
return;
|
|
67278
|
+
}
|
|
67279
|
+
const imageUrl = this.getters.getImage(figureId).path;
|
|
67280
|
+
file = await this.fileStore?.getFile(imageUrl);
|
|
67281
|
+
// we can only write on image/png format in the clipboard
|
|
67282
|
+
// So we convert the image to png if it's not already
|
|
67283
|
+
if (file.type !== "image/png") {
|
|
67284
|
+
if (file.size > MAX_FILE_SIZE) {
|
|
67285
|
+
this.ui.notifyUI({
|
|
67286
|
+
text: _t(`The file you are trying to copy is too large (>%sMB).\nIt will not be added to your OS clipboard.\nYou can download it directly instead.`, Math.round(MAX_FILE_SIZE / (1024 * 1024))),
|
|
67287
|
+
sticky: false,
|
|
67288
|
+
type: "warning",
|
|
67289
|
+
});
|
|
67290
|
+
return undefined;
|
|
67291
|
+
}
|
|
67292
|
+
file = await convertImageToPng(imageUrl);
|
|
67293
|
+
}
|
|
67294
|
+
}
|
|
67295
|
+
if (!file) {
|
|
67296
|
+
return undefined;
|
|
67297
|
+
}
|
|
67298
|
+
else {
|
|
67299
|
+
return file instanceof File ? file : new File([file], "image.png", { type: "image/png" });
|
|
67300
|
+
}
|
|
67301
|
+
}
|
|
66638
67302
|
isCutOperation() {
|
|
66639
67303
|
return this._isCutOperation ?? false;
|
|
66640
67304
|
}
|
|
@@ -68709,12 +69373,17 @@ class ImageProvider {
|
|
|
68709
69373
|
this.fileStore = fileStore;
|
|
68710
69374
|
}
|
|
68711
69375
|
async requestImage() {
|
|
68712
|
-
const file = await this.
|
|
69376
|
+
const file = await this.userImageUpload();
|
|
69377
|
+
const path = await this.fileStore.upload(file);
|
|
69378
|
+
const size = await this.getImageOriginalSize(path);
|
|
69379
|
+
return { path, size, mimetype: file.type };
|
|
69380
|
+
}
|
|
69381
|
+
async uploadFile(file) {
|
|
68713
69382
|
const path = await this.fileStore.upload(file);
|
|
68714
69383
|
const size = await this.getImageOriginalSize(path);
|
|
68715
69384
|
return { path, size, mimetype: file.type };
|
|
68716
69385
|
}
|
|
68717
|
-
|
|
69386
|
+
userImageUpload() {
|
|
68718
69387
|
return new Promise((resolve, reject) => {
|
|
68719
69388
|
const input = document.createElement("input");
|
|
68720
69389
|
input.setAttribute("type", "file");
|
|
@@ -68733,12 +69402,12 @@ class ImageProvider {
|
|
|
68733
69402
|
getImageOriginalSize(path) {
|
|
68734
69403
|
return new Promise((resolve, reject) => {
|
|
68735
69404
|
const image = new Image();
|
|
68736
|
-
image.src = path;
|
|
68737
69405
|
image.addEventListener("load", () => {
|
|
68738
69406
|
const size = { width: image.width, height: image.height };
|
|
68739
69407
|
resolve(size);
|
|
68740
69408
|
});
|
|
68741
69409
|
image.addEventListener("error", reject);
|
|
69410
|
+
image.src = path;
|
|
68742
69411
|
});
|
|
68743
69412
|
}
|
|
68744
69413
|
}
|
|
@@ -70116,6 +70785,126 @@ class SidePanel extends owl.Component {
|
|
|
70116
70785
|
}
|
|
70117
70786
|
}
|
|
70118
70787
|
|
|
70788
|
+
const COMPOSER_MAX_HEIGHT = 100;
|
|
70789
|
+
/* svg free of use from https://uxwing.com/formula-fx-icon/ */
|
|
70790
|
+
const FX_SVG = /*xml*/ `
|
|
70791
|
+
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 121.8 122.9' width='16' height='16' focusable='false'>
|
|
70792
|
+
<path d='m28 34-4 5v2h10l-6 40c-4 22-6 28-7 30-2 2-3 3-5 3-3 0-7-2-9-4H4c-2 2-4 4-4 7s4 6 8 6 9-2 15-8c8-7 13-17 18-39l7-35 13-1 3-6H49c4-23 7-27 11-27 2 0 5 2 8 6h4c1-1 4-4 4-7 0-2-3-6-9-6-5 0-13 4-20 10-6 7-9 14-11 24h-8zm41 16c4-5 7-7 8-7s2 1 5 9l3 12c-7 11-12 17-16 17l-3-1-2-1c-3 0-6 3-6 7s3 7 7 7c6 0 12-6 22-23l3 10c3 9 6 13 10 13 5 0 11-4 18-15l-3-4c-4 6-7 8-8 8-2 0-4-3-6-10l-5-15 8-10 6-4 3 1 3 2c2 0 6-3 6-7s-2-7-6-7c-6 0-11 5-21 20l-2-6c-3-9-5-14-9-14-5 0-12 6-18 15l3 3z' fill='#BDBDBD'/>
|
|
70793
|
+
</svg>
|
|
70794
|
+
`;
|
|
70795
|
+
css /* scss */ `
|
|
70796
|
+
.o-topbar-composer-container {
|
|
70797
|
+
height: ${TOPBAR_TOOLBAR_HEIGHT}px;
|
|
70798
|
+
}
|
|
70799
|
+
|
|
70800
|
+
.o-topbar-composer {
|
|
70801
|
+
height: fit-content;
|
|
70802
|
+
margin-top: -1px;
|
|
70803
|
+
margin-bottom: -1px;
|
|
70804
|
+
border: 1px solid;
|
|
70805
|
+
font-family: ${DEFAULT_FONT};
|
|
70806
|
+
|
|
70807
|
+
.o-composer:empty:not(:focus):not(.active)::before {
|
|
70808
|
+
content: url("data:image/svg+xml,${encodeURIComponent(FX_SVG)}");
|
|
70809
|
+
position: relative;
|
|
70810
|
+
top: 20%;
|
|
70811
|
+
}
|
|
70812
|
+
}
|
|
70813
|
+
|
|
70814
|
+
.user-select-text {
|
|
70815
|
+
user-select: text;
|
|
70816
|
+
}
|
|
70817
|
+
`;
|
|
70818
|
+
class TopBarComposer extends owl.Component {
|
|
70819
|
+
static template = "o-spreadsheet-TopBarComposer";
|
|
70820
|
+
static props = {};
|
|
70821
|
+
static components = { Composer };
|
|
70822
|
+
composerFocusStore;
|
|
70823
|
+
composerStore;
|
|
70824
|
+
composerInterface;
|
|
70825
|
+
setup() {
|
|
70826
|
+
this.composerFocusStore = useStore(ComposerFocusStore);
|
|
70827
|
+
const composerStore = useStore(CellComposerStore);
|
|
70828
|
+
this.composerStore = composerStore;
|
|
70829
|
+
this.composerInterface = {
|
|
70830
|
+
id: "topbarComposer",
|
|
70831
|
+
get editionMode() {
|
|
70832
|
+
return composerStore.editionMode;
|
|
70833
|
+
},
|
|
70834
|
+
startEdition: this.composerStore.startEdition,
|
|
70835
|
+
setCurrentContent: this.composerStore.setCurrentContent,
|
|
70836
|
+
stopEdition: this.composerStore.stopEdition,
|
|
70837
|
+
};
|
|
70838
|
+
}
|
|
70839
|
+
get focus() {
|
|
70840
|
+
return this.composerFocusStore.activeComposer === this.composerInterface
|
|
70841
|
+
? this.composerFocusStore.focusMode
|
|
70842
|
+
: "inactive";
|
|
70843
|
+
}
|
|
70844
|
+
get composerStyle() {
|
|
70845
|
+
const style = {
|
|
70846
|
+
padding: "5px 0px 5px 8px",
|
|
70847
|
+
"max-height": `${COMPOSER_MAX_HEIGHT}px`,
|
|
70848
|
+
"line-height": "24px",
|
|
70849
|
+
};
|
|
70850
|
+
style.height = this.focus === "inactive" ? `${TOPBAR_TOOLBAR_HEIGHT}px` : "fit-content";
|
|
70851
|
+
return cssPropertiesToCss(style);
|
|
70852
|
+
}
|
|
70853
|
+
get containerStyle() {
|
|
70854
|
+
if (this.focus === "inactive") {
|
|
70855
|
+
return cssPropertiesToCss({
|
|
70856
|
+
"border-color": SEPARATOR_COLOR,
|
|
70857
|
+
"border-right": "none",
|
|
70858
|
+
});
|
|
70859
|
+
}
|
|
70860
|
+
return cssPropertiesToCss({
|
|
70861
|
+
"border-color": SELECTION_BORDER_COLOR,
|
|
70862
|
+
"z-index": String(ComponentsImportance.TopBarComposer),
|
|
70863
|
+
});
|
|
70864
|
+
}
|
|
70865
|
+
onFocus(selection) {
|
|
70866
|
+
this.composerFocusStore.focusComposer(this.composerInterface, { selection });
|
|
70867
|
+
}
|
|
70868
|
+
}
|
|
70869
|
+
|
|
70870
|
+
/**
|
|
70871
|
+
* This store is used to manage the dropdown that is currently
|
|
70872
|
+
* opened after clicking an item on the toolbar.
|
|
70873
|
+
* It can only have one displayed at a time.
|
|
70874
|
+
*
|
|
70875
|
+
*/
|
|
70876
|
+
class TopBarToolStore {
|
|
70877
|
+
mutators = ["closeDropdowns", "openDropdown"];
|
|
70878
|
+
_currentDropdown = null;
|
|
70879
|
+
closeDropdowns() {
|
|
70880
|
+
this._currentDropdown = null;
|
|
70881
|
+
}
|
|
70882
|
+
openDropdown(dropdownComponent) {
|
|
70883
|
+
this._currentDropdown = dropdownComponent;
|
|
70884
|
+
}
|
|
70885
|
+
get currentDropdown() {
|
|
70886
|
+
return this._currentDropdown;
|
|
70887
|
+
}
|
|
70888
|
+
}
|
|
70889
|
+
|
|
70890
|
+
class ToolBarRegistry {
|
|
70891
|
+
content = {};
|
|
70892
|
+
add(key) {
|
|
70893
|
+
this.content[key] = [];
|
|
70894
|
+
return this;
|
|
70895
|
+
}
|
|
70896
|
+
addChild(key, value) {
|
|
70897
|
+
this.content[key].push(value);
|
|
70898
|
+
return this;
|
|
70899
|
+
}
|
|
70900
|
+
getEntries(id) {
|
|
70901
|
+
return this.content[id].sort((a, b) => a.sequence - b.sequence);
|
|
70902
|
+
}
|
|
70903
|
+
getCategories() {
|
|
70904
|
+
return Object.keys(this.content);
|
|
70905
|
+
}
|
|
70906
|
+
}
|
|
70907
|
+
|
|
70119
70908
|
css /* scss */ `
|
|
70120
70909
|
.o-menu-item-button {
|
|
70121
70910
|
display: flex;
|
|
@@ -70183,6 +70972,25 @@ class ActionButton extends owl.Component {
|
|
|
70183
70972
|
}
|
|
70184
70973
|
}
|
|
70185
70974
|
|
|
70975
|
+
function useToolBarDropdownStore() {
|
|
70976
|
+
const component = owl.useComponent();
|
|
70977
|
+
const topbarStore = useStore(TopBarToolStore);
|
|
70978
|
+
owl.onWillUnmount(() => {
|
|
70979
|
+
if (component === topbarStore.currentDropdown) {
|
|
70980
|
+
topbarStore.closeDropdowns();
|
|
70981
|
+
}
|
|
70982
|
+
});
|
|
70983
|
+
return {
|
|
70984
|
+
closeDropdowns: () => topbarStore.closeDropdowns(),
|
|
70985
|
+
openDropdown: () => {
|
|
70986
|
+
topbarStore.openDropdown(component);
|
|
70987
|
+
},
|
|
70988
|
+
get isActive() {
|
|
70989
|
+
return topbarStore.currentDropdown === component;
|
|
70990
|
+
},
|
|
70991
|
+
};
|
|
70992
|
+
}
|
|
70993
|
+
|
|
70186
70994
|
/**
|
|
70187
70995
|
* List the available borders positions and the corresponding icons.
|
|
70188
70996
|
* The structure of this array is defined to match the order/lines we want
|
|
@@ -70352,13 +71160,12 @@ class BorderEditor extends owl.Component {
|
|
|
70352
71160
|
class BorderEditorWidget extends owl.Component {
|
|
70353
71161
|
static template = "o-spreadsheet-BorderEditorWidget";
|
|
70354
71162
|
static props = {
|
|
70355
|
-
toggleBorderEditor: Function,
|
|
70356
|
-
showBorderEditor: Boolean,
|
|
70357
71163
|
disabled: { type: Boolean, optional: true },
|
|
70358
71164
|
dropdownMaxHeight: { type: Number, optional: true },
|
|
70359
71165
|
class: { type: String, optional: true },
|
|
70360
71166
|
};
|
|
70361
71167
|
static components = { BorderEditor };
|
|
71168
|
+
topBarToolStore;
|
|
70362
71169
|
borderEditorButtonRef = owl.useRef("borderEditorButton");
|
|
70363
71170
|
state = owl.useState({
|
|
70364
71171
|
currentColor: DEFAULT_BORDER_DESC.color,
|
|
@@ -70366,12 +71173,16 @@ class BorderEditorWidget extends owl.Component {
|
|
|
70366
71173
|
currentPosition: undefined,
|
|
70367
71174
|
});
|
|
70368
71175
|
setup() {
|
|
70369
|
-
|
|
70370
|
-
|
|
71176
|
+
this.topBarToolStore = useToolBarDropdownStore();
|
|
71177
|
+
owl.onWillUpdateProps(() => {
|
|
71178
|
+
if (!this.isActive) {
|
|
70371
71179
|
this.state.currentPosition = undefined;
|
|
70372
71180
|
}
|
|
70373
71181
|
});
|
|
70374
71182
|
}
|
|
71183
|
+
get dropdownMaxHeight() {
|
|
71184
|
+
return this.env.model.getters.getSheetViewDimension().height;
|
|
71185
|
+
}
|
|
70375
71186
|
get borderEditorAnchorRect() {
|
|
70376
71187
|
const button = this.borderEditorButtonRef.el;
|
|
70377
71188
|
const buttonRect = button.getBoundingClientRect();
|
|
@@ -70394,6 +71205,17 @@ class BorderEditorWidget extends owl.Component {
|
|
|
70394
71205
|
this.state.currentStyle = style;
|
|
70395
71206
|
this.updateBorder();
|
|
70396
71207
|
}
|
|
71208
|
+
get isActive() {
|
|
71209
|
+
return this.topBarToolStore.isActive;
|
|
71210
|
+
}
|
|
71211
|
+
toggleBorderEditor() {
|
|
71212
|
+
if (this.isActive) {
|
|
71213
|
+
this.topBarToolStore.closeDropdowns();
|
|
71214
|
+
}
|
|
71215
|
+
else {
|
|
71216
|
+
this.topBarToolStore.openDropdown();
|
|
71217
|
+
}
|
|
71218
|
+
}
|
|
70397
71219
|
updateBorder() {
|
|
70398
71220
|
if (this.state.currentPosition === undefined) {
|
|
70399
71221
|
return;
|
|
@@ -70410,93 +71232,42 @@ class BorderEditorWidget extends owl.Component {
|
|
|
70410
71232
|
}
|
|
70411
71233
|
}
|
|
70412
71234
|
|
|
70413
|
-
|
|
70414
|
-
|
|
70415
|
-
|
|
70416
|
-
|
|
70417
|
-
|
|
70418
|
-
|
|
70419
|
-
`;
|
|
70420
|
-
css /* scss */ `
|
|
70421
|
-
.o-topbar-composer-container {
|
|
70422
|
-
height: ${TOPBAR_TOOLBAR_HEIGHT}px;
|
|
70423
|
-
}
|
|
70424
|
-
|
|
70425
|
-
.o-topbar-composer {
|
|
70426
|
-
height: fit-content;
|
|
70427
|
-
margin-top: -1px;
|
|
70428
|
-
margin-bottom: -1px;
|
|
70429
|
-
border: 1px solid;
|
|
70430
|
-
font-family: ${DEFAULT_FONT};
|
|
70431
|
-
|
|
70432
|
-
.o-composer:empty:not(:focus):not(.active)::before {
|
|
70433
|
-
content: url("data:image/svg+xml,${encodeURIComponent(FX_SVG)}");
|
|
70434
|
-
position: relative;
|
|
70435
|
-
top: 20%;
|
|
70436
|
-
}
|
|
70437
|
-
}
|
|
70438
|
-
|
|
70439
|
-
.user-select-text {
|
|
70440
|
-
user-select: text;
|
|
70441
|
-
}
|
|
70442
|
-
`;
|
|
70443
|
-
class TopBarComposer extends owl.Component {
|
|
70444
|
-
static template = "o-spreadsheet-TopBarComposer";
|
|
70445
|
-
static props = {};
|
|
70446
|
-
static components = { Composer };
|
|
70447
|
-
composerFocusStore;
|
|
70448
|
-
composerStore;
|
|
70449
|
-
composerInterface;
|
|
71235
|
+
class PaintFormatButton extends owl.Component {
|
|
71236
|
+
static template = "o-spreadsheet-PaintFormatButton";
|
|
71237
|
+
static props = {
|
|
71238
|
+
class: { type: String, optional: true },
|
|
71239
|
+
};
|
|
71240
|
+
paintFormatStore;
|
|
70450
71241
|
setup() {
|
|
70451
|
-
this.
|
|
70452
|
-
const composerStore = useStore(CellComposerStore);
|
|
70453
|
-
this.composerStore = composerStore;
|
|
70454
|
-
this.composerInterface = {
|
|
70455
|
-
id: "topbarComposer",
|
|
70456
|
-
get editionMode() {
|
|
70457
|
-
return composerStore.editionMode;
|
|
70458
|
-
},
|
|
70459
|
-
startEdition: this.composerStore.startEdition,
|
|
70460
|
-
setCurrentContent: this.composerStore.setCurrentContent,
|
|
70461
|
-
stopEdition: this.composerStore.stopEdition,
|
|
70462
|
-
};
|
|
71242
|
+
this.paintFormatStore = useStore(PaintFormatStore);
|
|
70463
71243
|
}
|
|
70464
|
-
get
|
|
70465
|
-
return this.
|
|
70466
|
-
? this.composerFocusStore.focusMode
|
|
70467
|
-
: "inactive";
|
|
71244
|
+
get isActive() {
|
|
71245
|
+
return this.paintFormatStore.isActive;
|
|
70468
71246
|
}
|
|
70469
|
-
|
|
70470
|
-
|
|
70471
|
-
padding: "5px 0px 5px 8px",
|
|
70472
|
-
"max-height": `${COMPOSER_MAX_HEIGHT}px`,
|
|
70473
|
-
"line-height": "24px",
|
|
70474
|
-
};
|
|
70475
|
-
style.height = this.focus === "inactive" ? `${TOPBAR_TOOLBAR_HEIGHT}px` : "fit-content";
|
|
70476
|
-
return cssPropertiesToCss(style);
|
|
71247
|
+
onDblClick() {
|
|
71248
|
+
this.paintFormatStore.activate({ persistent: true });
|
|
70477
71249
|
}
|
|
70478
|
-
|
|
70479
|
-
if (this.
|
|
70480
|
-
|
|
70481
|
-
|
|
70482
|
-
|
|
70483
|
-
});
|
|
71250
|
+
togglePaintFormat() {
|
|
71251
|
+
if (this.isActive) {
|
|
71252
|
+
this.paintFormatStore.cancel();
|
|
71253
|
+
}
|
|
71254
|
+
else {
|
|
71255
|
+
this.paintFormatStore.activate({ persistent: false });
|
|
70484
71256
|
}
|
|
70485
|
-
return cssPropertiesToCss({
|
|
70486
|
-
"border-color": SELECTION_BORDER_COLOR,
|
|
70487
|
-
"z-index": String(ComponentsImportance.TopBarComposer),
|
|
70488
|
-
});
|
|
70489
|
-
}
|
|
70490
|
-
onFocus(selection) {
|
|
70491
|
-
this.composerFocusStore.focusComposer(this.composerInterface, { selection });
|
|
70492
71257
|
}
|
|
70493
71258
|
}
|
|
70494
71259
|
|
|
70495
71260
|
class TableDropdownButton extends owl.Component {
|
|
70496
71261
|
static template = "o-spreadsheet-TableDropdownButton";
|
|
70497
71262
|
static components = { TableStylesPopover, ActionButton };
|
|
70498
|
-
static props = {
|
|
71263
|
+
static props = {
|
|
71264
|
+
class: { type: String, optional: true },
|
|
71265
|
+
};
|
|
71266
|
+
topBarToolStore;
|
|
70499
71267
|
state = owl.useState({ popoverProps: undefined });
|
|
71268
|
+
setup() {
|
|
71269
|
+
this.topBarToolStore = useToolBarDropdownStore();
|
|
71270
|
+
}
|
|
70500
71271
|
onStylePicked(styleId) {
|
|
70501
71272
|
const sheetId = this.env.model.getters.getActiveSheetId();
|
|
70502
71273
|
const tableConfig = { ...this.tableConfig, styleId };
|
|
@@ -70512,12 +71283,13 @@ class TableDropdownButton extends owl.Component {
|
|
|
70512
71283
|
return;
|
|
70513
71284
|
}
|
|
70514
71285
|
if (this.env.model.getters.getFirstTableInSelection()) {
|
|
71286
|
+
this.topBarToolStore.closeDropdowns();
|
|
70515
71287
|
this.env.toggleSidePanel("TableSidePanel", {});
|
|
70516
71288
|
return;
|
|
70517
71289
|
}
|
|
70518
|
-
// Open the popover
|
|
70519
71290
|
const target = ev.currentTarget;
|
|
70520
|
-
const {
|
|
71291
|
+
const { left, bottom } = target.getBoundingClientRect();
|
|
71292
|
+
this.topBarToolStore.openDropdown();
|
|
70521
71293
|
this.state.popoverProps = {
|
|
70522
71294
|
anchorRect: { x: left, y: bottom, width: 0, height: 0 },
|
|
70523
71295
|
positioning: "BottomLeft",
|
|
@@ -70529,8 +71301,8 @@ class TableDropdownButton extends owl.Component {
|
|
|
70529
71301
|
}
|
|
70530
71302
|
get action() {
|
|
70531
71303
|
return {
|
|
70532
|
-
name: (env) =>
|
|
70533
|
-
icon: (env) =>
|
|
71304
|
+
name: (env) => env.model.getters.getFirstTableInSelection() ? _t("Edit table") : _t("Insert table"),
|
|
71305
|
+
icon: (env) => env.model.getters.getFirstTableInSelection()
|
|
70534
71306
|
? "o-spreadsheet-Icon.EDIT_TABLE"
|
|
70535
71307
|
: "o-spreadsheet-Icon.PAINT_TABLE",
|
|
70536
71308
|
};
|
|
@@ -70540,35 +71312,347 @@ class TableDropdownButton extends owl.Component {
|
|
|
70540
71312
|
}
|
|
70541
71313
|
}
|
|
70542
71314
|
|
|
70543
|
-
class
|
|
70544
|
-
static
|
|
71315
|
+
class TopBarColorEditor extends owl.Component {
|
|
71316
|
+
static components = { ColorPickerWidget };
|
|
71317
|
+
static props = { class: String, style: String, icon: String, title: String };
|
|
71318
|
+
static template = "o-spreadsheet-ColorEditor";
|
|
71319
|
+
topBarToolStore;
|
|
71320
|
+
state = owl.useState({
|
|
71321
|
+
isOpen: false,
|
|
71322
|
+
});
|
|
71323
|
+
setup() {
|
|
71324
|
+
this.topBarToolStore = useToolBarDropdownStore();
|
|
71325
|
+
}
|
|
71326
|
+
get currentColor() {
|
|
71327
|
+
return (this.env.model.getters.getCurrentStyle()[this.props.style] ||
|
|
71328
|
+
(this.props.style === "textColor" ? "#000000" : "#ffffff"));
|
|
71329
|
+
}
|
|
71330
|
+
setColor(color) {
|
|
71331
|
+
setStyle(this.env, { [this.props.style]: color });
|
|
71332
|
+
this.state.isOpen = false;
|
|
71333
|
+
}
|
|
71334
|
+
get dropdownMaxHeight() {
|
|
71335
|
+
return this.env.model.getters.getSheetViewDimension().height;
|
|
71336
|
+
}
|
|
71337
|
+
get isMenuOpen() {
|
|
71338
|
+
return this.topBarToolStore.isActive;
|
|
71339
|
+
}
|
|
71340
|
+
onClick() {
|
|
71341
|
+
if (!this.isMenuOpen) {
|
|
71342
|
+
this.topBarToolStore.openDropdown();
|
|
71343
|
+
}
|
|
71344
|
+
else {
|
|
71345
|
+
this.topBarToolStore.closeDropdowns();
|
|
71346
|
+
}
|
|
71347
|
+
}
|
|
71348
|
+
}
|
|
71349
|
+
|
|
71350
|
+
class DropdownAction extends owl.Component {
|
|
71351
|
+
static template = "o-spreadsheet-DropdownAction";
|
|
71352
|
+
static components = { ActionButton, Popover };
|
|
70545
71353
|
static props = {
|
|
70546
|
-
|
|
71354
|
+
parentAction: Object,
|
|
71355
|
+
childActions: Array,
|
|
71356
|
+
class: String,
|
|
71357
|
+
childClass: String,
|
|
70547
71358
|
};
|
|
70548
|
-
|
|
71359
|
+
topBarToolStore;
|
|
71360
|
+
actionRef = owl.useRef("actionRef");
|
|
70549
71361
|
setup() {
|
|
70550
|
-
this.
|
|
71362
|
+
this.topBarToolStore = useToolBarDropdownStore();
|
|
71363
|
+
}
|
|
71364
|
+
toggleDropdown() {
|
|
71365
|
+
if (this.isActive) {
|
|
71366
|
+
this.topBarToolStore.closeDropdowns();
|
|
71367
|
+
}
|
|
71368
|
+
else {
|
|
71369
|
+
this.topBarToolStore.openDropdown();
|
|
71370
|
+
}
|
|
70551
71371
|
}
|
|
70552
71372
|
get isActive() {
|
|
70553
|
-
return this.
|
|
71373
|
+
return this.topBarToolStore.isActive;
|
|
70554
71374
|
}
|
|
70555
|
-
|
|
70556
|
-
this.
|
|
71375
|
+
get popoverProps() {
|
|
71376
|
+
const rect = this.actionRef.el
|
|
71377
|
+
? this.actionRef.el.getBoundingClientRect()
|
|
71378
|
+
: { x: 0, y: 0, width: 0, height: 0 };
|
|
71379
|
+
return {
|
|
71380
|
+
anchorRect: rect,
|
|
71381
|
+
positioning: "BottomLeft",
|
|
71382
|
+
verticalOffset: 0,
|
|
71383
|
+
class: "rounded",
|
|
71384
|
+
};
|
|
70557
71385
|
}
|
|
70558
|
-
|
|
71386
|
+
}
|
|
71387
|
+
|
|
71388
|
+
class TopBarFontSizeEditor extends owl.Component {
|
|
71389
|
+
static components = { FontSizeEditor };
|
|
71390
|
+
static props = { class: String };
|
|
71391
|
+
static template = "o-spreadsheet-TopBarFontSizeEditor";
|
|
71392
|
+
topBarToolStore;
|
|
71393
|
+
setup() {
|
|
71394
|
+
this.topBarToolStore = useToolBarDropdownStore();
|
|
71395
|
+
}
|
|
71396
|
+
get currentFontSize() {
|
|
71397
|
+
return this.env.model.getters.getCurrentStyle().fontSize || DEFAULT_FONT_SIZE;
|
|
71398
|
+
}
|
|
71399
|
+
setFontSize(fontSize) {
|
|
71400
|
+
setStyle(this.env, { fontSize });
|
|
71401
|
+
}
|
|
71402
|
+
onToggle() {
|
|
70559
71403
|
if (this.isActive) {
|
|
70560
|
-
this.
|
|
71404
|
+
this.topBarToolStore.closeDropdowns();
|
|
70561
71405
|
}
|
|
70562
71406
|
else {
|
|
70563
|
-
this.
|
|
71407
|
+
this.topBarToolStore.openDropdown();
|
|
71408
|
+
}
|
|
71409
|
+
}
|
|
71410
|
+
onFocusInput() {
|
|
71411
|
+
this.topBarToolStore.openDropdown();
|
|
71412
|
+
}
|
|
71413
|
+
get isActive() {
|
|
71414
|
+
return this.topBarToolStore.isActive;
|
|
71415
|
+
}
|
|
71416
|
+
}
|
|
71417
|
+
|
|
71418
|
+
class NumberFormatsTool extends owl.Component {
|
|
71419
|
+
static template = "o-spreadsheet-NumberFormatsTool";
|
|
71420
|
+
static components = { Menu, ActionButton };
|
|
71421
|
+
static props = { class: String };
|
|
71422
|
+
formatNumberMenuItemSpec = formatNumberMenuItemSpec;
|
|
71423
|
+
topBarToolStore;
|
|
71424
|
+
buttonRef = owl.useRef("buttonRef");
|
|
71425
|
+
state = owl.useState({
|
|
71426
|
+
position: { x: 0, y: 0 },
|
|
71427
|
+
menuItems: [],
|
|
71428
|
+
});
|
|
71429
|
+
setup() {
|
|
71430
|
+
this.topBarToolStore = useToolBarDropdownStore();
|
|
71431
|
+
}
|
|
71432
|
+
toggleMenu() {
|
|
71433
|
+
if (this.isActive) {
|
|
71434
|
+
this.topBarToolStore.closeDropdowns();
|
|
71435
|
+
}
|
|
71436
|
+
else {
|
|
71437
|
+
const menu = createAction(this.formatNumberMenuItemSpec);
|
|
71438
|
+
this.state.menuItems = menu.children(this.env).sort((a, b) => a.sequence - b.sequence);
|
|
71439
|
+
const { x, y, height } = this.buttonRef.el.getBoundingClientRect();
|
|
71440
|
+
this.state.position = { x, y: y + height };
|
|
71441
|
+
this.topBarToolStore.openDropdown();
|
|
70564
71442
|
}
|
|
70565
71443
|
}
|
|
71444
|
+
get isActive() {
|
|
71445
|
+
return this.topBarToolStore.isActive;
|
|
71446
|
+
}
|
|
70566
71447
|
}
|
|
70567
71448
|
|
|
71449
|
+
const topBarToolBarRegistry = new ToolBarRegistry();
|
|
71450
|
+
topBarToolBarRegistry
|
|
71451
|
+
.add("edit")
|
|
71452
|
+
.addChild("edit", {
|
|
71453
|
+
component: ActionButton,
|
|
71454
|
+
props: {
|
|
71455
|
+
action: undo,
|
|
71456
|
+
class: "o-hoverable-button o-toolbar-button",
|
|
71457
|
+
},
|
|
71458
|
+
sequence: 1,
|
|
71459
|
+
})
|
|
71460
|
+
.addChild("edit", {
|
|
71461
|
+
component: ActionButton,
|
|
71462
|
+
props: {
|
|
71463
|
+
action: redo,
|
|
71464
|
+
class: "o-hoverable-button o-toolbar-button",
|
|
71465
|
+
},
|
|
71466
|
+
sequence: 2,
|
|
71467
|
+
})
|
|
71468
|
+
.addChild("edit", {
|
|
71469
|
+
component: PaintFormatButton,
|
|
71470
|
+
props: {
|
|
71471
|
+
class: "o-hoverable-button o-toolbar-button",
|
|
71472
|
+
},
|
|
71473
|
+
sequence: 3,
|
|
71474
|
+
})
|
|
71475
|
+
.addChild("edit", {
|
|
71476
|
+
component: ActionButton,
|
|
71477
|
+
props: {
|
|
71478
|
+
action: clearFormat,
|
|
71479
|
+
class: "o-hoverable-button o-toolbar-button",
|
|
71480
|
+
},
|
|
71481
|
+
sequence: 4,
|
|
71482
|
+
})
|
|
71483
|
+
.add("numberFormat")
|
|
71484
|
+
.addChild("numberFormat", {
|
|
71485
|
+
component: ActionButton,
|
|
71486
|
+
props: {
|
|
71487
|
+
action: formatPercent,
|
|
71488
|
+
class: "o-hoverable-button o-toolbar-button",
|
|
71489
|
+
},
|
|
71490
|
+
sequence: 1,
|
|
71491
|
+
})
|
|
71492
|
+
.addChild("numberFormat", {
|
|
71493
|
+
component: ActionButton,
|
|
71494
|
+
props: {
|
|
71495
|
+
action: decreaseDecimalPlaces,
|
|
71496
|
+
class: "o-hoverable-button o-toolbar-button",
|
|
71497
|
+
},
|
|
71498
|
+
sequence: 2,
|
|
71499
|
+
})
|
|
71500
|
+
.addChild("numberFormat", {
|
|
71501
|
+
component: ActionButton,
|
|
71502
|
+
props: {
|
|
71503
|
+
action: increaseDecimalPlaces,
|
|
71504
|
+
class: "o-hoverable-button o-toolbar-button",
|
|
71505
|
+
},
|
|
71506
|
+
sequence: 3,
|
|
71507
|
+
})
|
|
71508
|
+
.addChild("numberFormat", {
|
|
71509
|
+
component: NumberFormatsTool,
|
|
71510
|
+
props: {
|
|
71511
|
+
class: "o-menu-item-button o-hoverable-button o-toolbar-button",
|
|
71512
|
+
},
|
|
71513
|
+
sequence: 4,
|
|
71514
|
+
})
|
|
71515
|
+
.add("fontSize")
|
|
71516
|
+
.addChild("fontSize", {
|
|
71517
|
+
component: TopBarFontSizeEditor,
|
|
71518
|
+
props: {
|
|
71519
|
+
class: "o-hoverable-button o-toolbar-button",
|
|
71520
|
+
},
|
|
71521
|
+
sequence: 3,
|
|
71522
|
+
})
|
|
71523
|
+
.add("textStyle")
|
|
71524
|
+
.addChild("textStyle", {
|
|
71525
|
+
component: ActionButton,
|
|
71526
|
+
props: {
|
|
71527
|
+
action: formatBold,
|
|
71528
|
+
class: "o-hoverable-button o-toolbar-button",
|
|
71529
|
+
},
|
|
71530
|
+
sequence: 1,
|
|
71531
|
+
})
|
|
71532
|
+
.addChild("textStyle", {
|
|
71533
|
+
component: ActionButton,
|
|
71534
|
+
props: {
|
|
71535
|
+
action: formatItalic,
|
|
71536
|
+
class: "o-hoverable-button o-toolbar-button",
|
|
71537
|
+
},
|
|
71538
|
+
sequence: 2,
|
|
71539
|
+
})
|
|
71540
|
+
.addChild("textStyle", {
|
|
71541
|
+
component: ActionButton,
|
|
71542
|
+
props: {
|
|
71543
|
+
action: formatStrikethrough,
|
|
71544
|
+
class: "o-hoverable-button o-toolbar-button",
|
|
71545
|
+
},
|
|
71546
|
+
sequence: 3,
|
|
71547
|
+
})
|
|
71548
|
+
.addChild("textStyle", {
|
|
71549
|
+
component: TopBarColorEditor,
|
|
71550
|
+
props: {
|
|
71551
|
+
class: "o-hoverable-button o-menu-item-button o-toolbar-button",
|
|
71552
|
+
style: "textColor",
|
|
71553
|
+
icon: "o-spreadsheet-Icon.TEXT_COLOR",
|
|
71554
|
+
title: _t("Text Color"),
|
|
71555
|
+
},
|
|
71556
|
+
sequence: 4,
|
|
71557
|
+
})
|
|
71558
|
+
.add("cellStyle")
|
|
71559
|
+
.addChild("cellStyle", {
|
|
71560
|
+
component: TopBarColorEditor,
|
|
71561
|
+
props: {
|
|
71562
|
+
class: "o-hoverable-button o-menu-item-button o-toolbar-button",
|
|
71563
|
+
style: "fillColor",
|
|
71564
|
+
icon: "o-spreadsheet-Icon.FILL_COLOR",
|
|
71565
|
+
title: _t("Fill Color"),
|
|
71566
|
+
},
|
|
71567
|
+
sequence: 1,
|
|
71568
|
+
})
|
|
71569
|
+
.addChild("cellStyle", {
|
|
71570
|
+
component: BorderEditorWidget,
|
|
71571
|
+
props: {
|
|
71572
|
+
class: "o-hoverable-button o-menu-item-button o-toolbar-button",
|
|
71573
|
+
},
|
|
71574
|
+
sequence: 2,
|
|
71575
|
+
})
|
|
71576
|
+
.addChild("cellStyle", {
|
|
71577
|
+
component: ActionButton,
|
|
71578
|
+
props: {
|
|
71579
|
+
action: mergeCells,
|
|
71580
|
+
class: "o-hoverable-button o-menu-item-button o-toolbar-button",
|
|
71581
|
+
},
|
|
71582
|
+
sequence: 3,
|
|
71583
|
+
})
|
|
71584
|
+
.add("alignment")
|
|
71585
|
+
.addChild("alignment", {
|
|
71586
|
+
component: DropdownAction,
|
|
71587
|
+
props: {
|
|
71588
|
+
parentAction: formatAlignmentHorizontal,
|
|
71589
|
+
childActions: [
|
|
71590
|
+
formatAlignmentLeft,
|
|
71591
|
+
formatAlignmentCenter,
|
|
71592
|
+
formatAlignmentRight,
|
|
71593
|
+
],
|
|
71594
|
+
class: "o-hoverable-button o-toolbar-button",
|
|
71595
|
+
childClass: "o-hoverable-button",
|
|
71596
|
+
},
|
|
71597
|
+
sequence: 1,
|
|
71598
|
+
})
|
|
71599
|
+
.addChild("alignment", {
|
|
71600
|
+
component: DropdownAction,
|
|
71601
|
+
props: {
|
|
71602
|
+
parentAction: formatAlignmentVertical,
|
|
71603
|
+
childActions: [
|
|
71604
|
+
formatAlignmentTop,
|
|
71605
|
+
formatAlignmentMiddle,
|
|
71606
|
+
formatAlignmentBottom,
|
|
71607
|
+
],
|
|
71608
|
+
class: "o-hoverable-button o-menu-item-button o-toolbar-button",
|
|
71609
|
+
childClass: "o-hoverable-button",
|
|
71610
|
+
},
|
|
71611
|
+
sequence: 2,
|
|
71612
|
+
})
|
|
71613
|
+
.addChild("alignment", {
|
|
71614
|
+
component: DropdownAction,
|
|
71615
|
+
props: {
|
|
71616
|
+
parentAction: formatWrapping,
|
|
71617
|
+
childActions: [
|
|
71618
|
+
formatWrappingOverflow,
|
|
71619
|
+
formatWrappingWrap,
|
|
71620
|
+
formatWrappingClip,
|
|
71621
|
+
],
|
|
71622
|
+
class: "o-hoverable-button o-menu-item-button o-toolbar-button",
|
|
71623
|
+
childClass: "o-hoverable-button",
|
|
71624
|
+
},
|
|
71625
|
+
sequence: 3,
|
|
71626
|
+
})
|
|
71627
|
+
.add("misc")
|
|
71628
|
+
.addChild("misc", {
|
|
71629
|
+
component: TableDropdownButton,
|
|
71630
|
+
props: { class: "o-toolbar-button o-hoverable-button o-menu-item-button" },
|
|
71631
|
+
sequence: 1,
|
|
71632
|
+
})
|
|
71633
|
+
.addChild("misc", {
|
|
71634
|
+
component: ActionButton,
|
|
71635
|
+
props: {
|
|
71636
|
+
action: createRemoveFilterTool,
|
|
71637
|
+
class: "o-hoverable-button o-menu-item-button o-toolbar-button",
|
|
71638
|
+
},
|
|
71639
|
+
sequence: 2,
|
|
71640
|
+
});
|
|
71641
|
+
|
|
70568
71642
|
// -----------------------------------------------------------------------------
|
|
70569
71643
|
// TopBar
|
|
70570
71644
|
// -----------------------------------------------------------------------------
|
|
70571
71645
|
css /* scss */ `
|
|
71646
|
+
.o-topbar-divider {
|
|
71647
|
+
border-right: 1px solid ${SEPARATOR_COLOR};
|
|
71648
|
+
width: 0;
|
|
71649
|
+
margin: 0 6px;
|
|
71650
|
+
}
|
|
71651
|
+
|
|
71652
|
+
.o-toolbar-button {
|
|
71653
|
+
height: 30px;
|
|
71654
|
+
}
|
|
71655
|
+
|
|
70572
71656
|
.o-spreadsheet-topbar {
|
|
70573
71657
|
line-height: 1.2;
|
|
70574
71658
|
font-size: 13px;
|
|
@@ -70618,47 +71702,7 @@ css /* scss */ `
|
|
|
70618
71702
|
|
|
70619
71703
|
/* Toolbar */
|
|
70620
71704
|
.o-toolbar-tools {
|
|
70621
|
-
display: flex;
|
|
70622
|
-
flex-shrink: 0;
|
|
70623
|
-
margin: 0px 6px 0px 16px;
|
|
70624
71705
|
cursor: default;
|
|
70625
|
-
|
|
70626
|
-
.o-divider {
|
|
70627
|
-
display: inline-block;
|
|
70628
|
-
border-right: 1px solid ${SEPARATOR_COLOR};
|
|
70629
|
-
width: 0;
|
|
70630
|
-
margin: 0 6px;
|
|
70631
|
-
}
|
|
70632
|
-
|
|
70633
|
-
.o-dropdown {
|
|
70634
|
-
position: relative;
|
|
70635
|
-
display: flex;
|
|
70636
|
-
align-items: center;
|
|
70637
|
-
|
|
70638
|
-
> span {
|
|
70639
|
-
height: 30px;
|
|
70640
|
-
}
|
|
70641
|
-
|
|
70642
|
-
.o-dropdown-content {
|
|
70643
|
-
position: absolute;
|
|
70644
|
-
top: 100%;
|
|
70645
|
-
left: 0;
|
|
70646
|
-
overflow-y: auto;
|
|
70647
|
-
overflow-x: hidden;
|
|
70648
|
-
padding: 2px;
|
|
70649
|
-
z-index: ${ComponentsImportance.Dropdown};
|
|
70650
|
-
box-shadow: 1px 2px 5px 2px rgba(51, 51, 51, 0.15);
|
|
70651
|
-
background-color: white;
|
|
70652
|
-
|
|
70653
|
-
.o-dropdown-line {
|
|
70654
|
-
display: flex;
|
|
70655
|
-
|
|
70656
|
-
> span {
|
|
70657
|
-
padding: 4px;
|
|
70658
|
-
}
|
|
70659
|
-
}
|
|
70660
|
-
}
|
|
70661
|
-
}
|
|
70662
71706
|
}
|
|
70663
71707
|
}
|
|
70664
71708
|
}
|
|
@@ -70670,38 +71714,73 @@ class TopBar extends owl.Component {
|
|
|
70670
71714
|
dropdownMaxHeight: Number,
|
|
70671
71715
|
};
|
|
70672
71716
|
static components = {
|
|
70673
|
-
ColorPickerWidget,
|
|
70674
|
-
ColorPicker,
|
|
70675
71717
|
Menu,
|
|
70676
71718
|
TopBarComposer,
|
|
70677
|
-
|
|
70678
|
-
ActionButton,
|
|
70679
|
-
PaintFormatButton,
|
|
70680
|
-
BorderEditorWidget,
|
|
70681
|
-
TableDropdownButton,
|
|
71719
|
+
Popover,
|
|
70682
71720
|
};
|
|
71721
|
+
toolsCategories = topBarToolBarRegistry.getCategories();
|
|
70683
71722
|
state = owl.useState({
|
|
70684
71723
|
menuState: { isOpen: false, position: null, menuItems: [] },
|
|
70685
|
-
|
|
70686
|
-
|
|
70687
|
-
textColor: "#000000",
|
|
71724
|
+
invisibleToolsCategories: [],
|
|
71725
|
+
toolsPopoverState: { isOpen: false },
|
|
70688
71726
|
});
|
|
70689
71727
|
isSelectingMenu = false;
|
|
70690
71728
|
openedEl = null;
|
|
70691
71729
|
menus = [];
|
|
70692
|
-
|
|
70693
|
-
FORMAT = ACTION_FORMAT;
|
|
70694
|
-
DATA = ACTION_DATA;
|
|
71730
|
+
toolbarMenuRegistry = topBarToolBarRegistry;
|
|
70695
71731
|
formatNumberMenuItemSpec = formatNumberMenuItemSpec;
|
|
70696
71732
|
isntToolbarMenu = false;
|
|
70697
71733
|
composerFocusStore;
|
|
70698
71734
|
fingerprints;
|
|
71735
|
+
topBarToolStore;
|
|
71736
|
+
toolBarContainerRef = owl.useRef("toolBarContainer");
|
|
71737
|
+
toolbarRef = owl.useRef("toolBar");
|
|
71738
|
+
moreToolsContainerRef = owl.useRef("moreToolsContainer");
|
|
71739
|
+
moreToolsButtonRef = owl.useRef("moreToolsButton");
|
|
71740
|
+
spreadsheetRect = useSpreadsheetRect();
|
|
70699
71741
|
setup() {
|
|
70700
71742
|
this.composerFocusStore = useStore(ComposerFocusStore);
|
|
70701
71743
|
this.fingerprints = useStore(FormulaFingerprintStore);
|
|
71744
|
+
this.topBarToolStore = useStore(TopBarToolStore);
|
|
70702
71745
|
owl.useExternalListener(window, "click", this.onExternalClick);
|
|
70703
71746
|
owl.onWillStart(() => this.updateCellState());
|
|
70704
71747
|
owl.onWillUpdateProps(() => this.updateCellState());
|
|
71748
|
+
owl.useEffect(() => {
|
|
71749
|
+
this.state.toolsPopoverState.isOpen = false;
|
|
71750
|
+
this.setVisibilityToolsGroups();
|
|
71751
|
+
}, () => [this.spreadsheetRect.width]);
|
|
71752
|
+
}
|
|
71753
|
+
setVisibilityToolsGroups() {
|
|
71754
|
+
if (this.env.model.getters.isReadonly()) {
|
|
71755
|
+
return;
|
|
71756
|
+
}
|
|
71757
|
+
const hiddenCategories = [];
|
|
71758
|
+
const { x: toolsX } = this.toolbarRef.el.getBoundingClientRect();
|
|
71759
|
+
const { x } = this.toolBarContainerRef.el.getBoundingClientRect();
|
|
71760
|
+
// Compute the with of the button that will toggle the hidden tools
|
|
71761
|
+
this.moreToolsContainerRef.el?.classList.remove("d-none");
|
|
71762
|
+
const moreToolsWidth = this.moreToolsButtonRef.el?.getBoundingClientRect().width || 0;
|
|
71763
|
+
// The actual width in which we can place our tools so that they are visible.
|
|
71764
|
+
// Every tool container passed that width will be hidden.
|
|
71765
|
+
// We remove 16px to the width to account for a scrollbar that might appear.
|
|
71766
|
+
// Otherwise, we could end up in a loop of computation
|
|
71767
|
+
const usableWidth = Math.round(this.spreadsheetRect.width) - moreToolsWidth - (toolsX - x) - 16;
|
|
71768
|
+
const toolElements = document.querySelectorAll(".tool-container");
|
|
71769
|
+
let currentWidth = 0;
|
|
71770
|
+
for (let index = 0; index < toolElements.length; index++) {
|
|
71771
|
+
const element = toolElements[index];
|
|
71772
|
+
element.classList.remove("d-none");
|
|
71773
|
+
const { width: toolWidth } = element.getBoundingClientRect();
|
|
71774
|
+
currentWidth += toolWidth;
|
|
71775
|
+
if (currentWidth > usableWidth) {
|
|
71776
|
+
element.classList.add("d-none");
|
|
71777
|
+
hiddenCategories.push(this.toolsCategories[index]);
|
|
71778
|
+
}
|
|
71779
|
+
}
|
|
71780
|
+
this.state.invisibleToolsCategories = hiddenCategories;
|
|
71781
|
+
if (!hiddenCategories.length) {
|
|
71782
|
+
this.moreToolsContainerRef.el?.classList.add("d-none");
|
|
71783
|
+
}
|
|
70705
71784
|
}
|
|
70706
71785
|
get topbarComponents() {
|
|
70707
71786
|
return topbarComponentRegistry
|
|
@@ -70731,12 +71810,6 @@ class TopBar extends owl.Component {
|
|
|
70731
71810
|
this.openMenu(menu, ev);
|
|
70732
71811
|
}
|
|
70733
71812
|
}
|
|
70734
|
-
toggleDropdownTool(tool, ev) {
|
|
70735
|
-
const isOpen = this.state.activeTool === tool;
|
|
70736
|
-
this.closeMenus();
|
|
70737
|
-
this.state.activeTool = isOpen ? "" : tool;
|
|
70738
|
-
this.openedEl = isOpen ? null : ev.target;
|
|
70739
|
-
}
|
|
70740
71813
|
toggleContextMenu(menu, ev) {
|
|
70741
71814
|
if (this.state.menuState.isOpen && this.isntToolbarMenu) {
|
|
70742
71815
|
this.closeMenus();
|
|
@@ -70746,19 +71819,10 @@ class TopBar extends owl.Component {
|
|
|
70746
71819
|
this.isntToolbarMenu = true;
|
|
70747
71820
|
}
|
|
70748
71821
|
}
|
|
70749
|
-
toggleToolbarContextMenu(menuSpec, ev) {
|
|
70750
|
-
if (this.state.menuState.isOpen && !this.isntToolbarMenu) {
|
|
70751
|
-
this.closeMenus();
|
|
70752
|
-
}
|
|
70753
|
-
else {
|
|
70754
|
-
const menu = createAction(menuSpec);
|
|
70755
|
-
this.openMenu(menu, ev);
|
|
70756
|
-
this.isntToolbarMenu = false;
|
|
70757
|
-
}
|
|
70758
|
-
}
|
|
70759
71822
|
openMenu(menu, ev) {
|
|
71823
|
+
this.topBarToolStore.closeDropdowns();
|
|
71824
|
+
this.state.toolsPopoverState.isOpen = false;
|
|
70760
71825
|
const { left, top, height } = ev.currentTarget.getBoundingClientRect();
|
|
70761
|
-
this.state.activeTool = "";
|
|
70762
71826
|
this.state.menuState.isOpen = true;
|
|
70763
71827
|
this.state.menuState.position = { x: left, y: top + height };
|
|
70764
71828
|
this.state.menuState.menuItems = menu
|
|
@@ -70770,16 +71834,14 @@ class TopBar extends owl.Component {
|
|
|
70770
71834
|
this.composerFocusStore.activeComposer.stopEdition();
|
|
70771
71835
|
}
|
|
70772
71836
|
closeMenus() {
|
|
70773
|
-
this.
|
|
71837
|
+
this.topBarToolStore.closeDropdowns();
|
|
71838
|
+
this.state.toolsPopoverState.isOpen = false;
|
|
70774
71839
|
this.state.menuState.isOpen = false;
|
|
70775
71840
|
this.state.menuState.parentMenu = undefined;
|
|
70776
71841
|
this.isSelectingMenu = false;
|
|
70777
71842
|
this.openedEl = null;
|
|
70778
71843
|
}
|
|
70779
71844
|
updateCellState() {
|
|
70780
|
-
const style = this.env.model.getters.getCurrentStyle();
|
|
70781
|
-
this.state.fillColor = style.fillColor || "#ffffff";
|
|
70782
|
-
this.state.textColor = style.textColor || "#000000";
|
|
70783
71845
|
this.menus = topbarMenuRegistry.getMenuItems();
|
|
70784
71846
|
}
|
|
70785
71847
|
getMenuName(menu) {
|
|
@@ -70792,6 +71854,26 @@ class TopBar extends owl.Component {
|
|
|
70792
71854
|
setFontSize(fontSize) {
|
|
70793
71855
|
setStyle(this.env, { fontSize });
|
|
70794
71856
|
}
|
|
71857
|
+
toggleMoreTools() {
|
|
71858
|
+
this.topBarToolStore.closeDropdowns();
|
|
71859
|
+
this.state.toolsPopoverState.isOpen = !this.state.toolsPopoverState.isOpen;
|
|
71860
|
+
}
|
|
71861
|
+
get toolsPopoverProps() {
|
|
71862
|
+
const rect = this.moreToolsButtonRef.el
|
|
71863
|
+
? getBoundingRectAsPOJO(this.moreToolsButtonRef.el)
|
|
71864
|
+
: { x: 0, y: 0, width: 0, height: 0 };
|
|
71865
|
+
return {
|
|
71866
|
+
anchorRect: rect,
|
|
71867
|
+
positioning: "BottomLeft",
|
|
71868
|
+
verticalOffset: 0,
|
|
71869
|
+
class: "rounded",
|
|
71870
|
+
maxWidth: 300,
|
|
71871
|
+
};
|
|
71872
|
+
}
|
|
71873
|
+
showDivider(categoryIndex) {
|
|
71874
|
+
return (categoryIndex < this.toolsCategories.length - 1 ||
|
|
71875
|
+
this.state.invisibleToolsCategories.length > 0);
|
|
71876
|
+
}
|
|
70795
71877
|
}
|
|
70796
71878
|
|
|
70797
71879
|
function instantiateClipboard() {
|
|
@@ -70814,6 +71896,7 @@ class WebClipboardWrapper {
|
|
|
70814
71896
|
* Therefore, we try to catch any errors and fallback on writing only standard
|
|
70815
71897
|
* mimetypes to prevent the whole copy action from crashing.
|
|
70816
71898
|
*/
|
|
71899
|
+
console.log("Failed to write on the clipboard, falling back to plain/html text. Error %s", e);
|
|
70817
71900
|
try {
|
|
70818
71901
|
await this.clipboard?.write([
|
|
70819
71902
|
new ClipboardItem({
|
|
@@ -70845,14 +71928,20 @@ class WebClipboardWrapper {
|
|
|
70845
71928
|
if (this.clipboard?.read) {
|
|
70846
71929
|
try {
|
|
70847
71930
|
const clipboardItems = await this.clipboard.read();
|
|
70848
|
-
const
|
|
71931
|
+
const osClipboardContent = {};
|
|
70849
71932
|
for (const item of clipboardItems) {
|
|
70850
71933
|
for (const type of item.types) {
|
|
70851
71934
|
const blob = await item.getType(type);
|
|
70852
|
-
|
|
71935
|
+
if (AllowedImageMimeTypes.includes(type)) {
|
|
71936
|
+
osClipboardContent[type] = blob;
|
|
71937
|
+
}
|
|
71938
|
+
else {
|
|
71939
|
+
const text = await blob.text();
|
|
71940
|
+
osClipboardContent[type] = text;
|
|
71941
|
+
}
|
|
70853
71942
|
}
|
|
70854
71943
|
}
|
|
70855
|
-
return { status: "ok", content:
|
|
71944
|
+
return { status: "ok", content: osClipboardContent };
|
|
70856
71945
|
}
|
|
70857
71946
|
catch (e) {
|
|
70858
71947
|
const status = permissionResult?.state === "denied" ? "permissionDenied" : "notImplemented";
|
|
@@ -70869,13 +71958,17 @@ class WebClipboardWrapper {
|
|
|
70869
71958
|
}
|
|
70870
71959
|
}
|
|
70871
71960
|
getClipboardItems(content) {
|
|
70872
|
-
const clipboardItemData = {
|
|
70873
|
-
|
|
70874
|
-
[
|
|
70875
|
-
}
|
|
71961
|
+
const clipboardItemData = {};
|
|
71962
|
+
for (const type of Object.keys(content)) {
|
|
71963
|
+
clipboardItemData[type] = this.getBlob(content, type);
|
|
71964
|
+
}
|
|
70876
71965
|
return [new ClipboardItem(clipboardItemData)];
|
|
70877
71966
|
}
|
|
70878
71967
|
getBlob(clipboardContent, type) {
|
|
71968
|
+
const content = clipboardContent[type];
|
|
71969
|
+
if (content instanceof Blob || content instanceof File) {
|
|
71970
|
+
return content;
|
|
71971
|
+
}
|
|
70879
71972
|
return new Blob([clipboardContent[type] || ""], {
|
|
70880
71973
|
type,
|
|
70881
71974
|
});
|
|
@@ -71154,7 +72247,7 @@ class Spreadsheet extends owl.Component {
|
|
|
71154
72247
|
properties["grid-template-rows"] = `auto`;
|
|
71155
72248
|
}
|
|
71156
72249
|
else {
|
|
71157
|
-
properties["grid-template-rows"] = `
|
|
72250
|
+
properties["grid-template-rows"] = `min-content auto min-content`;
|
|
71158
72251
|
}
|
|
71159
72252
|
properties["grid-template-columns"] = `auto ${this.sidePanel.panelSize}px`;
|
|
71160
72253
|
return cssPropertiesToCss(properties);
|
|
@@ -71265,8 +72358,7 @@ class Spreadsheet extends owl.Component {
|
|
|
71265
72358
|
this._focusGrid();
|
|
71266
72359
|
}
|
|
71267
72360
|
get gridHeight() {
|
|
71268
|
-
|
|
71269
|
-
return height;
|
|
72361
|
+
return this.env.model.getters.getSheetViewDimension().height;
|
|
71270
72362
|
}
|
|
71271
72363
|
get gridContainerStyle() {
|
|
71272
72364
|
const gridColSize = GROUP_LAYER_WIDTH * this.rowLayers.length;
|
|
@@ -73437,7 +74529,7 @@ function numberRef(reference) {
|
|
|
73437
74529
|
`;
|
|
73438
74530
|
}
|
|
73439
74531
|
|
|
73440
|
-
function addFormula(formula, value) {
|
|
74532
|
+
function addFormula(formula, value, formulaSpillRange) {
|
|
73441
74533
|
if (!formula) {
|
|
73442
74534
|
return { attrs: [], node: escapeXml `` };
|
|
73443
74535
|
}
|
|
@@ -73445,10 +74537,17 @@ function addFormula(formula, value) {
|
|
|
73445
74537
|
if (type === undefined) {
|
|
73446
74538
|
return { attrs: [], node: escapeXml `` };
|
|
73447
74539
|
}
|
|
73448
|
-
const attrs = [
|
|
74540
|
+
const attrs = [
|
|
74541
|
+
["cm", "1"],
|
|
74542
|
+
["t", type],
|
|
74543
|
+
];
|
|
73449
74544
|
const XlsxFormula = adaptFormulaToExcel(formula);
|
|
73450
74545
|
const exportedValue = adaptFormulaValueToExcel(value);
|
|
73451
|
-
|
|
74546
|
+
// We treat all formulas as array formulas (a simple formula
|
|
74547
|
+
// is an array formula that spills on only one cell) to avoid
|
|
74548
|
+
// trying to detect spilling sub-formulas which is not a trivial task.
|
|
74549
|
+
let node;
|
|
74550
|
+
node = escapeXml /*xml*/ `<f t="array" ref="${formulaSpillRange}">${XlsxFormula}</f><v>${exportedValue}</v>`;
|
|
73452
74551
|
return { attrs, node };
|
|
73453
74552
|
}
|
|
73454
74553
|
function addContent(content, sharedStrings, forceString = false) {
|
|
@@ -74438,7 +75537,7 @@ function addRows(construct, data, sheet) {
|
|
|
74438
75537
|
let cellNode = escapeXml ``;
|
|
74439
75538
|
// Either formula or static value inside the cell
|
|
74440
75539
|
if (content?.startsWith("=") && value !== undefined) {
|
|
74441
|
-
const res = addFormula(content, value);
|
|
75540
|
+
const res = addFormula(content, value, sheet.formulaSpillRanges[xc] ?? xc);
|
|
74442
75541
|
if (!res) {
|
|
74443
75542
|
continue;
|
|
74444
75543
|
}
|
|
@@ -74724,6 +75823,30 @@ function createWorksheets(data, construct) {
|
|
|
74724
75823
|
`;
|
|
74725
75824
|
files.push(createXMLFile(parseXML(sheetXml), `xl/worksheets/sheet${sheetIndex}.xml`, "sheet"));
|
|
74726
75825
|
}
|
|
75826
|
+
const sheetMetadataXml = escapeXml /*xml*/ `
|
|
75827
|
+
<metadata xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:xda="http://schemas.microsoft.com/office/spreadsheetml/2017/dynamicarray">
|
|
75828
|
+
<metadataTypes count="1">
|
|
75829
|
+
<metadataType name="XLDAPR" minSupportedVersion="120000" copy="1" pasteAll="1"
|
|
75830
|
+
pasteValues="1" merge="1" splitFirst="1" rowColShift="1" clearFormats="1"
|
|
75831
|
+
clearComments="1" assign="1" coerce="1" cellMeta="1" />
|
|
75832
|
+
</metadataTypes>
|
|
75833
|
+
<futureMetadata name="XLDAPR" count="1">
|
|
75834
|
+
<bk>
|
|
75835
|
+
<extLst>
|
|
75836
|
+
<ext uri="{${ARRAY_FORMULA_URI}}">
|
|
75837
|
+
<xda:dynamicArrayProperties fDynamic="1" fCollapsed="0" />
|
|
75838
|
+
</ext>
|
|
75839
|
+
</extLst>
|
|
75840
|
+
</bk>
|
|
75841
|
+
</futureMetadata>
|
|
75842
|
+
<cellMetadata count="1">
|
|
75843
|
+
<bk>
|
|
75844
|
+
<rc t="1" v="0" />
|
|
75845
|
+
</bk>
|
|
75846
|
+
</cellMetadata>
|
|
75847
|
+
</metadata>
|
|
75848
|
+
`;
|
|
75849
|
+
files.push(createXMLFile(parseXML(sheetMetadataXml), "xl/metadata.xml", "metadata"));
|
|
74727
75850
|
addRelsToFile(construct.relsFiles, "xl/_rels/workbook.xml.rels", {
|
|
74728
75851
|
type: XLSX_RELATION_TYPE.sharedStrings,
|
|
74729
75852
|
target: "sharedStrings.xml",
|
|
@@ -74732,6 +75855,10 @@ function createWorksheets(data, construct) {
|
|
|
74732
75855
|
type: XLSX_RELATION_TYPE.styles,
|
|
74733
75856
|
target: "styles.xml",
|
|
74734
75857
|
});
|
|
75858
|
+
addRelsToFile(construct.relsFiles, "xl/_rels/workbook.xml.rels", {
|
|
75859
|
+
type: XLSX_RELATION_TYPE.metadata,
|
|
75860
|
+
target: "metadata.xml",
|
|
75861
|
+
});
|
|
74735
75862
|
return files;
|
|
74736
75863
|
}
|
|
74737
75864
|
/**
|
|
@@ -75693,6 +76820,6 @@ exports.tokenColors = tokenColors;
|
|
|
75693
76820
|
exports.tokenize = tokenize;
|
|
75694
76821
|
|
|
75695
76822
|
|
|
75696
|
-
__info__.version = "18.3.0-alpha.
|
|
75697
|
-
__info__.date = "2025-
|
|
75698
|
-
__info__.hash = "
|
|
76823
|
+
__info__.version = "18.3.0-alpha.3";
|
|
76824
|
+
__info__.date = "2025-03-07T10:41:05.411Z";
|
|
76825
|
+
__info__.hash = "f59f5f6";
|