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