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