@odoo/o-spreadsheet 16.2.0-alpha.3 → 16.2.0-alpha.4
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 → o-spreadsheet.cjs.js} +1587 -1514
- package/dist/{o_spreadsheet.d.ts → o-spreadsheet.d.ts} +132 -132
- package/dist/{o_spreadsheet.esm.js → o-spreadsheet.esm.js} +1588 -1515
- package/dist/{o_spreadsheet.iife.js → o-spreadsheet.iife.js} +1587 -1514
- package/dist/o-spreadsheet.iife.min.js +2431 -0
- package/package.json +10 -9
- package/readme.md +3 -1
- package/dist/o_spreadsheet.iife.min.js +0 -2398
|
@@ -2026,10 +2026,10 @@
|
|
|
2026
2026
|
if (right !== undefined && bottom !== undefined)
|
|
2027
2027
|
return { left, top, right, bottom };
|
|
2028
2028
|
else if (bottom === undefined && right !== undefined) {
|
|
2029
|
-
return { right, top, left, bottom: this.getSheetSize(this.sheetId).
|
|
2029
|
+
return { right, top, left, bottom: this.getSheetSize(this.sheetId).numberOfRows - 1 };
|
|
2030
2030
|
}
|
|
2031
2031
|
else if (right === undefined && bottom !== undefined) {
|
|
2032
|
-
return { bottom, left, top, right: this.getSheetSize(this.sheetId).
|
|
2032
|
+
return { bottom, left, top, right: this.getSheetSize(this.sheetId).numberOfCols - 1 };
|
|
2033
2033
|
}
|
|
2034
2034
|
throw new Error(_lt("Bad zone format"));
|
|
2035
2035
|
}
|
|
@@ -2652,13 +2652,13 @@
|
|
|
2652
2652
|
}
|
|
2653
2653
|
function zoneToDimension(zone) {
|
|
2654
2654
|
return {
|
|
2655
|
-
|
|
2656
|
-
|
|
2655
|
+
numberOfRows: zone.bottom - zone.top + 1,
|
|
2656
|
+
numberOfCols: zone.right - zone.left + 1,
|
|
2657
2657
|
};
|
|
2658
2658
|
}
|
|
2659
2659
|
function isOneDimensional(zone) {
|
|
2660
|
-
const {
|
|
2661
|
-
return
|
|
2660
|
+
const { numberOfCols, numberOfRows } = zoneToDimension(zone);
|
|
2661
|
+
return numberOfCols === 1 || numberOfRows === 1;
|
|
2662
2662
|
}
|
|
2663
2663
|
/**
|
|
2664
2664
|
* Array of all positions in the zone.
|
|
@@ -3133,7 +3133,7 @@
|
|
|
3133
3133
|
return this.runtime.fontColor;
|
|
3134
3134
|
}
|
|
3135
3135
|
get secondaryFontColor() {
|
|
3136
|
-
return relativeLuminance(this.
|
|
3136
|
+
return relativeLuminance(this.backgroundColor) > 0.3 ? "#525252" : "#C8C8C8";
|
|
3137
3137
|
}
|
|
3138
3138
|
get figure() {
|
|
3139
3139
|
return this.props.figure;
|
|
@@ -3745,11 +3745,11 @@
|
|
|
3745
3745
|
const labelZone = (_a = ds.labelCell) === null || _a === void 0 ? void 0 : _a.zone;
|
|
3746
3746
|
let dataZone = ds.dataRange.zone;
|
|
3747
3747
|
if (labelZone) {
|
|
3748
|
-
const {
|
|
3749
|
-
if (
|
|
3748
|
+
const { numberOfRows, numberOfCols } = zoneToDimension(dataZone);
|
|
3749
|
+
if (numberOfRows === 1) {
|
|
3750
3750
|
dataZone = { ...dataZone, left: dataZone.left + 1 };
|
|
3751
3751
|
}
|
|
3752
|
-
else if (
|
|
3752
|
+
else if (numberOfCols === 1) {
|
|
3753
3753
|
dataZone = { ...dataZone, top: dataZone.top + 1 };
|
|
3754
3754
|
}
|
|
3755
3755
|
}
|
|
@@ -5462,6 +5462,76 @@
|
|
|
5462
5462
|
chartComponentRegistry.add("gauge", ChartJsComponent);
|
|
5463
5463
|
chartComponentRegistry.add("scorecard", ScorecardChart$1);
|
|
5464
5464
|
|
|
5465
|
+
// -----------------------------------------------------------------------------
|
|
5466
|
+
// STYLE
|
|
5467
|
+
// -----------------------------------------------------------------------------
|
|
5468
|
+
css /* scss */ `
|
|
5469
|
+
.o-chart-container {
|
|
5470
|
+
width: 100%;
|
|
5471
|
+
height: 100%;
|
|
5472
|
+
position: relative;
|
|
5473
|
+
}
|
|
5474
|
+
`;
|
|
5475
|
+
class ChartFigure extends owl.Component {
|
|
5476
|
+
get chartType() {
|
|
5477
|
+
return this.env.model.getters.getChartType(this.props.figure.id);
|
|
5478
|
+
}
|
|
5479
|
+
get chartComponent() {
|
|
5480
|
+
const type = this.chartType;
|
|
5481
|
+
const component = chartComponentRegistry.get(type);
|
|
5482
|
+
if (!component) {
|
|
5483
|
+
throw new Error(`Component is not defined for type ${type}`);
|
|
5484
|
+
}
|
|
5485
|
+
return component;
|
|
5486
|
+
}
|
|
5487
|
+
}
|
|
5488
|
+
ChartFigure.template = "o-spreadsheet-ChartFigure";
|
|
5489
|
+
ChartFigure.components = {};
|
|
5490
|
+
ChartFigure.props = {
|
|
5491
|
+
figure: Object,
|
|
5492
|
+
onFigureDeleted: Function,
|
|
5493
|
+
};
|
|
5494
|
+
|
|
5495
|
+
function interactiveCut(env) {
|
|
5496
|
+
const result = env.model.dispatch("CUT");
|
|
5497
|
+
if (!result.isSuccessful) {
|
|
5498
|
+
if (result.isCancelledBecause(19 /* CommandResult.WrongCutSelection */)) {
|
|
5499
|
+
env.raiseError(_lt("This operation is not allowed with multiple selections."));
|
|
5500
|
+
}
|
|
5501
|
+
}
|
|
5502
|
+
}
|
|
5503
|
+
|
|
5504
|
+
const PasteInteractiveContent = {
|
|
5505
|
+
wrongPasteSelection: _lt("This operation is not allowed with multiple selections."),
|
|
5506
|
+
willRemoveExistingMerge: _lt("This operation is not possible due to a merge. Please remove the merges first than try again."),
|
|
5507
|
+
wrongFigurePasteOption: _lt("Cannot do a special paste of a figure."),
|
|
5508
|
+
frozenPaneOverlap: _lt("Cannot paste merged cells over a frozen pane."),
|
|
5509
|
+
};
|
|
5510
|
+
function handlePasteResult(env, result) {
|
|
5511
|
+
if (!result.isSuccessful) {
|
|
5512
|
+
if (result.reasons.includes(20 /* CommandResult.WrongPasteSelection */)) {
|
|
5513
|
+
env.raiseError(PasteInteractiveContent.wrongPasteSelection);
|
|
5514
|
+
}
|
|
5515
|
+
else if (result.reasons.includes(2 /* CommandResult.WillRemoveExistingMerge */)) {
|
|
5516
|
+
env.raiseError(PasteInteractiveContent.willRemoveExistingMerge);
|
|
5517
|
+
}
|
|
5518
|
+
else if (result.reasons.includes(22 /* CommandResult.WrongFigurePasteOption */)) {
|
|
5519
|
+
env.raiseError(PasteInteractiveContent.wrongFigurePasteOption);
|
|
5520
|
+
}
|
|
5521
|
+
else if (result.reasons.includes(75 /* CommandResult.FrozenPaneOverlap */)) {
|
|
5522
|
+
env.raiseError(PasteInteractiveContent.frozenPaneOverlap);
|
|
5523
|
+
}
|
|
5524
|
+
}
|
|
5525
|
+
}
|
|
5526
|
+
function interactivePaste(env, target, pasteOption) {
|
|
5527
|
+
const result = env.model.dispatch("PASTE", { target, pasteOption });
|
|
5528
|
+
handlePasteResult(env, result);
|
|
5529
|
+
}
|
|
5530
|
+
function interactivePasteFromOS(env, target, text) {
|
|
5531
|
+
const result = env.model.dispatch("PASTE_FROM_OS_CLIPBOARD", { target, text });
|
|
5532
|
+
handlePasteResult(env, result);
|
|
5533
|
+
}
|
|
5534
|
+
|
|
5465
5535
|
function createMenu(menuItems) {
|
|
5466
5536
|
return menuItems.map(createMenuItem).sort((a, b) => a.sequence - b.sequence);
|
|
5467
5537
|
}
|
|
@@ -5542,979 +5612,276 @@
|
|
|
5542
5612
|
}
|
|
5543
5613
|
|
|
5544
5614
|
/**
|
|
5545
|
-
*
|
|
5546
|
-
* to the browser viewport.
|
|
5615
|
+
* Create a function used to create a Chart based on the definition
|
|
5547
5616
|
*/
|
|
5548
|
-
function
|
|
5549
|
-
const
|
|
5550
|
-
|
|
5551
|
-
|
|
5552
|
-
|
|
5553
|
-
|
|
5554
|
-
spreadsheetElement = document.querySelector(".o-spreadsheet");
|
|
5555
|
-
}
|
|
5556
|
-
if (spreadsheetElement) {
|
|
5557
|
-
const { top, left, width, height } = spreadsheetElement.getBoundingClientRect();
|
|
5558
|
-
position.x = left;
|
|
5559
|
-
position.y = top;
|
|
5560
|
-
position.width = width;
|
|
5561
|
-
position.height = height;
|
|
5617
|
+
function chartFactory(getters) {
|
|
5618
|
+
const builders = chartRegistry.getAll().sort((a, b) => a.sequence - b.sequence);
|
|
5619
|
+
function createChart(id, definition, sheetId) {
|
|
5620
|
+
const builder = builders.find((builder) => builder.match(definition.type));
|
|
5621
|
+
if (!builder) {
|
|
5622
|
+
throw new Error(`No builder for this chart: ${definition.type}`);
|
|
5562
5623
|
}
|
|
5624
|
+
return builder.createChart(definition, sheetId, getters);
|
|
5563
5625
|
}
|
|
5564
|
-
|
|
5565
|
-
owl.onPatched(updatePosition);
|
|
5566
|
-
return position;
|
|
5626
|
+
return createChart;
|
|
5567
5627
|
}
|
|
5568
5628
|
/**
|
|
5569
|
-
*
|
|
5570
|
-
*
|
|
5571
|
-
*
|
|
5572
|
-
* Note: when used with a <Portal/> component, it will
|
|
5573
|
-
* return the portal position, not the teleported position.
|
|
5629
|
+
* Create a function used to create a Chart Runtime based on the chart class
|
|
5630
|
+
* instance
|
|
5574
5631
|
*/
|
|
5575
|
-
function
|
|
5576
|
-
const
|
|
5577
|
-
function
|
|
5578
|
-
const
|
|
5579
|
-
if (
|
|
5580
|
-
|
|
5632
|
+
function chartRuntimeFactory(getters) {
|
|
5633
|
+
const builders = chartRegistry.getAll().sort((a, b) => a.sequence - b.sequence);
|
|
5634
|
+
function createRuntimeChart(chart) {
|
|
5635
|
+
const builder = builders.find((builder) => builder.match(chart.type));
|
|
5636
|
+
if (!builder) {
|
|
5637
|
+
throw new Error("No runtime builder for this chart.");
|
|
5581
5638
|
}
|
|
5582
|
-
|
|
5583
|
-
rect.x = left;
|
|
5584
|
-
rect.y = top;
|
|
5585
|
-
rect.width = width;
|
|
5586
|
-
rect.height = height;
|
|
5639
|
+
return builder.getChartRuntime(chart, getters);
|
|
5587
5640
|
}
|
|
5588
|
-
|
|
5589
|
-
owl.onPatched(updateElRect);
|
|
5590
|
-
return rect;
|
|
5641
|
+
return createRuntimeChart;
|
|
5591
5642
|
}
|
|
5592
5643
|
/**
|
|
5593
|
-
*
|
|
5594
|
-
* It's the value defined in `env.getPopoverContainerRect`, or the Rect of the "o-spreadsheet"
|
|
5595
|
-
* element by default.
|
|
5596
|
-
*
|
|
5597
|
-
* Coordinates are expressed expressed as absolute DOM position.
|
|
5644
|
+
* Validate the chart definition given in arguments
|
|
5598
5645
|
*/
|
|
5599
|
-
function
|
|
5600
|
-
const
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
function updateRect() {
|
|
5604
|
-
const env = component.env;
|
|
5605
|
-
const newRect = "getPopoverContainerRect" in env ? env.getPopoverContainerRect() : spreadsheetRect;
|
|
5606
|
-
container.x = newRect.x;
|
|
5607
|
-
container.y = newRect.y;
|
|
5608
|
-
container.width = newRect.width;
|
|
5609
|
-
container.height = newRect.height;
|
|
5646
|
+
function validateChartDefinition(validator, definition) {
|
|
5647
|
+
const validators = chartRegistry.getAll().find((validator) => validator.match(definition.type));
|
|
5648
|
+
if (!validators) {
|
|
5649
|
+
throw new Error("Unknown chart type.");
|
|
5610
5650
|
}
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
owl.onPatched(updateRect);
|
|
5614
|
-
return container;
|
|
5615
|
-
}
|
|
5616
|
-
|
|
5651
|
+
return validators.validateChartDefinition(validator, definition);
|
|
5652
|
+
}
|
|
5617
5653
|
/**
|
|
5618
|
-
*
|
|
5619
|
-
*
|
|
5654
|
+
* Get a new chart definition transformed with the executed command. This
|
|
5655
|
+
* functions will be called during operational transform process
|
|
5620
5656
|
*/
|
|
5621
|
-
function
|
|
5622
|
-
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
const spreadsheetElement = document.querySelector(".o-grid-overlay");
|
|
5626
|
-
if (spreadsheetElement) {
|
|
5627
|
-
const { top, left } = spreadsheetElement === null || spreadsheetElement === void 0 ? void 0 : spreadsheetElement.getBoundingClientRect();
|
|
5628
|
-
return { top, left };
|
|
5657
|
+
function transformDefinition(definition, executed) {
|
|
5658
|
+
const transformation = chartRegistry.getAll().find((factory) => factory.match(definition.type));
|
|
5659
|
+
if (!transformation) {
|
|
5660
|
+
throw new Error("Unknown chart type.");
|
|
5629
5661
|
}
|
|
5630
|
-
|
|
5631
|
-
}
|
|
5632
|
-
function getBoundingRectAsPOJO(el) {
|
|
5633
|
-
const rect = el.getBoundingClientRect();
|
|
5634
|
-
return {
|
|
5635
|
-
x: rect.x,
|
|
5636
|
-
y: rect.y,
|
|
5637
|
-
width: rect.width,
|
|
5638
|
-
height: rect.height,
|
|
5639
|
-
};
|
|
5662
|
+
return transformation.transformDefinition(definition, executed);
|
|
5640
5663
|
}
|
|
5641
5664
|
/**
|
|
5642
|
-
*
|
|
5665
|
+
* Get an empty definition based on the given context and the given type
|
|
5643
5666
|
*/
|
|
5644
|
-
function
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
for (let child of el.childNodes) {
|
|
5648
|
-
yield* iterateChildren(child);
|
|
5649
|
-
}
|
|
5650
|
-
}
|
|
5651
|
-
}
|
|
5652
|
-
function getElementScrollTop(el) {
|
|
5653
|
-
return (el === null || el === void 0 ? void 0 : el.scrollTop) || 0;
|
|
5667
|
+
function getChartDefinitionFromContextCreation(context, type) {
|
|
5668
|
+
const chartClass = chartRegistry.get(type);
|
|
5669
|
+
return chartClass.getChartDefinitionFromContextCreation(context);
|
|
5654
5670
|
}
|
|
5655
|
-
function
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
|
|
5671
|
+
function getChartTypes() {
|
|
5672
|
+
const result = {};
|
|
5673
|
+
for (const key of chartRegistry.getKeys()) {
|
|
5674
|
+
result[key] = chartRegistry.get(key).name;
|
|
5675
|
+
}
|
|
5676
|
+
return result;
|
|
5659
5677
|
}
|
|
5660
|
-
function getOpenedMenus() {
|
|
5661
|
-
return Array.from(document.querySelectorAll(".o-spreadsheet .o-menu"));
|
|
5662
|
-
}
|
|
5663
|
-
|
|
5664
5678
|
/**
|
|
5665
|
-
*
|
|
5679
|
+
* Return a "smart" chart definition in the given zone. The definition is "smart" because it will
|
|
5680
|
+
* use the best type of chart to display the data of the zone.
|
|
5681
|
+
*
|
|
5682
|
+
* It will also try to find labels and datasets in the range, and try to find title for the datasets.
|
|
5683
|
+
*
|
|
5684
|
+
* The type of chart will be :
|
|
5685
|
+
* - If the zone is a single non-empty cell, returns a scorecard
|
|
5686
|
+
* - If the all the labels are numbers/date, returns a line chart
|
|
5687
|
+
* - Else returns a bar chart
|
|
5666
5688
|
*/
|
|
5667
|
-
function
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5689
|
+
function getSmartChartDefinition(zone, getters) {
|
|
5690
|
+
var _a;
|
|
5691
|
+
let dataSetZone = zone;
|
|
5692
|
+
if (zone.left !== zone.right) {
|
|
5693
|
+
dataSetZone = { ...zone, left: zone.left + 1 };
|
|
5694
|
+
}
|
|
5695
|
+
const dataSets = [zoneToXc(dataSetZone)];
|
|
5696
|
+
const sheetId = getters.getActiveSheetId();
|
|
5697
|
+
const topLeftCell = getters.getCell({ sheetId, col: zone.left, row: zone.top });
|
|
5698
|
+
if (getZoneArea(zone) === 1 && (topLeftCell === null || topLeftCell === void 0 ? void 0 : topLeftCell.content)) {
|
|
5699
|
+
return {
|
|
5700
|
+
type: "scorecard",
|
|
5701
|
+
title: "",
|
|
5702
|
+
background: ((_a = topLeftCell.style) === null || _a === void 0 ? void 0 : _a.fillColor) || undefined,
|
|
5703
|
+
keyValue: zoneToXc(zone),
|
|
5704
|
+
baselineMode: DEFAULT_SCORECARD_BASELINE_MODE,
|
|
5705
|
+
baselineColorUp: DEFAULT_SCORECARD_BASELINE_COLOR_UP,
|
|
5706
|
+
baselineColorDown: DEFAULT_SCORECARD_BASELINE_COLOR_DOWN,
|
|
5707
|
+
};
|
|
5708
|
+
}
|
|
5709
|
+
let title = "";
|
|
5710
|
+
const cellsInFirstRow = getters.getEvaluatedCellsInZone(sheetId, {
|
|
5711
|
+
...dataSetZone,
|
|
5712
|
+
bottom: dataSetZone.top,
|
|
5713
|
+
});
|
|
5714
|
+
const dataSetsHaveTitle = !!cellsInFirstRow.find((cell) => cell.type !== CellValueType.empty && cell.type !== CellValueType.number);
|
|
5715
|
+
if (dataSetsHaveTitle) {
|
|
5716
|
+
const texts = cellsInFirstRow
|
|
5717
|
+
.filter((cell) => cell.type !== CellValueType.error && cell.type !== CellValueType.empty)
|
|
5718
|
+
.map((cell) => cell.formattedValue);
|
|
5719
|
+
const lastElement = texts.splice(-1)[0];
|
|
5720
|
+
title = texts.join(", ");
|
|
5721
|
+
if (lastElement) {
|
|
5722
|
+
title += (title ? " " + _t("and") + " " : "") + lastElement;
|
|
5723
|
+
}
|
|
5724
|
+
}
|
|
5725
|
+
let labelRangeXc;
|
|
5726
|
+
if (zone.left !== zone.right) {
|
|
5727
|
+
labelRangeXc = zoneToXc({
|
|
5728
|
+
...zone,
|
|
5729
|
+
right: zone.left,
|
|
5730
|
+
top: dataSetsHaveTitle ? zone.top + 1 : zone.top,
|
|
5731
|
+
});
|
|
5732
|
+
}
|
|
5733
|
+
// Only display legend for several datasets.
|
|
5734
|
+
const newLegendPos = dataSetZone.right === dataSetZone.left ? "none" : "top";
|
|
5735
|
+
const labelRange = labelRangeXc ? getters.getRangeFromSheetXC(sheetId, labelRangeXc) : undefined;
|
|
5736
|
+
if (canChartParseLabels(labelRange, getters)) {
|
|
5737
|
+
return {
|
|
5738
|
+
title,
|
|
5739
|
+
dataSets,
|
|
5740
|
+
labelsAsText: false,
|
|
5741
|
+
stacked: false,
|
|
5742
|
+
aggregated: false,
|
|
5743
|
+
labelRange: labelRangeXc,
|
|
5744
|
+
type: "line",
|
|
5745
|
+
dataSetsHaveTitle,
|
|
5746
|
+
verticalAxisPosition: "left",
|
|
5747
|
+
legendPosition: newLegendPos,
|
|
5748
|
+
};
|
|
5749
|
+
}
|
|
5681
5750
|
return {
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
|
|
5751
|
+
title,
|
|
5752
|
+
dataSets,
|
|
5753
|
+
labelRange: labelRangeXc,
|
|
5754
|
+
type: "bar",
|
|
5755
|
+
stacked: false,
|
|
5756
|
+
aggregated: false,
|
|
5757
|
+
dataSetsHaveTitle,
|
|
5758
|
+
verticalAxisPosition: "left",
|
|
5759
|
+
legendPosition: newLegendPos,
|
|
5686
5760
|
};
|
|
5687
5761
|
}
|
|
5688
5762
|
|
|
5689
|
-
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
|
|
5696
|
-
|
|
5763
|
+
function centerFigurePosition(getters, size) {
|
|
5764
|
+
const { x: offsetCorrectionX, y: offsetCorrectionY } = getters.getMainViewportCoordinates();
|
|
5765
|
+
const { scrollX, scrollY } = getters.getActiveSheetScrollInfo();
|
|
5766
|
+
const dim = getters.getSheetViewDimension();
|
|
5767
|
+
const rect = getters.getVisibleRect(getters.getActiveMainViewport());
|
|
5768
|
+
const scrollableViewportWidth = Math.min(rect.width, dim.width - offsetCorrectionX);
|
|
5769
|
+
const scrollableViewportHeight = Math.min(rect.height, dim.height - offsetCorrectionY);
|
|
5770
|
+
const position = {
|
|
5771
|
+
x: offsetCorrectionX + scrollX + Math.max(0, (scrollableViewportWidth - size.width) / 2),
|
|
5772
|
+
y: offsetCorrectionY + scrollY + Math.max(0, (scrollableViewportHeight - size.height) / 2),
|
|
5773
|
+
}; // Position at the center of the scrollable viewport
|
|
5774
|
+
return position;
|
|
5775
|
+
}
|
|
5776
|
+
function getMaxFigureSize(getters, figureSize) {
|
|
5777
|
+
const size = deepCopy(figureSize);
|
|
5778
|
+
const dim = getters.getSheetViewDimension();
|
|
5779
|
+
const maxWidth = dim.width;
|
|
5780
|
+
const maxHeight = dim.height;
|
|
5781
|
+
if (size.width > maxWidth) {
|
|
5782
|
+
const ratio = maxWidth / size.width;
|
|
5783
|
+
size.width = maxWidth;
|
|
5784
|
+
size.height = size.height * ratio;
|
|
5785
|
+
}
|
|
5786
|
+
if (size.height > maxHeight) {
|
|
5787
|
+
const ratio = maxHeight / size.height;
|
|
5788
|
+
size.height = maxHeight;
|
|
5789
|
+
size.width = size.width * ratio;
|
|
5790
|
+
}
|
|
5791
|
+
return size;
|
|
5697
5792
|
}
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5793
|
+
|
|
5794
|
+
const SORT_TYPES = [
|
|
5795
|
+
CellValueType.number,
|
|
5796
|
+
CellValueType.error,
|
|
5797
|
+
CellValueType.text,
|
|
5798
|
+
CellValueType.boolean,
|
|
5799
|
+
];
|
|
5800
|
+
function sortCells(cells, sortDirection, emptyCellAsZero) {
|
|
5801
|
+
const cellsWithIndex = cells.map((cell, index) => ({
|
|
5802
|
+
index,
|
|
5803
|
+
type: cell.type,
|
|
5804
|
+
value: cell.value,
|
|
5805
|
+
}));
|
|
5806
|
+
let emptyCells = cellsWithIndex.filter((x) => x.type === CellValueType.empty);
|
|
5807
|
+
let nonEmptyCells = cellsWithIndex.filter((x) => x.type !== CellValueType.empty);
|
|
5808
|
+
if (emptyCellAsZero) {
|
|
5809
|
+
nonEmptyCells.push(...emptyCells.map((emptyCell) => ({ ...emptyCell, type: CellValueType.number, value: 0 })));
|
|
5810
|
+
emptyCells = [];
|
|
5706
5811
|
}
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
throw new Error("Popover container is not defined");
|
|
5715
|
-
const el = this.popoverRef.el;
|
|
5716
|
-
const anchor = rectIntersection(this.props.anchorRect, this.containerRect);
|
|
5717
|
-
const newDisplay = anchor ? "block" : "none";
|
|
5718
|
-
if (this.currentDisplayValue !== "none" && newDisplay === "none") {
|
|
5719
|
-
(_b = (_a = this.props).onPopoverHidden) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
5812
|
+
const inverse = sortDirection === "descending" ? -1 : 1;
|
|
5813
|
+
return nonEmptyCells
|
|
5814
|
+
.sort((left, right) => {
|
|
5815
|
+
let typeOrder = SORT_TYPES.indexOf(left.type) - SORT_TYPES.indexOf(right.type);
|
|
5816
|
+
if (typeOrder === 0) {
|
|
5817
|
+
if (left.type === CellValueType.text || left.type === CellValueType.error) {
|
|
5818
|
+
typeOrder = left.value.localeCompare(right.value);
|
|
5720
5819
|
}
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
5725
|
-
|
|
5726
|
-
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
5820
|
+
else
|
|
5821
|
+
typeOrder = left.value - right.value;
|
|
5822
|
+
}
|
|
5823
|
+
return inverse * typeOrder;
|
|
5824
|
+
})
|
|
5825
|
+
.concat(emptyCells);
|
|
5826
|
+
}
|
|
5827
|
+
function interactiveSortSelection(env, sheetId, anchor, zone, sortDirection) {
|
|
5828
|
+
let result = DispatchResult.Success;
|
|
5829
|
+
//several columns => bypass the contiguity check
|
|
5830
|
+
let multiColumns = zone.right > zone.left;
|
|
5831
|
+
if (env.model.getters.doesIntersectMerge(sheetId, zone)) {
|
|
5832
|
+
multiColumns = false;
|
|
5833
|
+
let table;
|
|
5834
|
+
for (let row = zone.top; row <= zone.bottom; row++) {
|
|
5835
|
+
table = [];
|
|
5836
|
+
for (let col = zone.left; col <= zone.right; col++) {
|
|
5837
|
+
let merge = env.model.getters.getMerge({ sheetId, col, row });
|
|
5838
|
+
if (merge && !table.includes(merge.id.toString())) {
|
|
5839
|
+
table.push(merge.id.toString());
|
|
5840
|
+
}
|
|
5737
5841
|
}
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
5842
|
+
if (table.length >= 2) {
|
|
5843
|
+
multiColumns = true;
|
|
5844
|
+
break;
|
|
5741
5845
|
}
|
|
5742
|
-
|
|
5743
|
-
});
|
|
5744
|
-
}
|
|
5745
|
-
}
|
|
5746
|
-
Popover.template = "o-spreadsheet-Popover";
|
|
5747
|
-
Popover.defaultProps = {
|
|
5748
|
-
positioning: "BottomLeft",
|
|
5749
|
-
verticalOffset: 0,
|
|
5750
|
-
onMouseWheel: () => { },
|
|
5751
|
-
onPopoverMoved: () => { },
|
|
5752
|
-
onPopoverHidden: () => { },
|
|
5753
|
-
};
|
|
5754
|
-
Popover.props = {
|
|
5755
|
-
anchorRect: Object,
|
|
5756
|
-
containerRect: { type: Object, optional: true },
|
|
5757
|
-
positioning: { type: String, optional: true },
|
|
5758
|
-
maxWidth: { type: Number, optional: true },
|
|
5759
|
-
maxHeight: { type: Number, optional: true },
|
|
5760
|
-
verticalOffset: { type: Number, optional: true },
|
|
5761
|
-
onMouseWheel: { type: Function, optional: true },
|
|
5762
|
-
onPopoverHidden: { type: Function, optional: true },
|
|
5763
|
-
onPopoverMoved: { type: Function, optional: true },
|
|
5764
|
-
slots: Object,
|
|
5765
|
-
};
|
|
5766
|
-
class PopoverPositionContext {
|
|
5767
|
-
constructor(anchorRect, containerRect, propsMaxSize, spreadsheetOffset) {
|
|
5768
|
-
this.anchorRect = anchorRect;
|
|
5769
|
-
this.containerRect = containerRect;
|
|
5770
|
-
this.propsMaxSize = propsMaxSize;
|
|
5771
|
-
this.spreadsheetOffset = spreadsheetOffset;
|
|
5846
|
+
}
|
|
5772
5847
|
}
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
|
|
5776
|
-
this.availableHeightDown >= this.availableHeightUp);
|
|
5848
|
+
const { col, row } = anchor;
|
|
5849
|
+
if (multiColumns) {
|
|
5850
|
+
result = env.model.dispatch("SORT_CELLS", { sheetId, col, row, zone, sortDirection });
|
|
5777
5851
|
}
|
|
5778
|
-
|
|
5779
|
-
|
|
5780
|
-
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
5797
|
-
|
|
5798
|
-
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
|
|
5804
|
-
|
|
5805
|
-
|
|
5806
|
-
|
|
5807
|
-
|
|
5808
|
-
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
this.spreadsheetOffset.y -
|
|
5812
|
-
verticalOffset +
|
|
5813
|
-
"px",
|
|
5814
|
-
left: this.getLeftCoordinate(actualWidth, shouldRenderAtRight) - this.spreadsheetOffset.x + "px",
|
|
5815
|
-
};
|
|
5816
|
-
return cssProperties;
|
|
5817
|
-
}
|
|
5818
|
-
getCurrentPosition(elDims) {
|
|
5819
|
-
const shouldRenderAtBottom = this.shouldRenderAtBottom(elDims.height);
|
|
5820
|
-
const shouldRenderAtRight = this.shouldRenderAtRight(elDims.width);
|
|
5821
|
-
if (shouldRenderAtBottom && shouldRenderAtRight)
|
|
5822
|
-
return "BottomRight";
|
|
5823
|
-
if (shouldRenderAtBottom && !shouldRenderAtRight)
|
|
5824
|
-
return "BottomLeft";
|
|
5825
|
-
if (!shouldRenderAtBottom && shouldRenderAtRight)
|
|
5826
|
-
return "TopRight";
|
|
5827
|
-
return "TopLeft";
|
|
5828
|
-
}
|
|
5829
|
-
}
|
|
5830
|
-
class BottomLeftPopoverContext extends PopoverPositionContext {
|
|
5831
|
-
get availableHeightUp() {
|
|
5832
|
-
return this.anchorRect.y - this.containerRect.y;
|
|
5833
|
-
}
|
|
5834
|
-
get availableHeightDown() {
|
|
5835
|
-
return this.containerRect.height - this.availableHeightUp - this.anchorRect.height;
|
|
5836
|
-
}
|
|
5837
|
-
get availableWidthRight() {
|
|
5838
|
-
return this.containerRect.x + this.containerRect.width - this.anchorRect.x;
|
|
5839
|
-
}
|
|
5840
|
-
get availableWidthLeft() {
|
|
5841
|
-
return this.anchorRect.x + this.anchorRect.width - this.containerRect.x;
|
|
5842
|
-
}
|
|
5843
|
-
getTopCoordinate(elementHeight, shouldRenderAtBottom) {
|
|
5844
|
-
if (shouldRenderAtBottom) {
|
|
5845
|
-
return this.anchorRect.y + this.anchorRect.height;
|
|
5846
|
-
}
|
|
5847
|
-
else {
|
|
5848
|
-
return this.anchorRect.y - elementHeight;
|
|
5849
|
-
}
|
|
5850
|
-
}
|
|
5851
|
-
getLeftCoordinate(elementWidth, shouldRenderAtRight) {
|
|
5852
|
-
if (shouldRenderAtRight) {
|
|
5853
|
-
return this.anchorRect.x;
|
|
5854
|
-
}
|
|
5855
|
-
else {
|
|
5856
|
-
return this.anchorRect.x + this.anchorRect.width - elementWidth;
|
|
5857
|
-
}
|
|
5858
|
-
}
|
|
5859
|
-
}
|
|
5860
|
-
class TopRightPopoverContext extends PopoverPositionContext {
|
|
5861
|
-
get availableHeightUp() {
|
|
5862
|
-
return this.anchorRect.y + this.anchorRect.height - this.containerRect.y;
|
|
5863
|
-
}
|
|
5864
|
-
get availableHeightDown() {
|
|
5865
|
-
return this.containerRect.y + this.containerRect.height - this.anchorRect.y;
|
|
5866
|
-
}
|
|
5867
|
-
get availableWidthRight() {
|
|
5868
|
-
return this.containerRect.width - this.anchorRect.width - this.availableWidthLeft;
|
|
5869
|
-
}
|
|
5870
|
-
get availableWidthLeft() {
|
|
5871
|
-
return this.anchorRect.x - this.containerRect.x;
|
|
5872
|
-
}
|
|
5873
|
-
getTopCoordinate(elementHeight, shouldRenderAtBottom) {
|
|
5874
|
-
if (shouldRenderAtBottom) {
|
|
5875
|
-
return this.anchorRect.y;
|
|
5876
|
-
}
|
|
5877
|
-
else {
|
|
5878
|
-
return this.anchorRect.y + this.anchorRect.height - elementHeight;
|
|
5879
|
-
}
|
|
5880
|
-
}
|
|
5881
|
-
getLeftCoordinate(elementWidth, shouldRenderAtRight) {
|
|
5882
|
-
if (shouldRenderAtRight) {
|
|
5883
|
-
return this.anchorRect.x + this.anchorRect.width;
|
|
5884
|
-
}
|
|
5885
|
-
else {
|
|
5886
|
-
return this.anchorRect.x - elementWidth;
|
|
5887
|
-
}
|
|
5888
|
-
}
|
|
5889
|
-
}
|
|
5890
|
-
|
|
5891
|
-
//------------------------------------------------------------------------------
|
|
5892
|
-
// Context Menu Component
|
|
5893
|
-
//------------------------------------------------------------------------------
|
|
5894
|
-
css /* scss */ `
|
|
5895
|
-
.o-menu {
|
|
5896
|
-
background-color: white;
|
|
5897
|
-
padding: ${MENU_VERTICAL_PADDING}px 0px;
|
|
5898
|
-
width: ${MENU_WIDTH}px;
|
|
5899
|
-
box-sizing: border-box !important;
|
|
5900
|
-
|
|
5901
|
-
.o-menu-item {
|
|
5902
|
-
display: flex;
|
|
5903
|
-
justify-content: space-between;
|
|
5904
|
-
align-items: center;
|
|
5905
|
-
box-sizing: border-box;
|
|
5906
|
-
height: ${MENU_ITEM_HEIGHT}px;
|
|
5907
|
-
padding: 4px 16px;
|
|
5908
|
-
cursor: pointer;
|
|
5909
|
-
user-select: none;
|
|
5910
|
-
|
|
5911
|
-
.o-menu-item-name {
|
|
5912
|
-
overflow: hidden;
|
|
5913
|
-
white-space: nowrap;
|
|
5914
|
-
text-overflow: ellipsis;
|
|
5915
|
-
}
|
|
5916
|
-
|
|
5917
|
-
&.o-menu-root {
|
|
5918
|
-
display: flex;
|
|
5919
|
-
justify-content: space-between;
|
|
5920
|
-
}
|
|
5921
|
-
.o-menu-item-icon {
|
|
5922
|
-
margin-top: auto;
|
|
5923
|
-
margin-bottom: auto;
|
|
5924
|
-
}
|
|
5925
|
-
.o-icon {
|
|
5926
|
-
width: 10px;
|
|
5927
|
-
}
|
|
5928
|
-
|
|
5929
|
-
&:not(.disabled) {
|
|
5930
|
-
&:hover,
|
|
5931
|
-
&.o-menu-item-active {
|
|
5932
|
-
background-color: #ebebeb;
|
|
5933
|
-
}
|
|
5934
|
-
.o-menu-item-description {
|
|
5935
|
-
color: grey;
|
|
5936
|
-
}
|
|
5937
|
-
}
|
|
5938
|
-
&.disabled {
|
|
5939
|
-
color: ${MENU_ITEM_DISABLED_COLOR};
|
|
5940
|
-
cursor: not-allowed;
|
|
5941
|
-
}
|
|
5942
|
-
}
|
|
5943
|
-
}
|
|
5944
|
-
`;
|
|
5945
|
-
class Menu extends owl.Component {
|
|
5946
|
-
constructor() {
|
|
5947
|
-
super(...arguments);
|
|
5948
|
-
this.subMenu = owl.useState({
|
|
5949
|
-
isOpen: false,
|
|
5950
|
-
position: null,
|
|
5951
|
-
scrollOffset: 0,
|
|
5952
|
-
menuItems: [],
|
|
5953
|
-
});
|
|
5954
|
-
this.menuRef = owl.useRef("menu");
|
|
5955
|
-
this.position = useAbsoluteBoundingRect(this.menuRef);
|
|
5956
|
-
}
|
|
5957
|
-
setup() {
|
|
5958
|
-
owl.useExternalListener(window, "click", this.onExternalClick, { capture: true });
|
|
5959
|
-
owl.useExternalListener(window, "contextmenu", this.onExternalClick, { capture: true });
|
|
5960
|
-
owl.onWillUpdateProps((nextProps) => {
|
|
5961
|
-
if (nextProps.menuItems !== this.props.menuItems) {
|
|
5962
|
-
this.closeSubMenu();
|
|
5963
|
-
}
|
|
5964
|
-
});
|
|
5965
|
-
}
|
|
5966
|
-
get visibleMenuItems() {
|
|
5967
|
-
return this.props.menuItems.filter((x) => x.isVisible(this.env));
|
|
5968
|
-
}
|
|
5969
|
-
get subMenuPosition() {
|
|
5970
|
-
const position = Object.assign({}, this.subMenu.position);
|
|
5971
|
-
position.y -= this.subMenu.scrollOffset || 0;
|
|
5972
|
-
return position;
|
|
5973
|
-
}
|
|
5974
|
-
get menuHeight() {
|
|
5975
|
-
const menuItems = this.visibleMenuItems;
|
|
5976
|
-
let menuItemsHeight = this.getMenuItemsHeight(menuItems);
|
|
5977
|
-
// We don't display separator at the end of a menu
|
|
5978
|
-
if (menuItems[menuItems.length - 1].separator) {
|
|
5979
|
-
menuItemsHeight -= MENU_SEPARATOR_HEIGHT;
|
|
5980
|
-
}
|
|
5981
|
-
const menuHeight = 2 * MENU_VERTICAL_PADDING + menuItemsHeight;
|
|
5982
|
-
return this.props.maxHeight ? Math.min(menuHeight, this.props.maxHeight) : menuHeight;
|
|
5983
|
-
}
|
|
5984
|
-
get popoverProps() {
|
|
5985
|
-
const isRoot = this.props.depth === 1;
|
|
5986
|
-
return {
|
|
5987
|
-
anchorRect: {
|
|
5988
|
-
x: this.props.position.x - MENU_WIDTH * (this.props.depth - 1),
|
|
5989
|
-
y: this.props.position.y,
|
|
5990
|
-
width: isRoot ? 0 : MENU_WIDTH,
|
|
5991
|
-
height: isRoot ? 0 : MENU_ITEM_HEIGHT,
|
|
5992
|
-
},
|
|
5993
|
-
positioning: "TopRight",
|
|
5994
|
-
verticalOffset: isRoot ? 0 : MENU_VERTICAL_PADDING,
|
|
5995
|
-
onPopoverHidden: () => this.closeSubMenu(),
|
|
5996
|
-
onPopoverMoved: () => this.closeSubMenu(),
|
|
5997
|
-
maxHeight: this.menuHeight,
|
|
5998
|
-
};
|
|
5999
|
-
}
|
|
6000
|
-
getColor(menu) {
|
|
6001
|
-
return menu.textColor ? `color: ${menu.textColor}` : undefined;
|
|
6002
|
-
}
|
|
6003
|
-
async activateMenu(menu) {
|
|
6004
|
-
var _a, _b, _c;
|
|
6005
|
-
const result = await ((_a = menu.action) === null || _a === void 0 ? void 0 : _a.call(menu, this.env));
|
|
6006
|
-
this.close();
|
|
6007
|
-
(_c = (_b = this.props).onMenuClicked) === null || _c === void 0 ? void 0 : _c.call(_b, { detail: result });
|
|
6008
|
-
}
|
|
6009
|
-
close() {
|
|
6010
|
-
this.closeSubMenu();
|
|
6011
|
-
this.props.onClose();
|
|
6012
|
-
}
|
|
6013
|
-
/**
|
|
6014
|
-
* Return the number of pixels between the top of the menu
|
|
6015
|
-
* and the menu item at a given index.
|
|
6016
|
-
*/
|
|
6017
|
-
subMenuVerticalPosition(menuIndex) {
|
|
6018
|
-
const menusAbove = this.visibleMenuItems.slice(0, menuIndex);
|
|
6019
|
-
return this.position.y + this.getMenuItemsHeight(menusAbove) + MENU_VERTICAL_PADDING;
|
|
6020
|
-
}
|
|
6021
|
-
onExternalClick(ev) {
|
|
6022
|
-
// Don't close a root menu when clicked to open the submenus.
|
|
6023
|
-
const el = this.menuRef.el;
|
|
6024
|
-
if (el && getOpenedMenus().some((el) => isChildEvent(el, ev))) {
|
|
6025
|
-
return;
|
|
6026
|
-
}
|
|
6027
|
-
ev.closedMenuId = this.props.menuId;
|
|
6028
|
-
this.close();
|
|
6029
|
-
}
|
|
6030
|
-
getMenuItemsHeight(menuItems) {
|
|
6031
|
-
const numberOfSeparators = menuItems.filter((m) => m.separator).length;
|
|
6032
|
-
return MENU_ITEM_HEIGHT * menuItems.length + MENU_SEPARATOR_HEIGHT * numberOfSeparators;
|
|
6033
|
-
}
|
|
6034
|
-
getName(menu) {
|
|
6035
|
-
return menu.name(this.env);
|
|
6036
|
-
}
|
|
6037
|
-
isRoot(menu) {
|
|
6038
|
-
return !menu.action;
|
|
6039
|
-
}
|
|
6040
|
-
isEnabled(menu) {
|
|
6041
|
-
if (menu.isEnabled(this.env)) {
|
|
6042
|
-
return this.env.model.getters.isReadonly() ? menu.isReadonlyAllowed : true;
|
|
6043
|
-
}
|
|
6044
|
-
return false;
|
|
6045
|
-
}
|
|
6046
|
-
onScroll(ev) {
|
|
6047
|
-
this.subMenu.scrollOffset = ev.target.scrollTop;
|
|
6048
|
-
}
|
|
6049
|
-
/**
|
|
6050
|
-
* If the given menu is not disabled, open it's submenu at the
|
|
6051
|
-
* correct position according to available surrounding space.
|
|
6052
|
-
*/
|
|
6053
|
-
openSubMenu(menu, menuIndex) {
|
|
6054
|
-
const y = this.subMenuVerticalPosition(menuIndex);
|
|
6055
|
-
this.subMenu.position = {
|
|
6056
|
-
x: this.position.x + this.props.depth * MENU_WIDTH,
|
|
6057
|
-
y: y - (this.subMenu.scrollOffset || 0),
|
|
6058
|
-
};
|
|
6059
|
-
this.subMenu.menuItems = menu.children(this.env);
|
|
6060
|
-
this.subMenu.isOpen = true;
|
|
6061
|
-
this.subMenu.parentMenu = menu;
|
|
6062
|
-
}
|
|
6063
|
-
isParentMenu(subMenu, menuItem) {
|
|
6064
|
-
var _a;
|
|
6065
|
-
return ((_a = subMenu.parentMenu) === null || _a === void 0 ? void 0 : _a.id) === menuItem.id;
|
|
6066
|
-
}
|
|
6067
|
-
closeSubMenu() {
|
|
6068
|
-
this.subMenu.isOpen = false;
|
|
6069
|
-
this.subMenu.parentMenu = undefined;
|
|
6070
|
-
}
|
|
6071
|
-
onClickMenu(menu, menuIndex) {
|
|
6072
|
-
if (this.isEnabled(menu)) {
|
|
6073
|
-
if (this.isRoot(menu)) {
|
|
6074
|
-
this.openSubMenu(menu, menuIndex);
|
|
6075
|
-
}
|
|
6076
|
-
else {
|
|
6077
|
-
this.activateMenu(menu);
|
|
6078
|
-
}
|
|
6079
|
-
}
|
|
6080
|
-
}
|
|
6081
|
-
onMouseOver(menu, position) {
|
|
6082
|
-
if (menu.isEnabled(this.env)) {
|
|
6083
|
-
if (this.isRoot(menu)) {
|
|
6084
|
-
this.openSubMenu(menu, position);
|
|
6085
|
-
}
|
|
6086
|
-
else {
|
|
6087
|
-
this.closeSubMenu();
|
|
6088
|
-
}
|
|
6089
|
-
}
|
|
6090
|
-
}
|
|
6091
|
-
}
|
|
6092
|
-
Menu.template = "o-spreadsheet-Menu";
|
|
6093
|
-
Menu.components = { Menu, Popover };
|
|
6094
|
-
Menu.defaultProps = {
|
|
6095
|
-
depth: 1,
|
|
6096
|
-
};
|
|
6097
|
-
Menu.props = {
|
|
6098
|
-
position: Object,
|
|
6099
|
-
menuItems: Array,
|
|
6100
|
-
depth: { type: Number, optional: true },
|
|
6101
|
-
maxHeight: { type: Number, optional: true },
|
|
6102
|
-
onClose: Function,
|
|
6103
|
-
onMenuClicked: { type: Function, optional: true },
|
|
6104
|
-
menuId: { type: String, optional: true },
|
|
6105
|
-
};
|
|
6106
|
-
|
|
6107
|
-
// -----------------------------------------------------------------------------
|
|
6108
|
-
// STYLE
|
|
6109
|
-
// -----------------------------------------------------------------------------
|
|
6110
|
-
css /* scss */ `
|
|
6111
|
-
.o-chart-container {
|
|
6112
|
-
width: 100%;
|
|
6113
|
-
height: 100%;
|
|
6114
|
-
position: relative;
|
|
6115
|
-
}
|
|
6116
|
-
`;
|
|
6117
|
-
class ChartFigure extends owl.Component {
|
|
6118
|
-
constructor() {
|
|
6119
|
-
super(...arguments);
|
|
6120
|
-
this.menuState = owl.useState({ isOpen: false, position: null, menuItems: [] });
|
|
6121
|
-
this.chartContainerRef = owl.useRef("chartContainer");
|
|
6122
|
-
this.menuButtonRef = owl.useRef("menuButton");
|
|
6123
|
-
this.menuButtonRect = useAbsoluteBoundingRect(this.menuButtonRef);
|
|
6124
|
-
this.position = useAbsoluteBoundingRect(this.chartContainerRef);
|
|
6125
|
-
}
|
|
6126
|
-
getMenuItemRegistry() {
|
|
6127
|
-
const registry = new MenuItemRegistry();
|
|
6128
|
-
registry.add("edit", {
|
|
6129
|
-
name: _lt("Edit"),
|
|
6130
|
-
sequence: 1,
|
|
6131
|
-
action: () => {
|
|
6132
|
-
this.env.model.dispatch("SELECT_FIGURE", { id: this.props.figure.id });
|
|
6133
|
-
this.env.openSidePanel("ChartPanel");
|
|
6134
|
-
},
|
|
6135
|
-
});
|
|
6136
|
-
registry.add("copy", {
|
|
6137
|
-
name: _lt("Copy"),
|
|
6138
|
-
sequence: 2,
|
|
6139
|
-
action: async () => {
|
|
6140
|
-
this.env.model.dispatch("SELECT_FIGURE", { id: this.props.figure.id });
|
|
6141
|
-
this.env.model.dispatch("COPY");
|
|
6142
|
-
await this.env.clipboard.write(this.env.model.getters.getClipboardContent());
|
|
6143
|
-
},
|
|
6144
|
-
});
|
|
6145
|
-
registry.add("cut", {
|
|
6146
|
-
name: _lt("Cut"),
|
|
6147
|
-
sequence: 3,
|
|
6148
|
-
action: async () => {
|
|
6149
|
-
this.env.model.dispatch("SELECT_FIGURE", { id: this.props.figure.id });
|
|
6150
|
-
this.env.model.dispatch("CUT");
|
|
6151
|
-
await this.env.clipboard.write(this.env.model.getters.getClipboardContent());
|
|
6152
|
-
},
|
|
6153
|
-
});
|
|
6154
|
-
registry.add("delete", {
|
|
6155
|
-
name: _lt("Delete"),
|
|
6156
|
-
sequence: 10,
|
|
6157
|
-
action: () => {
|
|
6158
|
-
this.env.model.dispatch("DELETE_FIGURE", {
|
|
6159
|
-
sheetId: this.env.model.getters.getActiveSheetId(),
|
|
6160
|
-
id: this.props.figure.id,
|
|
6161
|
-
});
|
|
6162
|
-
this.props.onFigureDeleted();
|
|
6163
|
-
},
|
|
6164
|
-
});
|
|
6165
|
-
return registry;
|
|
6166
|
-
}
|
|
6167
|
-
get chartType() {
|
|
6168
|
-
return this.env.model.getters.getChartType(this.props.figure.id);
|
|
6169
|
-
}
|
|
6170
|
-
onContextMenu(ev) {
|
|
6171
|
-
const position = {
|
|
6172
|
-
x: this.position.x + ev.offsetX,
|
|
6173
|
-
y: this.position.y + ev.offsetY,
|
|
6174
|
-
};
|
|
6175
|
-
this.openContextMenu(position);
|
|
6176
|
-
}
|
|
6177
|
-
showMenu() {
|
|
6178
|
-
const { x, y, width } = this.menuButtonRect;
|
|
6179
|
-
const menuPosition = {
|
|
6180
|
-
x: x >= MENU_WIDTH ? x - MENU_WIDTH : x + width,
|
|
6181
|
-
y: y,
|
|
6182
|
-
};
|
|
6183
|
-
this.openContextMenu(menuPosition);
|
|
6184
|
-
}
|
|
6185
|
-
openContextMenu(position) {
|
|
6186
|
-
const registry = this.getMenuItemRegistry();
|
|
6187
|
-
this.menuState.isOpen = true;
|
|
6188
|
-
this.menuState.menuItems = registry.getMenuItems();
|
|
6189
|
-
this.menuState.position = position;
|
|
6190
|
-
}
|
|
6191
|
-
get chartComponent() {
|
|
6192
|
-
const type = this.chartType;
|
|
6193
|
-
const component = chartComponentRegistry.get(type);
|
|
6194
|
-
if (!component) {
|
|
6195
|
-
throw new Error(`Component is not defined for type ${type}`);
|
|
6196
|
-
}
|
|
6197
|
-
return component;
|
|
6198
|
-
}
|
|
6199
|
-
}
|
|
6200
|
-
ChartFigure.template = "o-spreadsheet-ChartFigure";
|
|
6201
|
-
ChartFigure.components = { Menu };
|
|
6202
|
-
ChartFigure.props = {
|
|
6203
|
-
figure: Object,
|
|
6204
|
-
onFigureDeleted: Function,
|
|
6205
|
-
};
|
|
6206
|
-
|
|
6207
|
-
function interactiveCut(env) {
|
|
6208
|
-
const result = env.model.dispatch("CUT");
|
|
6209
|
-
if (!result.isSuccessful) {
|
|
6210
|
-
if (result.isCancelledBecause(19 /* CommandResult.WrongCutSelection */)) {
|
|
6211
|
-
env.raiseError(_lt("This operation is not allowed with multiple selections."));
|
|
6212
|
-
}
|
|
6213
|
-
}
|
|
6214
|
-
}
|
|
6215
|
-
|
|
6216
|
-
const PasteInteractiveContent = {
|
|
6217
|
-
wrongPasteSelection: _lt("This operation is not allowed with multiple selections."),
|
|
6218
|
-
willRemoveExistingMerge: _lt("This operation is not possible due to a merge. Please remove the merges first than try again."),
|
|
6219
|
-
wrongFigurePasteOption: _lt("Cannot do a special paste of a figure."),
|
|
6220
|
-
frozenPaneOverlap: _lt("Cannot paste merged cells over a frozen pane."),
|
|
6221
|
-
};
|
|
6222
|
-
function handlePasteResult(env, result) {
|
|
6223
|
-
if (!result.isSuccessful) {
|
|
6224
|
-
if (result.reasons.includes(20 /* CommandResult.WrongPasteSelection */)) {
|
|
6225
|
-
env.raiseError(PasteInteractiveContent.wrongPasteSelection);
|
|
6226
|
-
}
|
|
6227
|
-
else if (result.reasons.includes(2 /* CommandResult.WillRemoveExistingMerge */)) {
|
|
6228
|
-
env.raiseError(PasteInteractiveContent.willRemoveExistingMerge);
|
|
6229
|
-
}
|
|
6230
|
-
else if (result.reasons.includes(22 /* CommandResult.WrongFigurePasteOption */)) {
|
|
6231
|
-
env.raiseError(PasteInteractiveContent.wrongFigurePasteOption);
|
|
6232
|
-
}
|
|
6233
|
-
else if (result.reasons.includes(75 /* CommandResult.FrozenPaneOverlap */)) {
|
|
6234
|
-
env.raiseError(PasteInteractiveContent.frozenPaneOverlap);
|
|
6235
|
-
}
|
|
6236
|
-
}
|
|
6237
|
-
}
|
|
6238
|
-
function interactivePaste(env, target, pasteOption) {
|
|
6239
|
-
const result = env.model.dispatch("PASTE", { target, pasteOption });
|
|
6240
|
-
handlePasteResult(env, result);
|
|
6241
|
-
}
|
|
6242
|
-
function interactivePasteFromOS(env, target, text) {
|
|
6243
|
-
const result = env.model.dispatch("PASTE_FROM_OS_CLIPBOARD", { target, text });
|
|
6244
|
-
handlePasteResult(env, result);
|
|
6245
|
-
}
|
|
6246
|
-
|
|
6247
|
-
/**
|
|
6248
|
-
* Create a function used to create a Chart based on the definition
|
|
6249
|
-
*/
|
|
6250
|
-
function chartFactory(getters) {
|
|
6251
|
-
const builders = chartRegistry.getAll().sort((a, b) => a.sequence - b.sequence);
|
|
6252
|
-
function createChart(id, definition, sheetId) {
|
|
6253
|
-
const builder = builders.find((builder) => builder.match(definition.type));
|
|
6254
|
-
if (!builder) {
|
|
6255
|
-
throw new Error(`No builder for this chart: ${definition.type}`);
|
|
6256
|
-
}
|
|
6257
|
-
return builder.createChart(definition, sheetId, getters);
|
|
6258
|
-
}
|
|
6259
|
-
return createChart;
|
|
6260
|
-
}
|
|
6261
|
-
/**
|
|
6262
|
-
* Create a function used to create a Chart Runtime based on the chart class
|
|
6263
|
-
* instance
|
|
6264
|
-
*/
|
|
6265
|
-
function chartRuntimeFactory(getters) {
|
|
6266
|
-
const builders = chartRegistry.getAll().sort((a, b) => a.sequence - b.sequence);
|
|
6267
|
-
function createRuntimeChart(chart) {
|
|
6268
|
-
const builder = builders.find((builder) => builder.match(chart.type));
|
|
6269
|
-
if (!builder) {
|
|
6270
|
-
throw new Error("No runtime builder for this chart.");
|
|
6271
|
-
}
|
|
6272
|
-
return builder.getChartRuntime(chart, getters);
|
|
6273
|
-
}
|
|
6274
|
-
return createRuntimeChart;
|
|
6275
|
-
}
|
|
6276
|
-
/**
|
|
6277
|
-
* Validate the chart definition given in arguments
|
|
6278
|
-
*/
|
|
6279
|
-
function validateChartDefinition(validator, definition) {
|
|
6280
|
-
const validators = chartRegistry.getAll().find((validator) => validator.match(definition.type));
|
|
6281
|
-
if (!validators) {
|
|
6282
|
-
throw new Error("Unknown chart type.");
|
|
6283
|
-
}
|
|
6284
|
-
return validators.validateChartDefinition(validator, definition);
|
|
6285
|
-
}
|
|
6286
|
-
/**
|
|
6287
|
-
* Get a new chart definition transformed with the executed command. This
|
|
6288
|
-
* functions will be called during operational transform process
|
|
6289
|
-
*/
|
|
6290
|
-
function transformDefinition(definition, executed) {
|
|
6291
|
-
const transformation = chartRegistry.getAll().find((factory) => factory.match(definition.type));
|
|
6292
|
-
if (!transformation) {
|
|
6293
|
-
throw new Error("Unknown chart type.");
|
|
6294
|
-
}
|
|
6295
|
-
return transformation.transformDefinition(definition, executed);
|
|
6296
|
-
}
|
|
6297
|
-
/**
|
|
6298
|
-
* Get an empty definition based on the given context and the given type
|
|
6299
|
-
*/
|
|
6300
|
-
function getChartDefinitionFromContextCreation(context, type) {
|
|
6301
|
-
const chartClass = chartRegistry.get(type);
|
|
6302
|
-
return chartClass.getChartDefinitionFromContextCreation(context);
|
|
6303
|
-
}
|
|
6304
|
-
function getChartTypes() {
|
|
6305
|
-
const result = {};
|
|
6306
|
-
for (const key of chartRegistry.getKeys()) {
|
|
6307
|
-
result[key] = chartRegistry.get(key).name;
|
|
6308
|
-
}
|
|
6309
|
-
return result;
|
|
6310
|
-
}
|
|
6311
|
-
/**
|
|
6312
|
-
* Return a "smart" chart definition in the given zone. The definition is "smart" because it will
|
|
6313
|
-
* use the best type of chart to display the data of the zone.
|
|
6314
|
-
*
|
|
6315
|
-
* It will also try to find labels and datasets in the range, and try to find title for the datasets.
|
|
6316
|
-
*
|
|
6317
|
-
* The type of chart will be :
|
|
6318
|
-
* - If the zone is a single non-empty cell, returns a scorecard
|
|
6319
|
-
* - If the all the labels are numbers/date, returns a line chart
|
|
6320
|
-
* - Else returns a bar chart
|
|
6321
|
-
*/
|
|
6322
|
-
function getSmartChartDefinition(zone, getters) {
|
|
6323
|
-
var _a;
|
|
6324
|
-
let dataSetZone = zone;
|
|
6325
|
-
if (zone.left !== zone.right) {
|
|
6326
|
-
dataSetZone = { ...zone, left: zone.left + 1 };
|
|
6327
|
-
}
|
|
6328
|
-
const dataSets = [zoneToXc(dataSetZone)];
|
|
6329
|
-
const sheetId = getters.getActiveSheetId();
|
|
6330
|
-
const topLeftCell = getters.getCell({ sheetId, col: zone.left, row: zone.top });
|
|
6331
|
-
if (getZoneArea(zone) === 1 && (topLeftCell === null || topLeftCell === void 0 ? void 0 : topLeftCell.content)) {
|
|
6332
|
-
return {
|
|
6333
|
-
type: "scorecard",
|
|
6334
|
-
title: "",
|
|
6335
|
-
background: ((_a = topLeftCell.style) === null || _a === void 0 ? void 0 : _a.fillColor) || undefined,
|
|
6336
|
-
keyValue: zoneToXc(zone),
|
|
6337
|
-
baselineMode: DEFAULT_SCORECARD_BASELINE_MODE,
|
|
6338
|
-
baselineColorUp: DEFAULT_SCORECARD_BASELINE_COLOR_UP,
|
|
6339
|
-
baselineColorDown: DEFAULT_SCORECARD_BASELINE_COLOR_DOWN,
|
|
6340
|
-
};
|
|
6341
|
-
}
|
|
6342
|
-
let title = "";
|
|
6343
|
-
const cellsInFirstRow = getters.getEvaluatedCellsInZone(sheetId, {
|
|
6344
|
-
...dataSetZone,
|
|
6345
|
-
bottom: dataSetZone.top,
|
|
6346
|
-
});
|
|
6347
|
-
const dataSetsHaveTitle = !!cellsInFirstRow.find((cell) => cell.type !== CellValueType.empty && cell.type !== CellValueType.number);
|
|
6348
|
-
if (dataSetsHaveTitle) {
|
|
6349
|
-
const texts = cellsInFirstRow
|
|
6350
|
-
.filter((cell) => cell.type !== CellValueType.error && cell.type !== CellValueType.empty)
|
|
6351
|
-
.map((cell) => cell.formattedValue);
|
|
6352
|
-
const lastElement = texts.splice(-1)[0];
|
|
6353
|
-
title = texts.join(", ");
|
|
6354
|
-
if (lastElement) {
|
|
6355
|
-
title += (title ? " " + _t("and") + " " : "") + lastElement;
|
|
6356
|
-
}
|
|
6357
|
-
}
|
|
6358
|
-
let labelRangeXc;
|
|
6359
|
-
if (zone.left !== zone.right) {
|
|
6360
|
-
labelRangeXc = zoneToXc({
|
|
6361
|
-
...zone,
|
|
6362
|
-
right: zone.left,
|
|
6363
|
-
top: dataSetsHaveTitle ? zone.top + 1 : zone.top,
|
|
6364
|
-
});
|
|
6365
|
-
}
|
|
6366
|
-
// Only display legend for several datasets.
|
|
6367
|
-
const newLegendPos = dataSetZone.right === dataSetZone.left ? "none" : "top";
|
|
6368
|
-
const labelRange = labelRangeXc ? getters.getRangeFromSheetXC(sheetId, labelRangeXc) : undefined;
|
|
6369
|
-
if (canChartParseLabels(labelRange, getters)) {
|
|
6370
|
-
return {
|
|
6371
|
-
title,
|
|
6372
|
-
dataSets,
|
|
6373
|
-
labelsAsText: false,
|
|
6374
|
-
stacked: false,
|
|
6375
|
-
aggregated: false,
|
|
6376
|
-
labelRange: labelRangeXc,
|
|
6377
|
-
type: "line",
|
|
6378
|
-
dataSetsHaveTitle,
|
|
6379
|
-
verticalAxisPosition: "left",
|
|
6380
|
-
legendPosition: newLegendPos,
|
|
6381
|
-
};
|
|
6382
|
-
}
|
|
6383
|
-
return {
|
|
6384
|
-
title,
|
|
6385
|
-
dataSets,
|
|
6386
|
-
labelRange: labelRangeXc,
|
|
6387
|
-
type: "bar",
|
|
6388
|
-
stacked: false,
|
|
6389
|
-
aggregated: false,
|
|
6390
|
-
dataSetsHaveTitle,
|
|
6391
|
-
verticalAxisPosition: "left",
|
|
6392
|
-
legendPosition: newLegendPos,
|
|
6393
|
-
};
|
|
6394
|
-
}
|
|
6395
|
-
|
|
6396
|
-
function centerFigurePosition(getters, size) {
|
|
6397
|
-
const { x: offsetCorrectionX, y: offsetCorrectionY } = getters.getMainViewportCoordinates();
|
|
6398
|
-
const { scrollX, scrollY } = getters.getActiveSheetScrollInfo();
|
|
6399
|
-
const dim = getters.getSheetViewDimension();
|
|
6400
|
-
const rect = getters.getVisibleRect(getters.getActiveMainViewport());
|
|
6401
|
-
const scrollableViewportWidth = Math.min(rect.width, dim.width - offsetCorrectionX);
|
|
6402
|
-
const scrollableViewportHeight = Math.min(rect.height, dim.height - offsetCorrectionY);
|
|
6403
|
-
const position = {
|
|
6404
|
-
x: offsetCorrectionX + scrollX + Math.max(0, (scrollableViewportWidth - size.width) / 2),
|
|
6405
|
-
y: offsetCorrectionY + scrollY + Math.max(0, (scrollableViewportHeight - size.height) / 2),
|
|
6406
|
-
}; // Position at the center of the scrollable viewport
|
|
6407
|
-
return position;
|
|
6408
|
-
}
|
|
6409
|
-
function getMaxFigureSize(getters, figureSize) {
|
|
6410
|
-
const size = deepCopy(figureSize);
|
|
6411
|
-
const dim = getters.getSheetViewDimension();
|
|
6412
|
-
const maxWidth = dim.width;
|
|
6413
|
-
const maxHeight = dim.height;
|
|
6414
|
-
if (size.width > maxWidth) {
|
|
6415
|
-
const ratio = maxWidth / size.width;
|
|
6416
|
-
size.width = maxWidth;
|
|
6417
|
-
size.height = size.height * ratio;
|
|
6418
|
-
}
|
|
6419
|
-
if (size.height > maxHeight) {
|
|
6420
|
-
const ratio = maxHeight / size.height;
|
|
6421
|
-
size.height = maxHeight;
|
|
6422
|
-
size.width = size.width * ratio;
|
|
6423
|
-
}
|
|
6424
|
-
return size;
|
|
6425
|
-
}
|
|
6426
|
-
|
|
6427
|
-
const SORT_TYPES = [
|
|
6428
|
-
CellValueType.number,
|
|
6429
|
-
CellValueType.error,
|
|
6430
|
-
CellValueType.text,
|
|
6431
|
-
CellValueType.boolean,
|
|
6432
|
-
];
|
|
6433
|
-
function sortCells(cells, sortDirection, emptyCellAsZero) {
|
|
6434
|
-
const cellsWithIndex = cells.map((cell, index) => ({
|
|
6435
|
-
index,
|
|
6436
|
-
type: cell.type,
|
|
6437
|
-
value: cell.value,
|
|
6438
|
-
}));
|
|
6439
|
-
let emptyCells = cellsWithIndex.filter((x) => x.type === CellValueType.empty);
|
|
6440
|
-
let nonEmptyCells = cellsWithIndex.filter((x) => x.type !== CellValueType.empty);
|
|
6441
|
-
if (emptyCellAsZero) {
|
|
6442
|
-
nonEmptyCells.push(...emptyCells.map((emptyCell) => ({ ...emptyCell, type: CellValueType.number, value: 0 })));
|
|
6443
|
-
emptyCells = [];
|
|
6444
|
-
}
|
|
6445
|
-
const inverse = sortDirection === "descending" ? -1 : 1;
|
|
6446
|
-
return nonEmptyCells
|
|
6447
|
-
.sort((left, right) => {
|
|
6448
|
-
let typeOrder = SORT_TYPES.indexOf(left.type) - SORT_TYPES.indexOf(right.type);
|
|
6449
|
-
if (typeOrder === 0) {
|
|
6450
|
-
if (left.type === CellValueType.text || left.type === CellValueType.error) {
|
|
6451
|
-
typeOrder = left.value.localeCompare(right.value);
|
|
6452
|
-
}
|
|
6453
|
-
else
|
|
6454
|
-
typeOrder = left.value - right.value;
|
|
6455
|
-
}
|
|
6456
|
-
return inverse * typeOrder;
|
|
6457
|
-
})
|
|
6458
|
-
.concat(emptyCells);
|
|
6459
|
-
}
|
|
6460
|
-
function interactiveSortSelection(env, sheetId, anchor, zone, sortDirection) {
|
|
6461
|
-
let result = DispatchResult.Success;
|
|
6462
|
-
//several columns => bypass the contiguity check
|
|
6463
|
-
let multiColumns = zone.right > zone.left;
|
|
6464
|
-
if (env.model.getters.doesIntersectMerge(sheetId, zone)) {
|
|
6465
|
-
multiColumns = false;
|
|
6466
|
-
let table;
|
|
6467
|
-
for (let row = zone.top; row <= zone.bottom; row++) {
|
|
6468
|
-
table = [];
|
|
6469
|
-
for (let col = zone.left; col <= zone.right; col++) {
|
|
6470
|
-
let merge = env.model.getters.getMerge({ sheetId, col, row });
|
|
6471
|
-
if (merge && !table.includes(merge.id.toString())) {
|
|
6472
|
-
table.push(merge.id.toString());
|
|
6473
|
-
}
|
|
6474
|
-
}
|
|
6475
|
-
if (table.length >= 2) {
|
|
6476
|
-
multiColumns = true;
|
|
6477
|
-
break;
|
|
6478
|
-
}
|
|
6479
|
-
}
|
|
6480
|
-
}
|
|
6481
|
-
const { col, row } = anchor;
|
|
6482
|
-
if (multiColumns) {
|
|
6483
|
-
result = env.model.dispatch("SORT_CELLS", { sheetId, col, row, zone, sortDirection });
|
|
6484
|
-
}
|
|
6485
|
-
else {
|
|
6486
|
-
// check contiguity
|
|
6487
|
-
const contiguousZone = env.model.getters.getContiguousZone(sheetId, zone);
|
|
6488
|
-
if (isEqual(contiguousZone, zone)) {
|
|
6489
|
-
// merge as it is
|
|
6490
|
-
result = env.model.dispatch("SORT_CELLS", {
|
|
6491
|
-
sheetId,
|
|
6492
|
-
col,
|
|
6493
|
-
row,
|
|
6494
|
-
zone,
|
|
6495
|
-
sortDirection,
|
|
6496
|
-
});
|
|
6497
|
-
}
|
|
6498
|
-
else {
|
|
6499
|
-
env.askConfirmation(_lt("We found data next to your selection. Since this data was not selected, it will not be sorted. Do you want to extend your selection?"), () => {
|
|
6500
|
-
zone = contiguousZone;
|
|
6501
|
-
result = env.model.dispatch("SORT_CELLS", {
|
|
6502
|
-
sheetId,
|
|
6503
|
-
col,
|
|
6504
|
-
row,
|
|
6505
|
-
zone,
|
|
6506
|
-
sortDirection,
|
|
6507
|
-
});
|
|
6508
|
-
}, () => {
|
|
6509
|
-
result = env.model.dispatch("SORT_CELLS", {
|
|
6510
|
-
sheetId,
|
|
6511
|
-
col,
|
|
6512
|
-
row,
|
|
6513
|
-
zone,
|
|
6514
|
-
sortDirection,
|
|
6515
|
-
});
|
|
6516
|
-
});
|
|
6517
|
-
}
|
|
5852
|
+
else {
|
|
5853
|
+
// check contiguity
|
|
5854
|
+
const contiguousZone = env.model.getters.getContiguousZone(sheetId, zone);
|
|
5855
|
+
if (isEqual(contiguousZone, zone)) {
|
|
5856
|
+
// merge as it is
|
|
5857
|
+
result = env.model.dispatch("SORT_CELLS", {
|
|
5858
|
+
sheetId,
|
|
5859
|
+
col,
|
|
5860
|
+
row,
|
|
5861
|
+
zone,
|
|
5862
|
+
sortDirection,
|
|
5863
|
+
});
|
|
5864
|
+
}
|
|
5865
|
+
else {
|
|
5866
|
+
env.askConfirmation(_lt("We found data next to your selection. Since this data was not selected, it will not be sorted. Do you want to extend your selection?"), () => {
|
|
5867
|
+
zone = contiguousZone;
|
|
5868
|
+
result = env.model.dispatch("SORT_CELLS", {
|
|
5869
|
+
sheetId,
|
|
5870
|
+
col,
|
|
5871
|
+
row,
|
|
5872
|
+
zone,
|
|
5873
|
+
sortDirection,
|
|
5874
|
+
});
|
|
5875
|
+
}, () => {
|
|
5876
|
+
result = env.model.dispatch("SORT_CELLS", {
|
|
5877
|
+
sheetId,
|
|
5878
|
+
col,
|
|
5879
|
+
row,
|
|
5880
|
+
zone,
|
|
5881
|
+
sortDirection,
|
|
5882
|
+
});
|
|
5883
|
+
});
|
|
5884
|
+
}
|
|
6518
5885
|
}
|
|
6519
5886
|
if (result.isCancelledBecause(63 /* CommandResult.InvalidSortZone */)) {
|
|
6520
5887
|
const { col, row } = anchor;
|
|
@@ -7441,6 +6808,53 @@
|
|
|
7441
6808
|
action: OPEN_CF_SIDEPANEL_ACTION,
|
|
7442
6809
|
});
|
|
7443
6810
|
|
|
6811
|
+
/**
|
|
6812
|
+
* Return true if the event was triggered from
|
|
6813
|
+
* a child element.
|
|
6814
|
+
*/
|
|
6815
|
+
function isChildEvent(parent, ev) {
|
|
6816
|
+
return !!ev.target && parent.contains(ev.target);
|
|
6817
|
+
}
|
|
6818
|
+
function gridOverlayPosition() {
|
|
6819
|
+
const spreadsheetElement = document.querySelector(".o-grid-overlay");
|
|
6820
|
+
if (spreadsheetElement) {
|
|
6821
|
+
const { top, left } = spreadsheetElement === null || spreadsheetElement === void 0 ? void 0 : spreadsheetElement.getBoundingClientRect();
|
|
6822
|
+
return { top, left };
|
|
6823
|
+
}
|
|
6824
|
+
throw new Error("Can't find spreadsheet position");
|
|
6825
|
+
}
|
|
6826
|
+
function getBoundingRectAsPOJO(el) {
|
|
6827
|
+
const rect = el.getBoundingClientRect();
|
|
6828
|
+
return {
|
|
6829
|
+
x: rect.x,
|
|
6830
|
+
y: rect.y,
|
|
6831
|
+
width: rect.width,
|
|
6832
|
+
height: rect.height,
|
|
6833
|
+
};
|
|
6834
|
+
}
|
|
6835
|
+
/**
|
|
6836
|
+
* Iterate over all the children of `el` in the dom tree starting at `el`, depth first.
|
|
6837
|
+
*/
|
|
6838
|
+
function* iterateChildren(el) {
|
|
6839
|
+
yield el;
|
|
6840
|
+
if (el.hasChildNodes()) {
|
|
6841
|
+
for (let child of el.childNodes) {
|
|
6842
|
+
yield* iterateChildren(child);
|
|
6843
|
+
}
|
|
6844
|
+
}
|
|
6845
|
+
}
|
|
6846
|
+
function getElementScrollTop(el) {
|
|
6847
|
+
return (el === null || el === void 0 ? void 0 : el.scrollTop) || 0;
|
|
6848
|
+
}
|
|
6849
|
+
function setElementScrollTop(el, scroll) {
|
|
6850
|
+
if (!el)
|
|
6851
|
+
return;
|
|
6852
|
+
el.scrollTop = scroll;
|
|
6853
|
+
}
|
|
6854
|
+
function getOpenedMenus() {
|
|
6855
|
+
return Array.from(document.querySelectorAll(".o-spreadsheet .o-menu"));
|
|
6856
|
+
}
|
|
6857
|
+
|
|
7444
6858
|
function startDnd(onMouseMove, onMouseUp, onMouseDown = () => { }) {
|
|
7445
6859
|
const _onMouseDown = (ev) => {
|
|
7446
6860
|
ev.preventDefault();
|
|
@@ -15720,8 +15134,7 @@
|
|
|
15720
15134
|
return false;
|
|
15721
15135
|
const range = this.env.model.getters.getRangeFromSheetXC(refSheet, xc);
|
|
15722
15136
|
let zone = range.zone;
|
|
15723
|
-
|
|
15724
|
-
zone = height * width === 1 ? this.env.model.getters.expandZone(refSheet, zone) : zone;
|
|
15137
|
+
zone = getZoneArea(zone) === 1 ? this.env.model.getters.expandZone(refSheet, zone) : zone;
|
|
15725
15138
|
return isEqual(zone, highlight.zone);
|
|
15726
15139
|
});
|
|
15727
15140
|
return highlight && highlight.color ? highlight.color : undefined;
|
|
@@ -16338,484 +15751,1000 @@
|
|
|
16338
15751
|
y = 0;
|
|
16339
15752
|
break;
|
|
16340
15753
|
}
|
|
16341
|
-
const cell = data.cell;
|
|
16342
|
-
if (!cell || !cell.isFormula) {
|
|
16343
|
-
return { cellData: {} };
|
|
15754
|
+
const cell = data.cell;
|
|
15755
|
+
if (!cell || !cell.isFormula) {
|
|
15756
|
+
return { cellData: {} };
|
|
15757
|
+
}
|
|
15758
|
+
const sheetId = data.sheetId;
|
|
15759
|
+
const ranges = getters.createAdaptedRanges(cell.dependencies, x, y, sheetId);
|
|
15760
|
+
const content = getters.buildFormulaContent(sheetId, cell, ranges);
|
|
15761
|
+
return {
|
|
15762
|
+
cellData: {
|
|
15763
|
+
border: data.border,
|
|
15764
|
+
style: cell.style,
|
|
15765
|
+
format: cell.format,
|
|
15766
|
+
content,
|
|
15767
|
+
},
|
|
15768
|
+
tooltip: content ? { props: { content } } : undefined,
|
|
15769
|
+
};
|
|
15770
|
+
},
|
|
15771
|
+
});
|
|
15772
|
+
|
|
15773
|
+
const autofillRulesRegistry = new Registry();
|
|
15774
|
+
const numberPostfixRegExp = /(\d+)$/;
|
|
15775
|
+
const stringPrefixRegExp = /^(.*\D+)/;
|
|
15776
|
+
const alphaNumericValueRegExp = /^(.*\D+)(\d+)$/;
|
|
15777
|
+
/**
|
|
15778
|
+
* Get the consecutive evaluated cells that can pass the filter function (e.g. certain type filter).
|
|
15779
|
+
* Return the one which contains the given cell
|
|
15780
|
+
*/
|
|
15781
|
+
function getGroup(cell, cells, filter) {
|
|
15782
|
+
let group = [];
|
|
15783
|
+
let found = false;
|
|
15784
|
+
for (let x of cells) {
|
|
15785
|
+
if (x === cell) {
|
|
15786
|
+
found = true;
|
|
15787
|
+
}
|
|
15788
|
+
const cellValue = evaluateLiteral(x === null || x === void 0 ? void 0 : x.content);
|
|
15789
|
+
if (filter(cellValue)) {
|
|
15790
|
+
group.push(cellValue);
|
|
15791
|
+
}
|
|
15792
|
+
else {
|
|
15793
|
+
if (found) {
|
|
15794
|
+
return group;
|
|
15795
|
+
}
|
|
15796
|
+
group = [];
|
|
15797
|
+
}
|
|
15798
|
+
}
|
|
15799
|
+
return group;
|
|
15800
|
+
}
|
|
15801
|
+
/**
|
|
15802
|
+
* Get the average steps between numbers
|
|
15803
|
+
*/
|
|
15804
|
+
function getAverageIncrement(group) {
|
|
15805
|
+
const averages = [];
|
|
15806
|
+
let last = group[0];
|
|
15807
|
+
for (let i = 1; i < group.length; i++) {
|
|
15808
|
+
const current = group[i];
|
|
15809
|
+
averages.push(current - last);
|
|
15810
|
+
last = current;
|
|
15811
|
+
}
|
|
15812
|
+
return averages.reduce((a, b) => a + b, 0) / averages.length;
|
|
15813
|
+
}
|
|
15814
|
+
/**
|
|
15815
|
+
* Get the step for a group
|
|
15816
|
+
*/
|
|
15817
|
+
function calculateIncrementBasedOnGroup(group) {
|
|
15818
|
+
let increment = 1;
|
|
15819
|
+
if (group.length >= 2) {
|
|
15820
|
+
increment = getAverageIncrement(group) * group.length;
|
|
15821
|
+
}
|
|
15822
|
+
return increment;
|
|
15823
|
+
}
|
|
15824
|
+
autofillRulesRegistry
|
|
15825
|
+
.add("simple_value_copy", {
|
|
15826
|
+
condition: (cell, cells) => {
|
|
15827
|
+
var _a;
|
|
15828
|
+
return cells.length === 1 && !cell.isFormula && !((_a = cell.format) === null || _a === void 0 ? void 0 : _a.match(DATETIME_FORMAT));
|
|
15829
|
+
},
|
|
15830
|
+
generateRule: () => {
|
|
15831
|
+
return { type: "COPY_MODIFIER" };
|
|
15832
|
+
},
|
|
15833
|
+
sequence: 10,
|
|
15834
|
+
})
|
|
15835
|
+
.add("increment_alphanumeric_value", {
|
|
15836
|
+
condition: (cell) => !cell.isFormula &&
|
|
15837
|
+
evaluateLiteral(cell.content).type === CellValueType.text &&
|
|
15838
|
+
alphaNumericValueRegExp.test(cell.content),
|
|
15839
|
+
generateRule: (cell, cells) => {
|
|
15840
|
+
const numberPostfix = parseInt(cell.content.match(numberPostfixRegExp)[0]);
|
|
15841
|
+
const prefix = cell.content.match(stringPrefixRegExp)[0];
|
|
15842
|
+
const numberPostfixLength = cell.content.length - prefix.length;
|
|
15843
|
+
const group = getGroup(cell, cells, (evaluatedCell) => evaluatedCell.type === CellValueType.text &&
|
|
15844
|
+
alphaNumericValueRegExp.test(evaluatedCell.value)) // get consecutive alphanumeric cells, no matter what the prefix is
|
|
15845
|
+
.filter((cell) => prefix === cell.value.toString().match(stringPrefixRegExp)[0])
|
|
15846
|
+
.map((cell) => parseInt(cell.value.toString().match(numberPostfixRegExp)[0]));
|
|
15847
|
+
const increment = calculateIncrementBasedOnGroup(group);
|
|
15848
|
+
return {
|
|
15849
|
+
type: "ALPHANUMERIC_INCREMENT_MODIFIER",
|
|
15850
|
+
prefix,
|
|
15851
|
+
current: numberPostfix,
|
|
15852
|
+
increment,
|
|
15853
|
+
numberPostfixLength,
|
|
15854
|
+
};
|
|
15855
|
+
},
|
|
15856
|
+
sequence: 15,
|
|
15857
|
+
})
|
|
15858
|
+
.add("copy_text", {
|
|
15859
|
+
condition: (cell) => !cell.isFormula && evaluateLiteral(cell.content).type === CellValueType.text,
|
|
15860
|
+
generateRule: () => {
|
|
15861
|
+
return { type: "COPY_MODIFIER" };
|
|
15862
|
+
},
|
|
15863
|
+
sequence: 20,
|
|
15864
|
+
})
|
|
15865
|
+
.add("update_formula", {
|
|
15866
|
+
condition: (cell) => cell.isFormula,
|
|
15867
|
+
generateRule: (_, cells) => {
|
|
15868
|
+
return { type: "FORMULA_MODIFIER", increment: cells.length, current: 0 };
|
|
15869
|
+
},
|
|
15870
|
+
sequence: 30,
|
|
15871
|
+
})
|
|
15872
|
+
.add("increment_number", {
|
|
15873
|
+
condition: (cell) => !cell.isFormula && evaluateLiteral(cell.content).type === CellValueType.number,
|
|
15874
|
+
generateRule: (cell, cells) => {
|
|
15875
|
+
const group = getGroup(cell, cells, (evaluatedCell) => evaluatedCell.type === CellValueType.number).map((cell) => Number(cell.value));
|
|
15876
|
+
const increment = calculateIncrementBasedOnGroup(group);
|
|
15877
|
+
const evaluation = evaluateLiteral(cell.content);
|
|
15878
|
+
return {
|
|
15879
|
+
type: "INCREMENT_MODIFIER",
|
|
15880
|
+
increment,
|
|
15881
|
+
current: evaluation.type === CellValueType.number ? evaluation.value : 0,
|
|
15882
|
+
};
|
|
15883
|
+
},
|
|
15884
|
+
sequence: 40,
|
|
15885
|
+
});
|
|
15886
|
+
|
|
15887
|
+
const ERROR_TOOLTIP_MAX_HEIGHT = 80;
|
|
15888
|
+
const ERROR_TOOLTIP_WIDTH = 180;
|
|
15889
|
+
css /* scss */ `
|
|
15890
|
+
.o-error-tooltip {
|
|
15891
|
+
font-size: 13px;
|
|
15892
|
+
background-color: white;
|
|
15893
|
+
border-left: 3px solid red;
|
|
15894
|
+
padding: 10px;
|
|
15895
|
+
width: ${ERROR_TOOLTIP_WIDTH}px;
|
|
15896
|
+
box-sizing: border-box !important;
|
|
15897
|
+
}
|
|
15898
|
+
`;
|
|
15899
|
+
class ErrorToolTip extends owl.Component {
|
|
15900
|
+
}
|
|
15901
|
+
ErrorToolTip.maxSize = { maxHeight: ERROR_TOOLTIP_MAX_HEIGHT };
|
|
15902
|
+
ErrorToolTip.template = "o-spreadsheet-ErrorToolTip";
|
|
15903
|
+
ErrorToolTip.components = {};
|
|
15904
|
+
ErrorToolTip.props = {
|
|
15905
|
+
text: String,
|
|
15906
|
+
onClosed: { type: Function, optional: true },
|
|
15907
|
+
};
|
|
15908
|
+
const ErrorToolTipPopoverBuilder = {
|
|
15909
|
+
onHover: (position, getters) => {
|
|
15910
|
+
const cell = getters.getEvaluatedCell(position);
|
|
15911
|
+
if (cell.type === CellValueType.error && cell.error.logLevel > CellErrorLevel.silent) {
|
|
15912
|
+
return {
|
|
15913
|
+
isOpen: true,
|
|
15914
|
+
props: { text: cell.error.message },
|
|
15915
|
+
Component: ErrorToolTip,
|
|
15916
|
+
cellCorner: "TopRight",
|
|
15917
|
+
};
|
|
15918
|
+
}
|
|
15919
|
+
return { isOpen: false };
|
|
15920
|
+
},
|
|
15921
|
+
};
|
|
15922
|
+
|
|
15923
|
+
css /*SCSS*/ `
|
|
15924
|
+
.o-filter-menu-value {
|
|
15925
|
+
padding: 4px;
|
|
15926
|
+
line-height: 20px;
|
|
15927
|
+
height: 28px;
|
|
15928
|
+
.o-filter-menu-value-checked {
|
|
15929
|
+
width: 20px;
|
|
15930
|
+
}
|
|
15931
|
+
}
|
|
15932
|
+
`;
|
|
15933
|
+
class FilterMenuValueItem extends owl.Component {
|
|
15934
|
+
constructor() {
|
|
15935
|
+
super(...arguments);
|
|
15936
|
+
this.itemRef = owl.useRef("menuValueItem");
|
|
15937
|
+
}
|
|
15938
|
+
setup() {
|
|
15939
|
+
owl.onWillPatch(() => {
|
|
15940
|
+
if (this.props.scrolledTo) {
|
|
15941
|
+
this.scrollListToSelectedValue();
|
|
15942
|
+
}
|
|
15943
|
+
});
|
|
15944
|
+
}
|
|
15945
|
+
scrollListToSelectedValue() {
|
|
15946
|
+
var _a, _b;
|
|
15947
|
+
if (!this.itemRef.el) {
|
|
15948
|
+
return;
|
|
15949
|
+
}
|
|
15950
|
+
(_b = (_a = this.itemRef.el).scrollIntoView) === null || _b === void 0 ? void 0 : _b.call(_a, {
|
|
15951
|
+
block: this.props.scrolledTo === "bottom" ? "end" : "start",
|
|
15952
|
+
});
|
|
15953
|
+
}
|
|
15954
|
+
}
|
|
15955
|
+
FilterMenuValueItem.template = "o-spreadsheet-FilterMenuValueItem";
|
|
15956
|
+
FilterMenuValueItem.props = {
|
|
15957
|
+
value: String,
|
|
15958
|
+
isChecked: Boolean,
|
|
15959
|
+
isSelected: Boolean,
|
|
15960
|
+
onMouseMove: Function,
|
|
15961
|
+
onClick: Function,
|
|
15962
|
+
scrolledTo: { type: String, optional: true },
|
|
15963
|
+
};
|
|
15964
|
+
|
|
15965
|
+
const FILTER_MENU_HEIGHT = 295;
|
|
15966
|
+
const CSS = css /* scss */ `
|
|
15967
|
+
.o-filter-menu {
|
|
15968
|
+
box-sizing: border-box;
|
|
15969
|
+
padding: 8px 16px;
|
|
15970
|
+
height: ${FILTER_MENU_HEIGHT}px;
|
|
15971
|
+
line-height: 1;
|
|
15972
|
+
|
|
15973
|
+
.o-filter-menu-item {
|
|
15974
|
+
display: flex;
|
|
15975
|
+
box-sizing: border-box;
|
|
15976
|
+
height: ${MENU_ITEM_HEIGHT}px;
|
|
15977
|
+
padding: 4px 4px 4px 0px;
|
|
15978
|
+
cursor: pointer;
|
|
15979
|
+
user-select: none;
|
|
15980
|
+
|
|
15981
|
+
&.selected {
|
|
15982
|
+
background-color: rgba(0, 0, 0, 0.08);
|
|
15983
|
+
}
|
|
15984
|
+
}
|
|
15985
|
+
|
|
15986
|
+
input {
|
|
15987
|
+
box-sizing: border-box;
|
|
15988
|
+
margin-bottom: 5px;
|
|
15989
|
+
border: 1px solid #949494;
|
|
15990
|
+
height: 24px;
|
|
15991
|
+
padding-right: 28px;
|
|
15992
|
+
}
|
|
15993
|
+
|
|
15994
|
+
.o-search-icon {
|
|
15995
|
+
right: 5px;
|
|
15996
|
+
top: 4px;
|
|
15997
|
+
|
|
15998
|
+
svg {
|
|
15999
|
+
height: 16px;
|
|
16000
|
+
width: 16px;
|
|
16001
|
+
vertical-align: middle;
|
|
16002
|
+
}
|
|
16003
|
+
}
|
|
16004
|
+
|
|
16005
|
+
.o-filter-menu-actions {
|
|
16006
|
+
display: flex;
|
|
16007
|
+
flex-direction: row;
|
|
16008
|
+
margin-bottom: 4px;
|
|
16009
|
+
|
|
16010
|
+
.o-filter-menu-action-text {
|
|
16011
|
+
cursor: pointer;
|
|
16012
|
+
margin-right: 10px;
|
|
16013
|
+
color: blue;
|
|
16014
|
+
text-decoration: underline;
|
|
16015
|
+
}
|
|
16016
|
+
}
|
|
16017
|
+
|
|
16018
|
+
.o-filter-menu-list {
|
|
16019
|
+
flex: auto;
|
|
16020
|
+
overflow-y: auto;
|
|
16021
|
+
border: 1px solid #949494;
|
|
16022
|
+
|
|
16023
|
+
.o-filter-menu-no-values {
|
|
16024
|
+
color: #949494;
|
|
16025
|
+
font-style: italic;
|
|
16026
|
+
}
|
|
16027
|
+
}
|
|
16028
|
+
|
|
16029
|
+
.o-filter-menu-buttons {
|
|
16030
|
+
margin-top: 9px;
|
|
16031
|
+
|
|
16032
|
+
.o-filter-menu-button {
|
|
16033
|
+
border: 1px solid lightgrey;
|
|
16034
|
+
padding: 6px 10px;
|
|
16035
|
+
cursor: pointer;
|
|
16036
|
+
border-radius: 4px;
|
|
16037
|
+
font-weight: 500;
|
|
16038
|
+
line-height: 16px;
|
|
16039
|
+
}
|
|
16040
|
+
|
|
16041
|
+
.o-filter-menu-button-cancel {
|
|
16042
|
+
background: white;
|
|
16043
|
+
&:hover {
|
|
16044
|
+
background-color: rgba(0, 0, 0, 0.08);
|
|
16045
|
+
}
|
|
16046
|
+
}
|
|
16047
|
+
|
|
16048
|
+
.o-filter-menu-button-primary {
|
|
16049
|
+
background-color: #188038;
|
|
16050
|
+
&:hover {
|
|
16051
|
+
background-color: #1d9641;
|
|
16052
|
+
}
|
|
16053
|
+
color: white;
|
|
16054
|
+
font-weight: bold;
|
|
16055
|
+
margin-left: 10px;
|
|
16056
|
+
}
|
|
16057
|
+
}
|
|
16058
|
+
}
|
|
16059
|
+
`;
|
|
16060
|
+
class FilterMenu extends owl.Component {
|
|
16061
|
+
constructor() {
|
|
16062
|
+
super(...arguments);
|
|
16063
|
+
this.state = owl.useState({
|
|
16064
|
+
values: [],
|
|
16065
|
+
textFilter: "",
|
|
16066
|
+
selectedValue: undefined,
|
|
16067
|
+
});
|
|
16068
|
+
this.searchBar = owl.useRef("filterMenuSearchBar");
|
|
16069
|
+
}
|
|
16070
|
+
setup() {
|
|
16071
|
+
owl.onWillUpdateProps((nextProps) => {
|
|
16072
|
+
if (!deepEquals(nextProps.filterPosition, this.props.filterPosition)) {
|
|
16073
|
+
this.state.values = this.getFilterValues(nextProps.filterPosition);
|
|
16074
|
+
}
|
|
16075
|
+
});
|
|
16076
|
+
this.state.values = this.getFilterValues(this.props.filterPosition);
|
|
16077
|
+
}
|
|
16078
|
+
getFilterValues(position) {
|
|
16079
|
+
const sheetId = this.env.model.getters.getActiveSheetId();
|
|
16080
|
+
const filter = this.env.model.getters.getFilter({ sheetId, ...position });
|
|
16081
|
+
if (!filter) {
|
|
16082
|
+
return [];
|
|
16083
|
+
}
|
|
16084
|
+
const cellValues = (filter.filteredZone ? positions(filter.filteredZone) : [])
|
|
16085
|
+
.filter(({ row }) => !this.env.model.getters.isRowHidden(sheetId, row))
|
|
16086
|
+
.map(({ col, row }) => this.env.model.getters.getEvaluatedCell({ sheetId, col, row }).formattedValue);
|
|
16087
|
+
const filterValues = this.env.model.getters.getFilterValues({ sheetId, ...position });
|
|
16088
|
+
const strValues = [...cellValues, ...filterValues];
|
|
16089
|
+
const normalizedFilteredValues = filterValues.map(toLowerCase);
|
|
16090
|
+
// Set with lowercase values to avoid duplicates
|
|
16091
|
+
const normalizedValues = [...new Set(strValues.map(toLowerCase))];
|
|
16092
|
+
const sortedValues = normalizedValues.sort((val1, val2) => val1.localeCompare(val2, undefined, { numeric: true, sensitivity: "base" }));
|
|
16093
|
+
return sortedValues.map((normalizedValue) => {
|
|
16094
|
+
const checked = normalizedFilteredValues.findIndex((filteredValue) => filteredValue === normalizedValue) ===
|
|
16095
|
+
-1;
|
|
16096
|
+
return {
|
|
16097
|
+
checked,
|
|
16098
|
+
string: strValues.find((val) => toLowerCase(val) === normalizedValue) || "",
|
|
16099
|
+
};
|
|
16100
|
+
});
|
|
16101
|
+
}
|
|
16102
|
+
checkValue(value) {
|
|
16103
|
+
var _a;
|
|
16104
|
+
this.state.selectedValue = value.string;
|
|
16105
|
+
value.checked = !value.checked;
|
|
16106
|
+
(_a = this.searchBar.el) === null || _a === void 0 ? void 0 : _a.focus();
|
|
16107
|
+
}
|
|
16108
|
+
onMouseMove(value) {
|
|
16109
|
+
this.state.selectedValue = value.string;
|
|
16110
|
+
}
|
|
16111
|
+
selectAll() {
|
|
16112
|
+
this.state.values.forEach((value) => (value.checked = true));
|
|
16113
|
+
}
|
|
16114
|
+
clearAll() {
|
|
16115
|
+
this.state.values.forEach((value) => (value.checked = false));
|
|
16116
|
+
}
|
|
16117
|
+
get filterTable() {
|
|
16118
|
+
const sheetId = this.env.model.getters.getActiveSheetId();
|
|
16119
|
+
const position = this.props.filterPosition;
|
|
16120
|
+
return this.env.model.getters.getFilterTable({ sheetId, ...position });
|
|
16121
|
+
}
|
|
16122
|
+
get displayedValues() {
|
|
16123
|
+
if (!this.state.textFilter) {
|
|
16124
|
+
return this.state.values;
|
|
16125
|
+
}
|
|
16126
|
+
return fuzzyLookup(this.state.textFilter, this.state.values, (val) => val.string);
|
|
16127
|
+
}
|
|
16128
|
+
confirm() {
|
|
16129
|
+
var _a, _b;
|
|
16130
|
+
const position = this.props.filterPosition;
|
|
16131
|
+
this.env.model.dispatch("UPDATE_FILTER", {
|
|
16132
|
+
...position,
|
|
16133
|
+
sheetId: this.env.model.getters.getActiveSheetId(),
|
|
16134
|
+
values: this.state.values.filter((val) => !val.checked).map((val) => val.string),
|
|
16135
|
+
});
|
|
16136
|
+
(_b = (_a = this.props).onClosed) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
16137
|
+
}
|
|
16138
|
+
cancel() {
|
|
16139
|
+
var _a, _b;
|
|
16140
|
+
(_b = (_a = this.props).onClosed) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
16141
|
+
}
|
|
16142
|
+
onKeyDown(ev) {
|
|
16143
|
+
const displayedValues = this.displayedValues;
|
|
16144
|
+
if (displayedValues.length === 0)
|
|
16145
|
+
return;
|
|
16146
|
+
let selectedIndex = undefined;
|
|
16147
|
+
if (this.state.selectedValue !== undefined) {
|
|
16148
|
+
const index = displayedValues.findIndex((val) => val.string === this.state.selectedValue);
|
|
16149
|
+
selectedIndex = index === -1 ? undefined : index;
|
|
16150
|
+
}
|
|
16151
|
+
switch (ev.key) {
|
|
16152
|
+
case "ArrowDown":
|
|
16153
|
+
if (selectedIndex === undefined) {
|
|
16154
|
+
selectedIndex = 0;
|
|
16155
|
+
}
|
|
16156
|
+
else {
|
|
16157
|
+
selectedIndex = Math.min(selectedIndex + 1, displayedValues.length - 1);
|
|
16158
|
+
}
|
|
16159
|
+
ev.preventDefault();
|
|
16160
|
+
ev.stopPropagation();
|
|
16161
|
+
break;
|
|
16162
|
+
case "ArrowUp":
|
|
16163
|
+
if (selectedIndex === undefined) {
|
|
16164
|
+
selectedIndex = displayedValues.length - 1;
|
|
16165
|
+
}
|
|
16166
|
+
else {
|
|
16167
|
+
selectedIndex = Math.max(selectedIndex - 1, 0);
|
|
16168
|
+
}
|
|
16169
|
+
ev.preventDefault();
|
|
16170
|
+
ev.stopPropagation();
|
|
16171
|
+
break;
|
|
16172
|
+
case "Enter":
|
|
16173
|
+
if (selectedIndex !== undefined) {
|
|
16174
|
+
this.checkValue(displayedValues[selectedIndex]);
|
|
16175
|
+
}
|
|
16176
|
+
ev.stopPropagation();
|
|
16177
|
+
ev.preventDefault();
|
|
16178
|
+
break;
|
|
16179
|
+
}
|
|
16180
|
+
this.state.selectedValue =
|
|
16181
|
+
selectedIndex !== undefined ? displayedValues[selectedIndex].string : undefined;
|
|
16182
|
+
if (ev.key === "ArrowUp" || ev.key === "ArrowDown") {
|
|
16183
|
+
this.scrollListToSelectedValue(ev.key);
|
|
16184
|
+
}
|
|
16185
|
+
}
|
|
16186
|
+
clearScrolledToValue() {
|
|
16187
|
+
this.state.values.forEach((val) => (val.scrolledTo = undefined));
|
|
16188
|
+
}
|
|
16189
|
+
scrollListToSelectedValue(arrow) {
|
|
16190
|
+
this.clearScrolledToValue();
|
|
16191
|
+
const selectedValue = this.state.values.find((val) => val.string === this.state.selectedValue);
|
|
16192
|
+
if (selectedValue) {
|
|
16193
|
+
selectedValue.scrolledTo = arrow === "ArrowUp" ? "top" : "bottom";
|
|
16344
16194
|
}
|
|
16345
|
-
|
|
16346
|
-
|
|
16347
|
-
|
|
16195
|
+
}
|
|
16196
|
+
sortFilterZone(sortDirection) {
|
|
16197
|
+
var _a, _b;
|
|
16198
|
+
const filterPosition = this.props.filterPosition;
|
|
16199
|
+
const filterTable = this.filterTable;
|
|
16200
|
+
if (!filterPosition || !filterTable || !filterTable.contentZone) {
|
|
16201
|
+
return;
|
|
16202
|
+
}
|
|
16203
|
+
const sheetId = this.env.model.getters.getActiveSheetId();
|
|
16204
|
+
this.env.model.dispatch("SORT_CELLS", {
|
|
16205
|
+
sheetId,
|
|
16206
|
+
col: filterPosition.col,
|
|
16207
|
+
row: filterTable.contentZone.top,
|
|
16208
|
+
zone: filterTable.contentZone,
|
|
16209
|
+
sortDirection,
|
|
16210
|
+
sortOptions: { emptyCellAsZero: true, sortHeaders: true },
|
|
16211
|
+
});
|
|
16212
|
+
(_b = (_a = this.props).onClosed) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
16213
|
+
}
|
|
16214
|
+
}
|
|
16215
|
+
FilterMenu.size = { width: MENU_WIDTH, height: FILTER_MENU_HEIGHT };
|
|
16216
|
+
FilterMenu.template = "o-spreadsheet-FilterMenu";
|
|
16217
|
+
FilterMenu.style = CSS;
|
|
16218
|
+
FilterMenu.components = { FilterMenuValueItem };
|
|
16219
|
+
FilterMenu.props = {
|
|
16220
|
+
filterPosition: Object,
|
|
16221
|
+
onClosed: { type: Function, optional: true },
|
|
16222
|
+
};
|
|
16223
|
+
const FilterMenuPopoverBuilder = {
|
|
16224
|
+
onOpen: (position, getters) => {
|
|
16348
16225
|
return {
|
|
16349
|
-
|
|
16350
|
-
|
|
16351
|
-
|
|
16352
|
-
|
|
16353
|
-
content,
|
|
16354
|
-
},
|
|
16355
|
-
tooltip: content ? { props: { content } } : undefined,
|
|
16226
|
+
isOpen: true,
|
|
16227
|
+
props: { filterPosition: position },
|
|
16228
|
+
Component: FilterMenu,
|
|
16229
|
+
cellCorner: "BottomLeft",
|
|
16356
16230
|
};
|
|
16357
16231
|
},
|
|
16358
|
-
}
|
|
16232
|
+
};
|
|
16359
16233
|
|
|
16360
|
-
const autofillRulesRegistry = new Registry();
|
|
16361
|
-
const numberPostfixRegExp = /(\d+)$/;
|
|
16362
|
-
const stringPrefixRegExp = /^(.*\D+)/;
|
|
16363
|
-
const alphaNumericValueRegExp = /^(.*\D+)(\d+)$/;
|
|
16364
16234
|
/**
|
|
16365
|
-
*
|
|
16366
|
-
*
|
|
16235
|
+
* Return the o-spreadsheet element position relative
|
|
16236
|
+
* to the browser viewport.
|
|
16367
16237
|
*/
|
|
16368
|
-
function
|
|
16369
|
-
|
|
16370
|
-
let
|
|
16371
|
-
|
|
16372
|
-
|
|
16373
|
-
|
|
16374
|
-
|
|
16375
|
-
const cellValue = evaluateLiteral(x === null || x === void 0 ? void 0 : x.content);
|
|
16376
|
-
if (filter(cellValue)) {
|
|
16377
|
-
group.push(cellValue);
|
|
16238
|
+
function useSpreadsheetRect() {
|
|
16239
|
+
const position = owl.useState({ x: 0, y: 0, width: 0, height: 0 });
|
|
16240
|
+
let spreadsheetElement = document.querySelector(".o-spreadsheet");
|
|
16241
|
+
updatePosition();
|
|
16242
|
+
function updatePosition() {
|
|
16243
|
+
if (!spreadsheetElement) {
|
|
16244
|
+
spreadsheetElement = document.querySelector(".o-spreadsheet");
|
|
16378
16245
|
}
|
|
16379
|
-
|
|
16380
|
-
|
|
16381
|
-
|
|
16382
|
-
|
|
16383
|
-
|
|
16246
|
+
if (spreadsheetElement) {
|
|
16247
|
+
const { top, left, width, height } = spreadsheetElement.getBoundingClientRect();
|
|
16248
|
+
position.x = left;
|
|
16249
|
+
position.y = top;
|
|
16250
|
+
position.width = width;
|
|
16251
|
+
position.height = height;
|
|
16384
16252
|
}
|
|
16385
16253
|
}
|
|
16386
|
-
|
|
16254
|
+
owl.onMounted(updatePosition);
|
|
16255
|
+
owl.onPatched(updatePosition);
|
|
16256
|
+
return position;
|
|
16387
16257
|
}
|
|
16388
16258
|
/**
|
|
16389
|
-
*
|
|
16259
|
+
* Return the component (or ref's component) BoundingRect, relative
|
|
16260
|
+
* to the upper left corner of the screen (<body> element).
|
|
16261
|
+
*
|
|
16262
|
+
* Note: when used with a <Portal/> component, it will
|
|
16263
|
+
* return the portal position, not the teleported position.
|
|
16390
16264
|
*/
|
|
16391
|
-
function
|
|
16392
|
-
const
|
|
16393
|
-
|
|
16394
|
-
|
|
16395
|
-
|
|
16396
|
-
|
|
16397
|
-
|
|
16265
|
+
function useAbsoluteBoundingRect(ref) {
|
|
16266
|
+
const rect = owl.useState({ x: 0, y: 0, width: 0, height: 0 });
|
|
16267
|
+
function updateElRect() {
|
|
16268
|
+
const el = ref.el;
|
|
16269
|
+
if (el === null) {
|
|
16270
|
+
return;
|
|
16271
|
+
}
|
|
16272
|
+
const { top, left, width, height } = el.getBoundingClientRect();
|
|
16273
|
+
rect.x = left;
|
|
16274
|
+
rect.y = top;
|
|
16275
|
+
rect.width = width;
|
|
16276
|
+
rect.height = height;
|
|
16398
16277
|
}
|
|
16399
|
-
|
|
16278
|
+
owl.onMounted(updateElRect);
|
|
16279
|
+
owl.onPatched(updateElRect);
|
|
16280
|
+
return rect;
|
|
16400
16281
|
}
|
|
16401
16282
|
/**
|
|
16402
|
-
* Get the
|
|
16283
|
+
* Get the rectangle inside which a popover should stay when being displayed.
|
|
16284
|
+
* It's the value defined in `env.getPopoverContainerRect`, or the Rect of the "o-spreadsheet"
|
|
16285
|
+
* element by default.
|
|
16286
|
+
*
|
|
16287
|
+
* Coordinates are expressed expressed as absolute DOM position.
|
|
16403
16288
|
*/
|
|
16404
|
-
function
|
|
16405
|
-
|
|
16406
|
-
|
|
16407
|
-
|
|
16289
|
+
function usePopoverContainer() {
|
|
16290
|
+
const container = owl.useState({ x: 0, y: 0, width: 0, height: 0 });
|
|
16291
|
+
const component = owl.useComponent();
|
|
16292
|
+
const spreadsheetRect = useSpreadsheetRect();
|
|
16293
|
+
function updateRect() {
|
|
16294
|
+
const env = component.env;
|
|
16295
|
+
const newRect = "getPopoverContainerRect" in env ? env.getPopoverContainerRect() : spreadsheetRect;
|
|
16296
|
+
container.x = newRect.x;
|
|
16297
|
+
container.y = newRect.y;
|
|
16298
|
+
container.width = newRect.width;
|
|
16299
|
+
container.height = newRect.height;
|
|
16408
16300
|
}
|
|
16409
|
-
|
|
16410
|
-
|
|
16411
|
-
|
|
16412
|
-
|
|
16413
|
-
condition: (cell, cells) => {
|
|
16414
|
-
var _a;
|
|
16415
|
-
return cells.length === 1 && !cell.isFormula && !((_a = cell.format) === null || _a === void 0 ? void 0 : _a.match(DATETIME_FORMAT));
|
|
16416
|
-
},
|
|
16417
|
-
generateRule: () => {
|
|
16418
|
-
return { type: "COPY_MODIFIER" };
|
|
16419
|
-
},
|
|
16420
|
-
sequence: 10,
|
|
16421
|
-
})
|
|
16422
|
-
.add("increment_alphanumeric_value", {
|
|
16423
|
-
condition: (cell) => !cell.isFormula &&
|
|
16424
|
-
evaluateLiteral(cell.content).type === CellValueType.text &&
|
|
16425
|
-
alphaNumericValueRegExp.test(cell.content),
|
|
16426
|
-
generateRule: (cell, cells) => {
|
|
16427
|
-
const numberPostfix = parseInt(cell.content.match(numberPostfixRegExp)[0]);
|
|
16428
|
-
const prefix = cell.content.match(stringPrefixRegExp)[0];
|
|
16429
|
-
const numberPostfixLength = cell.content.length - prefix.length;
|
|
16430
|
-
const group = getGroup(cell, cells, (evaluatedCell) => evaluatedCell.type === CellValueType.text &&
|
|
16431
|
-
alphaNumericValueRegExp.test(evaluatedCell.value)) // get consecutive alphanumeric cells, no matter what the prefix is
|
|
16432
|
-
.filter((cell) => prefix === cell.value.toString().match(stringPrefixRegExp)[0])
|
|
16433
|
-
.map((cell) => parseInt(cell.value.toString().match(numberPostfixRegExp)[0]));
|
|
16434
|
-
const increment = calculateIncrementBasedOnGroup(group);
|
|
16435
|
-
return {
|
|
16436
|
-
type: "ALPHANUMERIC_INCREMENT_MODIFIER",
|
|
16437
|
-
prefix,
|
|
16438
|
-
current: numberPostfix,
|
|
16439
|
-
increment,
|
|
16440
|
-
numberPostfixLength,
|
|
16441
|
-
};
|
|
16442
|
-
},
|
|
16443
|
-
sequence: 15,
|
|
16444
|
-
})
|
|
16445
|
-
.add("copy_text", {
|
|
16446
|
-
condition: (cell) => !cell.isFormula && evaluateLiteral(cell.content).type === CellValueType.text,
|
|
16447
|
-
generateRule: () => {
|
|
16448
|
-
return { type: "COPY_MODIFIER" };
|
|
16449
|
-
},
|
|
16450
|
-
sequence: 20,
|
|
16451
|
-
})
|
|
16452
|
-
.add("update_formula", {
|
|
16453
|
-
condition: (cell) => cell.isFormula,
|
|
16454
|
-
generateRule: (_, cells) => {
|
|
16455
|
-
return { type: "FORMULA_MODIFIER", increment: cells.length, current: 0 };
|
|
16456
|
-
},
|
|
16457
|
-
sequence: 30,
|
|
16458
|
-
})
|
|
16459
|
-
.add("increment_number", {
|
|
16460
|
-
condition: (cell) => !cell.isFormula && evaluateLiteral(cell.content).type === CellValueType.number,
|
|
16461
|
-
generateRule: (cell, cells) => {
|
|
16462
|
-
const group = getGroup(cell, cells, (evaluatedCell) => evaluatedCell.type === CellValueType.number).map((cell) => Number(cell.value));
|
|
16463
|
-
const increment = calculateIncrementBasedOnGroup(group);
|
|
16464
|
-
const evaluation = evaluateLiteral(cell.content);
|
|
16465
|
-
return {
|
|
16466
|
-
type: "INCREMENT_MODIFIER",
|
|
16467
|
-
increment,
|
|
16468
|
-
current: evaluation.type === CellValueType.number ? evaluation.value : 0,
|
|
16469
|
-
};
|
|
16470
|
-
},
|
|
16471
|
-
sequence: 40,
|
|
16472
|
-
});
|
|
16473
|
-
|
|
16474
|
-
const ERROR_TOOLTIP_MAX_HEIGHT = 80;
|
|
16475
|
-
const ERROR_TOOLTIP_WIDTH = 180;
|
|
16476
|
-
css /* scss */ `
|
|
16477
|
-
.o-error-tooltip {
|
|
16478
|
-
font-size: 13px;
|
|
16479
|
-
background-color: white;
|
|
16480
|
-
border-left: 3px solid red;
|
|
16481
|
-
padding: 10px;
|
|
16482
|
-
width: ${ERROR_TOOLTIP_WIDTH}px;
|
|
16483
|
-
box-sizing: border-box !important;
|
|
16301
|
+
updateRect();
|
|
16302
|
+
owl.onMounted(updateRect);
|
|
16303
|
+
owl.onPatched(updateRect);
|
|
16304
|
+
return container;
|
|
16484
16305
|
}
|
|
16485
|
-
|
|
16486
|
-
|
|
16306
|
+
|
|
16307
|
+
/**
|
|
16308
|
+
* Compute the intersection of two rectangles. Returns nothing if the two rectangles don't overlap
|
|
16309
|
+
*/
|
|
16310
|
+
function rectIntersection(rect1, rect2) {
|
|
16311
|
+
return zoneToRect(intersection(rectToZone(rect1), rectToZone(rect2)));
|
|
16487
16312
|
}
|
|
16488
|
-
|
|
16489
|
-
|
|
16490
|
-
|
|
16491
|
-
|
|
16492
|
-
|
|
16493
|
-
|
|
16494
|
-
|
|
16495
|
-
|
|
16496
|
-
|
|
16497
|
-
|
|
16498
|
-
|
|
16499
|
-
|
|
16500
|
-
|
|
16501
|
-
|
|
16502
|
-
|
|
16503
|
-
|
|
16504
|
-
|
|
16505
|
-
|
|
16506
|
-
return { isOpen: false };
|
|
16507
|
-
},
|
|
16508
|
-
};
|
|
16313
|
+
function rectToZone(rect) {
|
|
16314
|
+
return {
|
|
16315
|
+
left: rect.x,
|
|
16316
|
+
top: rect.y,
|
|
16317
|
+
right: rect.x + rect.width,
|
|
16318
|
+
bottom: rect.y + rect.height,
|
|
16319
|
+
};
|
|
16320
|
+
}
|
|
16321
|
+
function zoneToRect(zone) {
|
|
16322
|
+
if (!zone)
|
|
16323
|
+
return undefined;
|
|
16324
|
+
return {
|
|
16325
|
+
x: zone.left,
|
|
16326
|
+
y: zone.top,
|
|
16327
|
+
width: zone.right - zone.left,
|
|
16328
|
+
height: zone.bottom - zone.top,
|
|
16329
|
+
};
|
|
16330
|
+
}
|
|
16509
16331
|
|
|
16510
|
-
css /*
|
|
16511
|
-
.o-
|
|
16512
|
-
|
|
16513
|
-
|
|
16514
|
-
|
|
16515
|
-
|
|
16516
|
-
|
|
16517
|
-
|
|
16332
|
+
css /* scss */ `
|
|
16333
|
+
.o-popover {
|
|
16334
|
+
position: absolute;
|
|
16335
|
+
z-index: ${ComponentsImportance.Popover};
|
|
16336
|
+
overflow: auto;
|
|
16337
|
+
box-shadow: 1px 2px 5px 2px rgb(51 51 51 / 15%);
|
|
16338
|
+
width: fit-content;
|
|
16339
|
+
height: fit-content;
|
|
16518
16340
|
}
|
|
16519
16341
|
`;
|
|
16520
|
-
class
|
|
16342
|
+
class Popover extends owl.Component {
|
|
16521
16343
|
constructor() {
|
|
16522
16344
|
super(...arguments);
|
|
16523
|
-
this.
|
|
16345
|
+
this.popoverRef = owl.useRef("popover");
|
|
16346
|
+
this.currentPosition = undefined;
|
|
16347
|
+
this.currentDisplayValue = undefined;
|
|
16348
|
+
this.spreadsheetRect = useSpreadsheetRect();
|
|
16524
16349
|
}
|
|
16525
16350
|
setup() {
|
|
16526
|
-
|
|
16527
|
-
|
|
16528
|
-
|
|
16351
|
+
this.containerRect = usePopoverContainer();
|
|
16352
|
+
// useEffect occurs after the DOM is created and the element width/height are computed, but before
|
|
16353
|
+
// the element in rendered, so we can still set its position
|
|
16354
|
+
owl.useEffect(() => {
|
|
16355
|
+
var _a, _b, _c, _d;
|
|
16356
|
+
if (!this.containerRect)
|
|
16357
|
+
throw new Error("Popover container is not defined");
|
|
16358
|
+
const el = this.popoverRef.el;
|
|
16359
|
+
const anchor = rectIntersection(this.props.anchorRect, this.containerRect);
|
|
16360
|
+
const newDisplay = anchor ? "block" : "none";
|
|
16361
|
+
if (this.currentDisplayValue !== "none" && newDisplay === "none") {
|
|
16362
|
+
(_b = (_a = this.props).onPopoverHidden) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
16363
|
+
}
|
|
16364
|
+
el.style.display = newDisplay;
|
|
16365
|
+
this.currentDisplayValue = newDisplay;
|
|
16366
|
+
if (!anchor)
|
|
16367
|
+
return;
|
|
16368
|
+
const propsMaxSize = { width: this.props.maxWidth, height: this.props.maxHeight };
|
|
16369
|
+
const elDims = {
|
|
16370
|
+
width: el.getBoundingClientRect().width,
|
|
16371
|
+
height: el.getBoundingClientRect().height,
|
|
16372
|
+
};
|
|
16373
|
+
const spreadsheetRect = this.spreadsheetRect;
|
|
16374
|
+
const popoverPositionHelper = this.props.positioning === "BottomLeft"
|
|
16375
|
+
? new BottomLeftPopoverContext(anchor, this.containerRect, propsMaxSize, spreadsheetRect)
|
|
16376
|
+
: new TopRightPopoverContext(anchor, this.containerRect, propsMaxSize, spreadsheetRect);
|
|
16377
|
+
const style = popoverPositionHelper.getCss(elDims, this.props.verticalOffset);
|
|
16378
|
+
for (const property of Object.keys(style)) {
|
|
16379
|
+
el.style[property] = style[property];
|
|
16380
|
+
}
|
|
16381
|
+
const newPosition = popoverPositionHelper.getCurrentPosition(elDims);
|
|
16382
|
+
if (this.currentPosition && newPosition !== this.currentPosition) {
|
|
16383
|
+
(_d = (_c = this.props).onPopoverMoved) === null || _d === void 0 ? void 0 : _d.call(_c);
|
|
16529
16384
|
}
|
|
16385
|
+
this.currentPosition = newPosition;
|
|
16530
16386
|
});
|
|
16531
16387
|
}
|
|
16532
|
-
|
|
16533
|
-
|
|
16534
|
-
|
|
16535
|
-
|
|
16388
|
+
}
|
|
16389
|
+
Popover.template = "o-spreadsheet-Popover";
|
|
16390
|
+
Popover.defaultProps = {
|
|
16391
|
+
positioning: "BottomLeft",
|
|
16392
|
+
verticalOffset: 0,
|
|
16393
|
+
onMouseWheel: () => { },
|
|
16394
|
+
onPopoverMoved: () => { },
|
|
16395
|
+
onPopoverHidden: () => { },
|
|
16396
|
+
};
|
|
16397
|
+
Popover.props = {
|
|
16398
|
+
anchorRect: Object,
|
|
16399
|
+
containerRect: { type: Object, optional: true },
|
|
16400
|
+
positioning: { type: String, optional: true },
|
|
16401
|
+
maxWidth: { type: Number, optional: true },
|
|
16402
|
+
maxHeight: { type: Number, optional: true },
|
|
16403
|
+
verticalOffset: { type: Number, optional: true },
|
|
16404
|
+
onMouseWheel: { type: Function, optional: true },
|
|
16405
|
+
onPopoverHidden: { type: Function, optional: true },
|
|
16406
|
+
onPopoverMoved: { type: Function, optional: true },
|
|
16407
|
+
slots: Object,
|
|
16408
|
+
};
|
|
16409
|
+
class PopoverPositionContext {
|
|
16410
|
+
constructor(anchorRect, containerRect, propsMaxSize, spreadsheetOffset) {
|
|
16411
|
+
this.anchorRect = anchorRect;
|
|
16412
|
+
this.containerRect = containerRect;
|
|
16413
|
+
this.propsMaxSize = propsMaxSize;
|
|
16414
|
+
this.spreadsheetOffset = spreadsheetOffset;
|
|
16415
|
+
}
|
|
16416
|
+
/** Check if there is enough space for the popover to be rendered at the bottom of the anchorRect */
|
|
16417
|
+
shouldRenderAtBottom(elementHeight) {
|
|
16418
|
+
return (elementHeight <= this.availableHeightDown ||
|
|
16419
|
+
this.availableHeightDown >= this.availableHeightUp);
|
|
16420
|
+
}
|
|
16421
|
+
/** Check if there is enough space for the popover to be rendered at the right of the anchorRect */
|
|
16422
|
+
shouldRenderAtRight(elementWidth) {
|
|
16423
|
+
return (elementWidth <= this.availableWidthRight ||
|
|
16424
|
+
this.availableWidthRight >= this.availableWidthLeft);
|
|
16425
|
+
}
|
|
16426
|
+
getMaxHeight(elementHeight) {
|
|
16427
|
+
const shouldRenderAtBottom = this.shouldRenderAtBottom(elementHeight);
|
|
16428
|
+
const availableHeight = shouldRenderAtBottom
|
|
16429
|
+
? this.availableHeightDown
|
|
16430
|
+
: this.availableHeightUp;
|
|
16431
|
+
return this.propsMaxSize.height
|
|
16432
|
+
? Math.min(availableHeight, this.propsMaxSize.height)
|
|
16433
|
+
: availableHeight;
|
|
16434
|
+
}
|
|
16435
|
+
getMaxWidth(elementWidth) {
|
|
16436
|
+
const shouldRenderAtRight = this.shouldRenderAtRight(elementWidth);
|
|
16437
|
+
const availableWidth = shouldRenderAtRight ? this.availableWidthRight : this.availableWidthLeft;
|
|
16438
|
+
return this.propsMaxSize.width
|
|
16439
|
+
? Math.min(availableWidth, this.propsMaxSize.width)
|
|
16440
|
+
: availableWidth;
|
|
16441
|
+
}
|
|
16442
|
+
getCss(elDims, verticalOffset) {
|
|
16443
|
+
const maxHeight = this.getMaxHeight(elDims.height);
|
|
16444
|
+
const maxWidth = this.getMaxWidth(elDims.width);
|
|
16445
|
+
const actualHeight = Math.min(maxHeight, elDims.height);
|
|
16446
|
+
const actualWidth = Math.min(maxWidth, elDims.width);
|
|
16447
|
+
const shouldRenderAtBottom = this.shouldRenderAtBottom(elDims.height);
|
|
16448
|
+
const shouldRenderAtRight = this.shouldRenderAtRight(elDims.width);
|
|
16449
|
+
verticalOffset = shouldRenderAtBottom ? verticalOffset : -verticalOffset;
|
|
16450
|
+
const cssProperties = {
|
|
16451
|
+
"max-height": maxHeight + "px",
|
|
16452
|
+
"max-width": maxWidth + "px",
|
|
16453
|
+
top: this.getTopCoordinate(actualHeight, shouldRenderAtBottom) -
|
|
16454
|
+
this.spreadsheetOffset.y -
|
|
16455
|
+
verticalOffset +
|
|
16456
|
+
"px",
|
|
16457
|
+
left: this.getLeftCoordinate(actualWidth, shouldRenderAtRight) - this.spreadsheetOffset.x + "px",
|
|
16458
|
+
};
|
|
16459
|
+
return cssProperties;
|
|
16460
|
+
}
|
|
16461
|
+
getCurrentPosition(elDims) {
|
|
16462
|
+
const shouldRenderAtBottom = this.shouldRenderAtBottom(elDims.height);
|
|
16463
|
+
const shouldRenderAtRight = this.shouldRenderAtRight(elDims.width);
|
|
16464
|
+
if (shouldRenderAtBottom && shouldRenderAtRight)
|
|
16465
|
+
return "BottomRight";
|
|
16466
|
+
if (shouldRenderAtBottom && !shouldRenderAtRight)
|
|
16467
|
+
return "BottomLeft";
|
|
16468
|
+
if (!shouldRenderAtBottom && shouldRenderAtRight)
|
|
16469
|
+
return "TopRight";
|
|
16470
|
+
return "TopLeft";
|
|
16471
|
+
}
|
|
16472
|
+
}
|
|
16473
|
+
class BottomLeftPopoverContext extends PopoverPositionContext {
|
|
16474
|
+
get availableHeightUp() {
|
|
16475
|
+
return this.anchorRect.y - this.containerRect.y;
|
|
16476
|
+
}
|
|
16477
|
+
get availableHeightDown() {
|
|
16478
|
+
return this.containerRect.height - this.availableHeightUp - this.anchorRect.height;
|
|
16479
|
+
}
|
|
16480
|
+
get availableWidthRight() {
|
|
16481
|
+
return this.containerRect.x + this.containerRect.width - this.anchorRect.x;
|
|
16482
|
+
}
|
|
16483
|
+
get availableWidthLeft() {
|
|
16484
|
+
return this.anchorRect.x + this.anchorRect.width - this.containerRect.x;
|
|
16485
|
+
}
|
|
16486
|
+
getTopCoordinate(elementHeight, shouldRenderAtBottom) {
|
|
16487
|
+
if (shouldRenderAtBottom) {
|
|
16488
|
+
return this.anchorRect.y + this.anchorRect.height;
|
|
16489
|
+
}
|
|
16490
|
+
else {
|
|
16491
|
+
return this.anchorRect.y - elementHeight;
|
|
16492
|
+
}
|
|
16493
|
+
}
|
|
16494
|
+
getLeftCoordinate(elementWidth, shouldRenderAtRight) {
|
|
16495
|
+
if (shouldRenderAtRight) {
|
|
16496
|
+
return this.anchorRect.x;
|
|
16497
|
+
}
|
|
16498
|
+
else {
|
|
16499
|
+
return this.anchorRect.x + this.anchorRect.width - elementWidth;
|
|
16536
16500
|
}
|
|
16537
|
-
(_b = (_a = this.itemRef.el).scrollIntoView) === null || _b === void 0 ? void 0 : _b.call(_a, {
|
|
16538
|
-
block: this.props.scrolledTo === "bottom" ? "end" : "start",
|
|
16539
|
-
});
|
|
16540
16501
|
}
|
|
16541
16502
|
}
|
|
16542
|
-
|
|
16543
|
-
|
|
16544
|
-
|
|
16545
|
-
|
|
16546
|
-
|
|
16547
|
-
|
|
16548
|
-
|
|
16549
|
-
|
|
16550
|
-
|
|
16503
|
+
class TopRightPopoverContext extends PopoverPositionContext {
|
|
16504
|
+
get availableHeightUp() {
|
|
16505
|
+
return this.anchorRect.y + this.anchorRect.height - this.containerRect.y;
|
|
16506
|
+
}
|
|
16507
|
+
get availableHeightDown() {
|
|
16508
|
+
return this.containerRect.y + this.containerRect.height - this.anchorRect.y;
|
|
16509
|
+
}
|
|
16510
|
+
get availableWidthRight() {
|
|
16511
|
+
return this.containerRect.width - this.anchorRect.width - this.availableWidthLeft;
|
|
16512
|
+
}
|
|
16513
|
+
get availableWidthLeft() {
|
|
16514
|
+
return this.anchorRect.x - this.containerRect.x;
|
|
16515
|
+
}
|
|
16516
|
+
getTopCoordinate(elementHeight, shouldRenderAtBottom) {
|
|
16517
|
+
if (shouldRenderAtBottom) {
|
|
16518
|
+
return this.anchorRect.y;
|
|
16519
|
+
}
|
|
16520
|
+
else {
|
|
16521
|
+
return this.anchorRect.y + this.anchorRect.height - elementHeight;
|
|
16522
|
+
}
|
|
16523
|
+
}
|
|
16524
|
+
getLeftCoordinate(elementWidth, shouldRenderAtRight) {
|
|
16525
|
+
if (shouldRenderAtRight) {
|
|
16526
|
+
return this.anchorRect.x + this.anchorRect.width;
|
|
16527
|
+
}
|
|
16528
|
+
else {
|
|
16529
|
+
return this.anchorRect.x - elementWidth;
|
|
16530
|
+
}
|
|
16531
|
+
}
|
|
16532
|
+
}
|
|
16551
16533
|
|
|
16552
|
-
|
|
16553
|
-
|
|
16554
|
-
|
|
16555
|
-
|
|
16556
|
-
|
|
16557
|
-
|
|
16558
|
-
|
|
16534
|
+
//------------------------------------------------------------------------------
|
|
16535
|
+
// Context Menu Component
|
|
16536
|
+
//------------------------------------------------------------------------------
|
|
16537
|
+
css /* scss */ `
|
|
16538
|
+
.o-menu {
|
|
16539
|
+
background-color: white;
|
|
16540
|
+
padding: ${MENU_VERTICAL_PADDING}px 0px;
|
|
16541
|
+
width: ${MENU_WIDTH}px;
|
|
16542
|
+
box-sizing: border-box !important;
|
|
16559
16543
|
|
|
16560
|
-
.o-
|
|
16544
|
+
.o-menu-item {
|
|
16561
16545
|
display: flex;
|
|
16546
|
+
justify-content: space-between;
|
|
16547
|
+
align-items: center;
|
|
16562
16548
|
box-sizing: border-box;
|
|
16563
16549
|
height: ${MENU_ITEM_HEIGHT}px;
|
|
16564
|
-
padding: 4px
|
|
16550
|
+
padding: 4px 16px;
|
|
16565
16551
|
cursor: pointer;
|
|
16566
16552
|
user-select: none;
|
|
16567
16553
|
|
|
16568
|
-
|
|
16569
|
-
|
|
16570
|
-
|
|
16571
|
-
|
|
16572
|
-
|
|
16573
|
-
input {
|
|
16574
|
-
box-sizing: border-box;
|
|
16575
|
-
margin-bottom: 5px;
|
|
16576
|
-
border: 1px solid #949494;
|
|
16577
|
-
height: 24px;
|
|
16578
|
-
padding-right: 28px;
|
|
16579
|
-
}
|
|
16580
|
-
|
|
16581
|
-
.o-search-icon {
|
|
16582
|
-
right: 5px;
|
|
16583
|
-
top: 4px;
|
|
16584
|
-
|
|
16585
|
-
svg {
|
|
16586
|
-
height: 16px;
|
|
16587
|
-
width: 16px;
|
|
16588
|
-
vertical-align: middle;
|
|
16554
|
+
.o-menu-item-name {
|
|
16555
|
+
overflow: hidden;
|
|
16556
|
+
white-space: nowrap;
|
|
16557
|
+
text-overflow: ellipsis;
|
|
16589
16558
|
}
|
|
16590
|
-
}
|
|
16591
|
-
|
|
16592
|
-
.o-filter-menu-actions {
|
|
16593
|
-
display: flex;
|
|
16594
|
-
flex-direction: row;
|
|
16595
|
-
margin-bottom: 4px;
|
|
16596
16559
|
|
|
16597
|
-
|
|
16598
|
-
|
|
16599
|
-
|
|
16600
|
-
color: blue;
|
|
16601
|
-
text-decoration: underline;
|
|
16560
|
+
&.o-menu-root {
|
|
16561
|
+
display: flex;
|
|
16562
|
+
justify-content: space-between;
|
|
16602
16563
|
}
|
|
16603
|
-
|
|
16604
|
-
|
|
16605
|
-
|
|
16606
|
-
flex: auto;
|
|
16607
|
-
overflow-y: auto;
|
|
16608
|
-
border: 1px solid #949494;
|
|
16609
|
-
|
|
16610
|
-
.o-filter-menu-no-values {
|
|
16611
|
-
color: #949494;
|
|
16612
|
-
font-style: italic;
|
|
16564
|
+
.o-menu-item-icon {
|
|
16565
|
+
margin-top: auto;
|
|
16566
|
+
margin-bottom: auto;
|
|
16613
16567
|
}
|
|
16614
|
-
|
|
16615
|
-
|
|
16616
|
-
.o-filter-menu-buttons {
|
|
16617
|
-
margin-top: 9px;
|
|
16618
|
-
|
|
16619
|
-
.o-filter-menu-button {
|
|
16620
|
-
border: 1px solid lightgrey;
|
|
16621
|
-
padding: 6px 10px;
|
|
16622
|
-
cursor: pointer;
|
|
16623
|
-
border-radius: 4px;
|
|
16624
|
-
font-weight: 500;
|
|
16625
|
-
line-height: 16px;
|
|
16568
|
+
.o-icon {
|
|
16569
|
+
width: 10px;
|
|
16626
16570
|
}
|
|
16627
16571
|
|
|
16628
|
-
.
|
|
16629
|
-
|
|
16630
|
-
|
|
16631
|
-
background-color:
|
|
16572
|
+
&:not(.disabled) {
|
|
16573
|
+
&:hover,
|
|
16574
|
+
&.o-menu-item-active {
|
|
16575
|
+
background-color: #ebebeb;
|
|
16632
16576
|
}
|
|
16633
|
-
|
|
16634
|
-
|
|
16635
|
-
.o-filter-menu-button-primary {
|
|
16636
|
-
background-color: #188038;
|
|
16637
|
-
&:hover {
|
|
16638
|
-
background-color: #1d9641;
|
|
16577
|
+
.o-menu-item-description {
|
|
16578
|
+
color: grey;
|
|
16639
16579
|
}
|
|
16640
|
-
|
|
16641
|
-
|
|
16642
|
-
|
|
16580
|
+
}
|
|
16581
|
+
&.disabled {
|
|
16582
|
+
color: ${MENU_ITEM_DISABLED_COLOR};
|
|
16583
|
+
cursor: not-allowed;
|
|
16643
16584
|
}
|
|
16644
16585
|
}
|
|
16645
16586
|
}
|
|
16646
16587
|
`;
|
|
16647
|
-
class
|
|
16588
|
+
class Menu extends owl.Component {
|
|
16648
16589
|
constructor() {
|
|
16649
16590
|
super(...arguments);
|
|
16650
|
-
this.
|
|
16651
|
-
|
|
16652
|
-
|
|
16653
|
-
|
|
16591
|
+
this.subMenu = owl.useState({
|
|
16592
|
+
isOpen: false,
|
|
16593
|
+
position: null,
|
|
16594
|
+
scrollOffset: 0,
|
|
16595
|
+
menuItems: [],
|
|
16654
16596
|
});
|
|
16655
|
-
this.
|
|
16597
|
+
this.menuRef = owl.useRef("menu");
|
|
16598
|
+
this.position = useAbsoluteBoundingRect(this.menuRef);
|
|
16656
16599
|
}
|
|
16657
16600
|
setup() {
|
|
16601
|
+
owl.useExternalListener(window, "click", this.onExternalClick, { capture: true });
|
|
16602
|
+
owl.useExternalListener(window, "contextmenu", this.onExternalClick, { capture: true });
|
|
16658
16603
|
owl.onWillUpdateProps((nextProps) => {
|
|
16659
|
-
if (
|
|
16660
|
-
this.
|
|
16604
|
+
if (nextProps.menuItems !== this.props.menuItems) {
|
|
16605
|
+
this.closeSubMenu();
|
|
16661
16606
|
}
|
|
16662
16607
|
});
|
|
16663
|
-
this.state.values = this.getFilterValues(this.props.filterPosition);
|
|
16664
16608
|
}
|
|
16665
|
-
|
|
16666
|
-
|
|
16667
|
-
|
|
16668
|
-
|
|
16669
|
-
|
|
16609
|
+
get visibleMenuItems() {
|
|
16610
|
+
return this.props.menuItems.filter((x) => x.isVisible(this.env));
|
|
16611
|
+
}
|
|
16612
|
+
get subMenuPosition() {
|
|
16613
|
+
const position = Object.assign({}, this.subMenu.position);
|
|
16614
|
+
position.y -= this.subMenu.scrollOffset || 0;
|
|
16615
|
+
return position;
|
|
16616
|
+
}
|
|
16617
|
+
get menuHeight() {
|
|
16618
|
+
const menuItems = this.visibleMenuItems;
|
|
16619
|
+
let menuItemsHeight = this.getMenuItemsHeight(menuItems);
|
|
16620
|
+
// We don't display separator at the end of a menu
|
|
16621
|
+
if (menuItems[menuItems.length - 1].separator) {
|
|
16622
|
+
menuItemsHeight -= MENU_SEPARATOR_HEIGHT;
|
|
16670
16623
|
}
|
|
16671
|
-
const
|
|
16672
|
-
|
|
16673
|
-
.map(({ col, row }) => this.env.model.getters.getEvaluatedCell({ sheetId, col, row }).formattedValue);
|
|
16674
|
-
const filterValues = this.env.model.getters.getFilterValues({ sheetId, ...position });
|
|
16675
|
-
const strValues = [...cellValues, ...filterValues];
|
|
16676
|
-
const normalizedFilteredValues = filterValues.map(toLowerCase);
|
|
16677
|
-
// Set with lowercase values to avoid duplicates
|
|
16678
|
-
const normalizedValues = [...new Set(strValues.map(toLowerCase))];
|
|
16679
|
-
const sortedValues = normalizedValues.sort((val1, val2) => val1.localeCompare(val2, undefined, { numeric: true, sensitivity: "base" }));
|
|
16680
|
-
return sortedValues.map((normalizedValue) => {
|
|
16681
|
-
const checked = normalizedFilteredValues.findIndex((filteredValue) => filteredValue === normalizedValue) ===
|
|
16682
|
-
-1;
|
|
16683
|
-
return {
|
|
16684
|
-
checked,
|
|
16685
|
-
string: strValues.find((val) => toLowerCase(val) === normalizedValue) || "",
|
|
16686
|
-
};
|
|
16687
|
-
});
|
|
16624
|
+
const menuHeight = 2 * MENU_VERTICAL_PADDING + menuItemsHeight;
|
|
16625
|
+
return this.props.maxHeight ? Math.min(menuHeight, this.props.maxHeight) : menuHeight;
|
|
16688
16626
|
}
|
|
16689
|
-
|
|
16690
|
-
|
|
16691
|
-
|
|
16692
|
-
|
|
16693
|
-
|
|
16627
|
+
get popoverProps() {
|
|
16628
|
+
const isRoot = this.props.depth === 1;
|
|
16629
|
+
return {
|
|
16630
|
+
anchorRect: {
|
|
16631
|
+
x: this.props.position.x - MENU_WIDTH * (this.props.depth - 1),
|
|
16632
|
+
y: this.props.position.y,
|
|
16633
|
+
width: isRoot ? 0 : MENU_WIDTH,
|
|
16634
|
+
height: isRoot ? 0 : MENU_ITEM_HEIGHT,
|
|
16635
|
+
},
|
|
16636
|
+
positioning: "TopRight",
|
|
16637
|
+
verticalOffset: isRoot ? 0 : MENU_VERTICAL_PADDING,
|
|
16638
|
+
onPopoverHidden: () => this.closeSubMenu(),
|
|
16639
|
+
onPopoverMoved: () => this.closeSubMenu(),
|
|
16640
|
+
maxHeight: this.menuHeight,
|
|
16641
|
+
};
|
|
16694
16642
|
}
|
|
16695
|
-
|
|
16696
|
-
|
|
16643
|
+
getColor(menu) {
|
|
16644
|
+
return menu.textColor ? `color: ${menu.textColor}` : undefined;
|
|
16697
16645
|
}
|
|
16698
|
-
|
|
16699
|
-
|
|
16646
|
+
async activateMenu(menu) {
|
|
16647
|
+
var _a, _b, _c;
|
|
16648
|
+
const result = await ((_a = menu.action) === null || _a === void 0 ? void 0 : _a.call(menu, this.env));
|
|
16649
|
+
this.close();
|
|
16650
|
+
(_c = (_b = this.props).onMenuClicked) === null || _c === void 0 ? void 0 : _c.call(_b, { detail: result });
|
|
16700
16651
|
}
|
|
16701
|
-
|
|
16702
|
-
this.
|
|
16652
|
+
close() {
|
|
16653
|
+
this.closeSubMenu();
|
|
16654
|
+
this.props.onClose();
|
|
16703
16655
|
}
|
|
16704
|
-
|
|
16705
|
-
|
|
16706
|
-
|
|
16707
|
-
|
|
16656
|
+
/**
|
|
16657
|
+
* Return the number of pixels between the top of the menu
|
|
16658
|
+
* and the menu item at a given index.
|
|
16659
|
+
*/
|
|
16660
|
+
subMenuVerticalPosition(menuIndex) {
|
|
16661
|
+
const menusAbove = this.visibleMenuItems.slice(0, menuIndex);
|
|
16662
|
+
return this.position.y + this.getMenuItemsHeight(menusAbove) + MENU_VERTICAL_PADDING;
|
|
16708
16663
|
}
|
|
16709
|
-
|
|
16710
|
-
|
|
16711
|
-
|
|
16664
|
+
onExternalClick(ev) {
|
|
16665
|
+
// Don't close a root menu when clicked to open the submenus.
|
|
16666
|
+
const el = this.menuRef.el;
|
|
16667
|
+
if (el && getOpenedMenus().some((el) => isChildEvent(el, ev))) {
|
|
16668
|
+
return;
|
|
16712
16669
|
}
|
|
16713
|
-
|
|
16670
|
+
ev.closedMenuId = this.props.menuId;
|
|
16671
|
+
this.close();
|
|
16714
16672
|
}
|
|
16715
|
-
|
|
16716
|
-
|
|
16717
|
-
|
|
16718
|
-
this.env.model.dispatch("UPDATE_FILTER", {
|
|
16719
|
-
...position,
|
|
16720
|
-
sheetId: this.env.model.getters.getActiveSheetId(),
|
|
16721
|
-
values: this.state.values.filter((val) => !val.checked).map((val) => val.string),
|
|
16722
|
-
});
|
|
16723
|
-
(_b = (_a = this.props).onClosed) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
16673
|
+
getMenuItemsHeight(menuItems) {
|
|
16674
|
+
const numberOfSeparators = menuItems.filter((m) => m.separator).length;
|
|
16675
|
+
return MENU_ITEM_HEIGHT * menuItems.length + MENU_SEPARATOR_HEIGHT * numberOfSeparators;
|
|
16724
16676
|
}
|
|
16725
|
-
|
|
16726
|
-
|
|
16727
|
-
(_b = (_a = this.props).onClosed) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
16677
|
+
getName(menu) {
|
|
16678
|
+
return menu.name(this.env);
|
|
16728
16679
|
}
|
|
16729
|
-
|
|
16730
|
-
|
|
16731
|
-
|
|
16732
|
-
|
|
16733
|
-
|
|
16734
|
-
|
|
16735
|
-
const index = displayedValues.findIndex((val) => val.string === this.state.selectedValue);
|
|
16736
|
-
selectedIndex = index === -1 ? undefined : index;
|
|
16737
|
-
}
|
|
16738
|
-
switch (ev.key) {
|
|
16739
|
-
case "ArrowDown":
|
|
16740
|
-
if (selectedIndex === undefined) {
|
|
16741
|
-
selectedIndex = 0;
|
|
16742
|
-
}
|
|
16743
|
-
else {
|
|
16744
|
-
selectedIndex = Math.min(selectedIndex + 1, displayedValues.length - 1);
|
|
16745
|
-
}
|
|
16746
|
-
ev.preventDefault();
|
|
16747
|
-
ev.stopPropagation();
|
|
16748
|
-
break;
|
|
16749
|
-
case "ArrowUp":
|
|
16750
|
-
if (selectedIndex === undefined) {
|
|
16751
|
-
selectedIndex = displayedValues.length - 1;
|
|
16752
|
-
}
|
|
16753
|
-
else {
|
|
16754
|
-
selectedIndex = Math.max(selectedIndex - 1, 0);
|
|
16755
|
-
}
|
|
16756
|
-
ev.preventDefault();
|
|
16757
|
-
ev.stopPropagation();
|
|
16758
|
-
break;
|
|
16759
|
-
case "Enter":
|
|
16760
|
-
if (selectedIndex !== undefined) {
|
|
16761
|
-
this.checkValue(displayedValues[selectedIndex]);
|
|
16762
|
-
}
|
|
16763
|
-
ev.stopPropagation();
|
|
16764
|
-
ev.preventDefault();
|
|
16765
|
-
break;
|
|
16766
|
-
}
|
|
16767
|
-
this.state.selectedValue =
|
|
16768
|
-
selectedIndex !== undefined ? displayedValues[selectedIndex].string : undefined;
|
|
16769
|
-
if (ev.key === "ArrowUp" || ev.key === "ArrowDown") {
|
|
16770
|
-
this.scrollListToSelectedValue(ev.key);
|
|
16680
|
+
isRoot(menu) {
|
|
16681
|
+
return !menu.action;
|
|
16682
|
+
}
|
|
16683
|
+
isEnabled(menu) {
|
|
16684
|
+
if (menu.isEnabled(this.env)) {
|
|
16685
|
+
return this.env.model.getters.isReadonly() ? menu.isReadonlyAllowed : true;
|
|
16771
16686
|
}
|
|
16687
|
+
return false;
|
|
16772
16688
|
}
|
|
16773
|
-
|
|
16774
|
-
this.
|
|
16689
|
+
onScroll(ev) {
|
|
16690
|
+
this.subMenu.scrollOffset = ev.target.scrollTop;
|
|
16775
16691
|
}
|
|
16776
|
-
|
|
16777
|
-
|
|
16778
|
-
|
|
16779
|
-
|
|
16780
|
-
|
|
16692
|
+
/**
|
|
16693
|
+
* If the given menu is not disabled, open it's submenu at the
|
|
16694
|
+
* correct position according to available surrounding space.
|
|
16695
|
+
*/
|
|
16696
|
+
openSubMenu(menu, menuIndex) {
|
|
16697
|
+
const y = this.subMenuVerticalPosition(menuIndex);
|
|
16698
|
+
this.subMenu.position = {
|
|
16699
|
+
x: this.position.x + this.props.depth * MENU_WIDTH,
|
|
16700
|
+
y: y - (this.subMenu.scrollOffset || 0),
|
|
16701
|
+
};
|
|
16702
|
+
this.subMenu.menuItems = menu.children(this.env);
|
|
16703
|
+
this.subMenu.isOpen = true;
|
|
16704
|
+
this.subMenu.parentMenu = menu;
|
|
16705
|
+
}
|
|
16706
|
+
isParentMenu(subMenu, menuItem) {
|
|
16707
|
+
var _a;
|
|
16708
|
+
return ((_a = subMenu.parentMenu) === null || _a === void 0 ? void 0 : _a.id) === menuItem.id;
|
|
16709
|
+
}
|
|
16710
|
+
closeSubMenu() {
|
|
16711
|
+
this.subMenu.isOpen = false;
|
|
16712
|
+
this.subMenu.parentMenu = undefined;
|
|
16713
|
+
}
|
|
16714
|
+
onClickMenu(menu, menuIndex) {
|
|
16715
|
+
if (this.isEnabled(menu)) {
|
|
16716
|
+
if (this.isRoot(menu)) {
|
|
16717
|
+
this.openSubMenu(menu, menuIndex);
|
|
16718
|
+
}
|
|
16719
|
+
else {
|
|
16720
|
+
this.activateMenu(menu);
|
|
16721
|
+
}
|
|
16781
16722
|
}
|
|
16782
16723
|
}
|
|
16783
|
-
|
|
16784
|
-
|
|
16785
|
-
|
|
16786
|
-
|
|
16787
|
-
|
|
16788
|
-
|
|
16724
|
+
onMouseOver(menu, position) {
|
|
16725
|
+
if (menu.isEnabled(this.env)) {
|
|
16726
|
+
if (this.isRoot(menu)) {
|
|
16727
|
+
this.openSubMenu(menu, position);
|
|
16728
|
+
}
|
|
16729
|
+
else {
|
|
16730
|
+
this.closeSubMenu();
|
|
16731
|
+
}
|
|
16789
16732
|
}
|
|
16790
|
-
const sheetId = this.env.model.getters.getActiveSheetId();
|
|
16791
|
-
this.env.model.dispatch("SORT_CELLS", {
|
|
16792
|
-
sheetId,
|
|
16793
|
-
col: filterPosition.col,
|
|
16794
|
-
row: filterTable.contentZone.top,
|
|
16795
|
-
zone: filterTable.contentZone,
|
|
16796
|
-
sortDirection,
|
|
16797
|
-
sortOptions: { emptyCellAsZero: true, sortHeaders: true },
|
|
16798
|
-
});
|
|
16799
|
-
(_b = (_a = this.props).onClosed) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
16800
16733
|
}
|
|
16801
16734
|
}
|
|
16802
|
-
|
|
16803
|
-
|
|
16804
|
-
|
|
16805
|
-
|
|
16806
|
-
FilterMenu.props = {
|
|
16807
|
-
filterPosition: Object,
|
|
16808
|
-
onClosed: { type: Function, optional: true },
|
|
16735
|
+
Menu.template = "o-spreadsheet-Menu";
|
|
16736
|
+
Menu.components = { Menu, Popover };
|
|
16737
|
+
Menu.defaultProps = {
|
|
16738
|
+
depth: 1,
|
|
16809
16739
|
};
|
|
16810
|
-
|
|
16811
|
-
|
|
16812
|
-
|
|
16813
|
-
|
|
16814
|
-
|
|
16815
|
-
|
|
16816
|
-
|
|
16817
|
-
|
|
16818
|
-
},
|
|
16740
|
+
Menu.props = {
|
|
16741
|
+
position: Object,
|
|
16742
|
+
menuItems: Array,
|
|
16743
|
+
depth: { type: Number, optional: true },
|
|
16744
|
+
maxHeight: { type: Number, optional: true },
|
|
16745
|
+
onClose: Function,
|
|
16746
|
+
onMenuClicked: { type: Function, optional: true },
|
|
16747
|
+
menuId: { type: String, optional: true },
|
|
16819
16748
|
};
|
|
16820
16749
|
|
|
16821
16750
|
const LINK_TOOLTIP_HEIGHT = 32;
|
|
@@ -17149,83 +17078,6 @@
|
|
|
17149
17078
|
const currenciesRegistry = new Registry();
|
|
17150
17079
|
|
|
17151
17080
|
class ImageFigure extends owl.Component {
|
|
17152
|
-
constructor() {
|
|
17153
|
-
super(...arguments);
|
|
17154
|
-
this.menuState = owl.useState({
|
|
17155
|
-
isOpen: false,
|
|
17156
|
-
position: null,
|
|
17157
|
-
});
|
|
17158
|
-
this.imageContainerRef = owl.useRef("o-image");
|
|
17159
|
-
this.menuButtonRef = owl.useRef("menuButton");
|
|
17160
|
-
this.menuButtonRect = useAbsoluteBoundingRect(this.menuButtonRef);
|
|
17161
|
-
this.position = useAbsoluteBoundingRect(this.imageContainerRef);
|
|
17162
|
-
this.menuItems = createMenu([
|
|
17163
|
-
{
|
|
17164
|
-
id: "copy",
|
|
17165
|
-
name: _lt("Copy"),
|
|
17166
|
-
description: "Ctrl+C",
|
|
17167
|
-
action: async () => {
|
|
17168
|
-
this.env.model.dispatch("SELECT_FIGURE", { id: this.figureId });
|
|
17169
|
-
this.env.model.dispatch("COPY");
|
|
17170
|
-
await this.env.clipboard.write(this.env.model.getters.getClipboardContent());
|
|
17171
|
-
},
|
|
17172
|
-
},
|
|
17173
|
-
{
|
|
17174
|
-
id: "cut",
|
|
17175
|
-
name: _lt("Cut"),
|
|
17176
|
-
description: "Ctrl+X",
|
|
17177
|
-
action: async () => {
|
|
17178
|
-
this.env.model.dispatch("SELECT_FIGURE", { id: this.figureId });
|
|
17179
|
-
this.env.model.dispatch("CUT");
|
|
17180
|
-
await this.env.clipboard.write(this.env.model.getters.getClipboardContent());
|
|
17181
|
-
},
|
|
17182
|
-
},
|
|
17183
|
-
{
|
|
17184
|
-
id: "reset_size",
|
|
17185
|
-
name: _lt("Reset size"),
|
|
17186
|
-
action: () => {
|
|
17187
|
-
const size = this.env.model.getters.getImageSize(this.figureId);
|
|
17188
|
-
const { height, width } = getMaxFigureSize(this.env.model.getters, size);
|
|
17189
|
-
this.env.model.dispatch("UPDATE_FIGURE", {
|
|
17190
|
-
sheetId: this.env.model.getters.getActiveSheetId(),
|
|
17191
|
-
id: this.figureId,
|
|
17192
|
-
height,
|
|
17193
|
-
width,
|
|
17194
|
-
});
|
|
17195
|
-
},
|
|
17196
|
-
},
|
|
17197
|
-
{
|
|
17198
|
-
id: "delete",
|
|
17199
|
-
name: _lt("Delete image"),
|
|
17200
|
-
description: "delete",
|
|
17201
|
-
action: () => {
|
|
17202
|
-
this.env.model.dispatch("DELETE_FIGURE", {
|
|
17203
|
-
sheetId: this.env.model.getters.getActiveSheetId(),
|
|
17204
|
-
id: this.figureId,
|
|
17205
|
-
});
|
|
17206
|
-
},
|
|
17207
|
-
},
|
|
17208
|
-
]);
|
|
17209
|
-
}
|
|
17210
|
-
onContextMenu(ev) {
|
|
17211
|
-
const position = {
|
|
17212
|
-
x: this.position.x + ev.offsetX,
|
|
17213
|
-
y: this.position.y + ev.offsetY,
|
|
17214
|
-
};
|
|
17215
|
-
this.openContextMenu(position);
|
|
17216
|
-
}
|
|
17217
|
-
showMenu() {
|
|
17218
|
-
const { x, y, width } = this.menuButtonRect;
|
|
17219
|
-
const menuPosition = {
|
|
17220
|
-
x: x >= MENU_WIDTH ? x - MENU_WIDTH : x + width,
|
|
17221
|
-
y: y,
|
|
17222
|
-
};
|
|
17223
|
-
this.openContextMenu(menuPosition);
|
|
17224
|
-
}
|
|
17225
|
-
openContextMenu(position) {
|
|
17226
|
-
this.menuState.isOpen = true;
|
|
17227
|
-
this.menuState.position = position;
|
|
17228
|
-
}
|
|
17229
17081
|
// ---------------------------------------------------------------------------
|
|
17230
17082
|
// Getters
|
|
17231
17083
|
// ---------------------------------------------------------------------------
|
|
@@ -17237,20 +17089,105 @@
|
|
|
17237
17089
|
}
|
|
17238
17090
|
}
|
|
17239
17091
|
ImageFigure.template = "o-spreadsheet-ImageFigure";
|
|
17240
|
-
ImageFigure.components = {
|
|
17092
|
+
ImageFigure.components = {};
|
|
17241
17093
|
ImageFigure.props = {
|
|
17242
17094
|
figure: Object,
|
|
17243
17095
|
onFigureDeleted: Function,
|
|
17244
17096
|
};
|
|
17245
17097
|
|
|
17246
17098
|
const figureRegistry = new Registry();
|
|
17247
|
-
figureRegistry.add("chart", {
|
|
17099
|
+
figureRegistry.add("chart", {
|
|
17100
|
+
Component: ChartFigure,
|
|
17101
|
+
SidePanelComponent: "ChartPanel",
|
|
17102
|
+
menuBuilder: getChartMenu,
|
|
17103
|
+
});
|
|
17248
17104
|
figureRegistry.add("image", {
|
|
17249
17105
|
Component: ImageFigure,
|
|
17250
17106
|
keepRatio: true,
|
|
17251
17107
|
minFigSize: 20,
|
|
17252
17108
|
borderWidth: 0,
|
|
17253
|
-
|
|
17109
|
+
menuBuilder: getImageMenuRegistry,
|
|
17110
|
+
});
|
|
17111
|
+
function getChartMenu(figureId, onFigureDeleted, env) {
|
|
17112
|
+
const menuItemSpecs = [
|
|
17113
|
+
{
|
|
17114
|
+
id: "edit",
|
|
17115
|
+
name: _lt("Edit"),
|
|
17116
|
+
sequence: 1,
|
|
17117
|
+
action: () => {
|
|
17118
|
+
env.model.dispatch("SELECT_FIGURE", { id: figureId });
|
|
17119
|
+
env.openSidePanel("ChartPanel");
|
|
17120
|
+
},
|
|
17121
|
+
},
|
|
17122
|
+
getCopyMenuItem(figureId, env),
|
|
17123
|
+
getCutMenuItem(figureId, env),
|
|
17124
|
+
getDeleteMenuItem(figureId, onFigureDeleted, env),
|
|
17125
|
+
];
|
|
17126
|
+
return createMenu(menuItemSpecs);
|
|
17127
|
+
}
|
|
17128
|
+
function getImageMenuRegistry(figureId, onFigureDeleted, env) {
|
|
17129
|
+
const menuItemSpecs = [
|
|
17130
|
+
getCopyMenuItem(figureId, env),
|
|
17131
|
+
getCutMenuItem(figureId, env),
|
|
17132
|
+
{
|
|
17133
|
+
id: "reset_size",
|
|
17134
|
+
name: _lt("Reset size"),
|
|
17135
|
+
sequence: 4,
|
|
17136
|
+
action: () => {
|
|
17137
|
+
const size = env.model.getters.getImageSize(figureId);
|
|
17138
|
+
const { height, width } = getMaxFigureSize(env.model.getters, size);
|
|
17139
|
+
env.model.dispatch("UPDATE_FIGURE", {
|
|
17140
|
+
sheetId: env.model.getters.getActiveSheetId(),
|
|
17141
|
+
id: figureId,
|
|
17142
|
+
height,
|
|
17143
|
+
width,
|
|
17144
|
+
});
|
|
17145
|
+
},
|
|
17146
|
+
},
|
|
17147
|
+
getDeleteMenuItem(figureId, onFigureDeleted, env),
|
|
17148
|
+
];
|
|
17149
|
+
return createMenu(menuItemSpecs);
|
|
17150
|
+
}
|
|
17151
|
+
function getCopyMenuItem(figureId, env) {
|
|
17152
|
+
return {
|
|
17153
|
+
id: "copy",
|
|
17154
|
+
name: _lt("Copy"),
|
|
17155
|
+
sequence: 2,
|
|
17156
|
+
description: "Ctrl+C",
|
|
17157
|
+
action: async () => {
|
|
17158
|
+
env.model.dispatch("SELECT_FIGURE", { id: figureId });
|
|
17159
|
+
env.model.dispatch("COPY");
|
|
17160
|
+
await env.clipboard.write(env.model.getters.getClipboardContent());
|
|
17161
|
+
},
|
|
17162
|
+
};
|
|
17163
|
+
}
|
|
17164
|
+
function getCutMenuItem(figureId, env) {
|
|
17165
|
+
return {
|
|
17166
|
+
id: "cut",
|
|
17167
|
+
name: _lt("Cut"),
|
|
17168
|
+
sequence: 3,
|
|
17169
|
+
description: "Ctrl+X",
|
|
17170
|
+
action: async () => {
|
|
17171
|
+
env.model.dispatch("SELECT_FIGURE", { id: figureId });
|
|
17172
|
+
env.model.dispatch("CUT");
|
|
17173
|
+
await env.clipboard.write(env.model.getters.getClipboardContent());
|
|
17174
|
+
},
|
|
17175
|
+
};
|
|
17176
|
+
}
|
|
17177
|
+
function getDeleteMenuItem(figureId, onFigureDeleted, env) {
|
|
17178
|
+
return {
|
|
17179
|
+
id: "delete",
|
|
17180
|
+
name: _lt("Delete"),
|
|
17181
|
+
sequence: 10,
|
|
17182
|
+
action: () => {
|
|
17183
|
+
env.model.dispatch("DELETE_FIGURE", {
|
|
17184
|
+
sheetId: env.model.getters.getActiveSheetId(),
|
|
17185
|
+
id: figureId,
|
|
17186
|
+
});
|
|
17187
|
+
onFigureDeleted();
|
|
17188
|
+
},
|
|
17189
|
+
};
|
|
17190
|
+
}
|
|
17254
17191
|
|
|
17255
17192
|
const inverseCommandRegistry = new Registry()
|
|
17256
17193
|
.add("ADD_COLUMNS_ROWS", inverseAddColumnsRows)
|
|
@@ -18425,7 +18362,7 @@
|
|
|
18425
18362
|
}
|
|
18426
18363
|
onColorClick(color) {
|
|
18427
18364
|
if (color) {
|
|
18428
|
-
this.props.onColorPicked(color);
|
|
18365
|
+
this.props.onColorPicked(toHex(color));
|
|
18429
18366
|
}
|
|
18430
18367
|
}
|
|
18431
18368
|
getCheckMarkColor() {
|
|
@@ -18440,8 +18377,10 @@
|
|
|
18440
18377
|
this.state.isCurrentColorInvalid = true;
|
|
18441
18378
|
return;
|
|
18442
18379
|
}
|
|
18380
|
+
const color = toHex(this.state.currentColor);
|
|
18443
18381
|
this.state.isCurrentColorInvalid = false;
|
|
18444
|
-
this.props.onColorPicked(
|
|
18382
|
+
this.props.onColorPicked(color);
|
|
18383
|
+
this.state.currentColor = color;
|
|
18445
18384
|
}
|
|
18446
18385
|
toggleColorPicker() {
|
|
18447
18386
|
this.state.showGradient = !this.state.showGradient;
|
|
@@ -20178,9 +20117,8 @@
|
|
|
20178
20117
|
|
|
20179
20118
|
.o-figure-menu {
|
|
20180
20119
|
right: 0px;
|
|
20120
|
+
top: 0px;
|
|
20181
20121
|
display: none;
|
|
20182
|
-
position: absolute;
|
|
20183
|
-
padding: 5px;
|
|
20184
20122
|
}
|
|
20185
20123
|
|
|
20186
20124
|
.o-figure-menu-item {
|
|
@@ -20198,7 +20136,10 @@
|
|
|
20198
20136
|
class FigureComponent extends owl.Component {
|
|
20199
20137
|
constructor() {
|
|
20200
20138
|
super(...arguments);
|
|
20139
|
+
this.menuState = owl.useState({ isOpen: false, position: null, menuItems: [] });
|
|
20201
20140
|
this.figureRef = owl.useRef("figure");
|
|
20141
|
+
this.menuButtonRef = owl.useRef("menuButton");
|
|
20142
|
+
this.menuButtonRect = useAbsoluteBoundingRect(this.menuButtonRef);
|
|
20202
20143
|
}
|
|
20203
20144
|
get isSelected() {
|
|
20204
20145
|
return this.env.model.getters.getSelectedFigureId() === this.props.figure.id;
|
|
@@ -20306,9 +20247,33 @@
|
|
|
20306
20247
|
break;
|
|
20307
20248
|
}
|
|
20308
20249
|
}
|
|
20250
|
+
onContextMenu(ev) {
|
|
20251
|
+
if (this.env.isDashboard())
|
|
20252
|
+
return;
|
|
20253
|
+
const position = {
|
|
20254
|
+
x: ev.clientX,
|
|
20255
|
+
y: ev.clientY,
|
|
20256
|
+
};
|
|
20257
|
+
this.openContextMenu(position);
|
|
20258
|
+
}
|
|
20259
|
+
showMenu() {
|
|
20260
|
+
const { x, y, width } = this.menuButtonRect;
|
|
20261
|
+
const menuPosition = {
|
|
20262
|
+
x: x >= MENU_WIDTH ? x - MENU_WIDTH : x + width,
|
|
20263
|
+
y: y,
|
|
20264
|
+
};
|
|
20265
|
+
this.openContextMenu(menuPosition);
|
|
20266
|
+
}
|
|
20267
|
+
openContextMenu(position) {
|
|
20268
|
+
this.menuState.isOpen = true;
|
|
20269
|
+
this.menuState.position = position;
|
|
20270
|
+
this.menuState.menuItems = figureRegistry
|
|
20271
|
+
.get(this.props.figure.tag)
|
|
20272
|
+
.menuBuilder(this.props.figure.id, this.props.onFigureDeleted, this.env);
|
|
20273
|
+
}
|
|
20309
20274
|
}
|
|
20310
20275
|
FigureComponent.template = "o-spreadsheet-FigureComponent";
|
|
20311
|
-
FigureComponent.components = {};
|
|
20276
|
+
FigureComponent.components = { Menu };
|
|
20312
20277
|
FigureComponent.defaultProps = {
|
|
20313
20278
|
onFigureDeleted: () => { },
|
|
20314
20279
|
onMouseDown: () => { },
|
|
@@ -22393,6 +22358,7 @@
|
|
|
22393
22358
|
theme: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme",
|
|
22394
22359
|
table: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/table",
|
|
22395
22360
|
hyperlink: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink",
|
|
22361
|
+
image: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
|
|
22396
22362
|
};
|
|
22397
22363
|
const RELATIONSHIP_NSR = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
|
|
22398
22364
|
const HEIGHT_FACTOR = 0.75; // 100px => 75 u
|
|
@@ -23416,12 +23382,8 @@
|
|
|
23416
23382
|
return "n";
|
|
23417
23383
|
}
|
|
23418
23384
|
}
|
|
23419
|
-
/**
|
|
23420
|
-
* For some reason, Excel will only take the devicePixelRatio (i.e. interface scale on Windows desktop)
|
|
23421
|
-
* into account for the height.
|
|
23422
|
-
*/
|
|
23423
23385
|
function convertHeightToExcel(height) {
|
|
23424
|
-
return Math.round(HEIGHT_FACTOR * height *
|
|
23386
|
+
return Math.round(HEIGHT_FACTOR * height * 100) / 100;
|
|
23425
23387
|
}
|
|
23426
23388
|
function convertWidthToExcel(width) {
|
|
23427
23389
|
return Math.round(WIDTH_FACTOR * width * 100) / 100;
|
|
@@ -23563,6 +23525,19 @@
|
|
|
23563
23525
|
}
|
|
23564
23526
|
return xlsxId + 1;
|
|
23565
23527
|
}
|
|
23528
|
+
const imageIds = [];
|
|
23529
|
+
/**
|
|
23530
|
+
* Convert a image o-spreadsheet id to a xlsx id which
|
|
23531
|
+
* are unsigned integers (starting from 1).
|
|
23532
|
+
*/
|
|
23533
|
+
function convertImageId(imageId) {
|
|
23534
|
+
const xlsxId = imageIds.findIndex((id) => id === imageId);
|
|
23535
|
+
if (xlsxId === -1) {
|
|
23536
|
+
imageIds.push(imageId);
|
|
23537
|
+
return imageIds.length;
|
|
23538
|
+
}
|
|
23539
|
+
return xlsxId + 1;
|
|
23540
|
+
}
|
|
23566
23541
|
/**
|
|
23567
23542
|
* Convert a value expressed in dot to EMU.
|
|
23568
23543
|
* EMU = English Metrical Unit
|
|
@@ -26324,6 +26299,7 @@
|
|
|
26324
26299
|
return {
|
|
26325
26300
|
...createEmptySheet(sheetId, name),
|
|
26326
26301
|
charts: [],
|
|
26302
|
+
images: [],
|
|
26327
26303
|
};
|
|
26328
26304
|
}
|
|
26329
26305
|
function createEmptyExcelWorkbookData() {
|
|
@@ -28959,7 +28935,7 @@
|
|
|
28959
28935
|
filters.push(new Filter(filter.id, filterZone));
|
|
28960
28936
|
}
|
|
28961
28937
|
// Add filters for new columns
|
|
28962
|
-
if (filters.length < zoneToDimension(zone).
|
|
28938
|
+
if (filters.length < zoneToDimension(zone).numberOfCols) {
|
|
28963
28939
|
for (let col = zone.left; col <= zone.right; col++) {
|
|
28964
28940
|
if (!filters.find((filter) => filter.col === col)) {
|
|
28965
28941
|
filters.push(new Filter(this.uuidGenerator.uuidv4(), { ...zone, left: col, right: col }));
|
|
@@ -29610,6 +29586,24 @@
|
|
|
29610
29586
|
}
|
|
29611
29587
|
}
|
|
29612
29588
|
}
|
|
29589
|
+
exportForExcel(data) {
|
|
29590
|
+
for (const sheet of data.sheets) {
|
|
29591
|
+
const figures = this.getters.getFigures(sheet.id);
|
|
29592
|
+
const images = [];
|
|
29593
|
+
for (const figure of figures) {
|
|
29594
|
+
if ((figure === null || figure === void 0 ? void 0 : figure.tag) === "image") {
|
|
29595
|
+
const image = this.getImage(figure.id);
|
|
29596
|
+
if (image) {
|
|
29597
|
+
images.push({
|
|
29598
|
+
...figure,
|
|
29599
|
+
data: deepCopy(image),
|
|
29600
|
+
});
|
|
29601
|
+
}
|
|
29602
|
+
}
|
|
29603
|
+
}
|
|
29604
|
+
sheet.images = images;
|
|
29605
|
+
}
|
|
29606
|
+
}
|
|
29613
29607
|
getAllImages() {
|
|
29614
29608
|
const images = [];
|
|
29615
29609
|
for (const sheetId in this.images) {
|
|
@@ -29807,8 +29801,8 @@
|
|
|
29807
29801
|
if (merge) {
|
|
29808
29802
|
return isEqual(zone, merge);
|
|
29809
29803
|
}
|
|
29810
|
-
const {
|
|
29811
|
-
return
|
|
29804
|
+
const { numberOfCols, numberOfRows } = zoneToDimension(zone);
|
|
29805
|
+
return numberOfCols === 1 && numberOfRows === 1;
|
|
29812
29806
|
}
|
|
29813
29807
|
// ---------------------------------------------------------------------------
|
|
29814
29808
|
// Merges
|
|
@@ -29976,8 +29970,8 @@
|
|
|
29976
29970
|
this.removeMerge(sheetId, currentZone);
|
|
29977
29971
|
break;
|
|
29978
29972
|
default:
|
|
29979
|
-
const {
|
|
29980
|
-
if (
|
|
29973
|
+
const { numberOfCols, numberOfRows } = zoneToDimension(result.range.zone);
|
|
29974
|
+
if (numberOfCols === 1 && numberOfRows === 1) {
|
|
29981
29975
|
this.removeMerge(sheetId, currentZone);
|
|
29982
29976
|
}
|
|
29983
29977
|
else {
|
|
@@ -30736,8 +30730,8 @@
|
|
|
30736
30730
|
}
|
|
30737
30731
|
getSheetSize(sheetId) {
|
|
30738
30732
|
return {
|
|
30739
|
-
|
|
30740
|
-
|
|
30733
|
+
numberOfRows: this.getNumberRows(sheetId),
|
|
30734
|
+
numberOfCols: this.getNumberCols(sheetId),
|
|
30741
30735
|
};
|
|
30742
30736
|
}
|
|
30743
30737
|
getSheetZone(sheetId) {
|
|
@@ -32253,6 +32247,9 @@
|
|
|
32253
32247
|
case "CREATE_SHEET":
|
|
32254
32248
|
this.filterValues[cmd.sheetId] = {};
|
|
32255
32249
|
break;
|
|
32250
|
+
case "HIDE_COLUMNS_ROWS":
|
|
32251
|
+
this.updateHiddenRows();
|
|
32252
|
+
break;
|
|
32256
32253
|
case "UPDATE_FILTER":
|
|
32257
32254
|
this.updateFilter(cmd);
|
|
32258
32255
|
this.updateHiddenRows();
|
|
@@ -32361,9 +32358,16 @@
|
|
|
32361
32358
|
updateHiddenRows() {
|
|
32362
32359
|
var _a, _b;
|
|
32363
32360
|
const sheetId = this.getters.getActiveSheetId();
|
|
32364
|
-
const filters = this.getters
|
|
32361
|
+
const filters = this.getters
|
|
32362
|
+
.getFilters(sheetId)
|
|
32363
|
+
.sort((filter1, filter2) => filter1.zoneWithHeaders.top - filter2.zoneWithHeaders.top);
|
|
32365
32364
|
const hiddenRows = new Set();
|
|
32366
32365
|
for (let filter of filters) {
|
|
32366
|
+
// Disable filters whose header are hidden
|
|
32367
|
+
if (this.getters.isRowHiddenByUser(sheetId, filter.zoneWithHeaders.top))
|
|
32368
|
+
continue;
|
|
32369
|
+
if (hiddenRows.has(filter.zoneWithHeaders.top))
|
|
32370
|
+
continue;
|
|
32367
32371
|
const filteredValues = (_b = (_a = this.filterValues[sheetId]) === null || _a === void 0 ? void 0 : _a[filter.id]) === null || _b === void 0 ? void 0 : _b.map(toLowerCase);
|
|
32368
32372
|
if (!filteredValues || !filter.filteredZone)
|
|
32369
32373
|
continue;
|
|
@@ -32386,7 +32390,7 @@
|
|
|
32386
32390
|
const tableZone = toZone(tableData.range);
|
|
32387
32391
|
const filters = [];
|
|
32388
32392
|
const headerNames = [];
|
|
32389
|
-
for (const i of range(0, zoneToDimension(tableZone).
|
|
32393
|
+
for (const i of range(0, zoneToDimension(tableZone).numberOfCols)) {
|
|
32390
32394
|
const position = {
|
|
32391
32395
|
sheetId: sheetData.id,
|
|
32392
32396
|
col: tableZone.left + i,
|
|
@@ -33897,7 +33901,7 @@
|
|
|
33897
33901
|
dimensionsToSum(sheetId, zone) {
|
|
33898
33902
|
const dimensions = new Set();
|
|
33899
33903
|
if (isOneDimensional(zone)) {
|
|
33900
|
-
dimensions.add(zoneToDimension(zone).
|
|
33904
|
+
dimensions.add(zoneToDimension(zone).numberOfCols === 1 ? "COL" : "ROW");
|
|
33901
33905
|
return dimensions;
|
|
33902
33906
|
}
|
|
33903
33907
|
if (this.lastColIsEmpty(sheetId, zone)) {
|
|
@@ -34485,8 +34489,8 @@
|
|
|
34485
34489
|
x.zone.bottom < this.getters.getNumberRows(x.sheetId) &&
|
|
34486
34490
|
x.zone.right < this.getters.getNumberCols(x.sheetId))
|
|
34487
34491
|
.map((highlight) => {
|
|
34488
|
-
const {
|
|
34489
|
-
const zone =
|
|
34492
|
+
const { numberOfRows, numberOfCols } = zoneToDimension(highlight.zone);
|
|
34493
|
+
const zone = numberOfRows * numberOfCols === 1
|
|
34490
34494
|
? this.getters.expandZone(highlight.sheetId, highlight.zone)
|
|
34491
34495
|
: highlight.zone;
|
|
34492
34496
|
return {
|
|
@@ -36393,7 +36397,7 @@
|
|
|
36393
36397
|
const merges = this.getters.getMerges(sheetId).filter((merge) => overlap(merge, zone));
|
|
36394
36398
|
/*Test the presence of merges of different sizes*/
|
|
36395
36399
|
const mergeDimension = zoneToDimension(merges[0]);
|
|
36396
|
-
let [widthFirst, heightFirst] = [mergeDimension.
|
|
36400
|
+
let [widthFirst, heightFirst] = [mergeDimension.numberOfCols, mergeDimension.numberOfRows];
|
|
36397
36401
|
if (!merges.every((merge) => {
|
|
36398
36402
|
let [widthCurrent, heightCurrent] = [
|
|
36399
36403
|
merge.right - merge.left + 1,
|
|
@@ -37526,9 +37530,9 @@
|
|
|
37526
37530
|
const values = this.values;
|
|
37527
37531
|
const pasteZone = this.getPasteZone(target);
|
|
37528
37532
|
const { left: activeCol, top: activeRow } = pasteZone;
|
|
37529
|
-
const {
|
|
37533
|
+
const { numberOfCols, numberOfRows } = zoneToDimension(pasteZone);
|
|
37530
37534
|
const sheetId = this.getters.getActiveSheetId();
|
|
37531
|
-
this.addMissingDimensions(
|
|
37535
|
+
this.addMissingDimensions(numberOfCols, numberOfRows, activeCol, activeRow);
|
|
37532
37536
|
for (let i = 0; i < values.length; i++) {
|
|
37533
37537
|
for (let j = 0; j < values[i].length; j++) {
|
|
37534
37538
|
this.dispatch("UPDATE_CELL", {
|
|
@@ -37542,8 +37546,8 @@
|
|
|
37542
37546
|
const zone = {
|
|
37543
37547
|
left: activeCol,
|
|
37544
37548
|
top: activeRow,
|
|
37545
|
-
right: activeCol +
|
|
37546
|
-
bottom: activeRow +
|
|
37549
|
+
right: activeCol + numberOfCols - 1,
|
|
37550
|
+
bottom: activeRow + numberOfRows - 1,
|
|
37547
37551
|
};
|
|
37548
37552
|
this.selection.selectZone({ cell: { col: activeCol, row: activeRow }, zone });
|
|
37549
37553
|
}
|
|
@@ -43642,7 +43646,7 @@
|
|
|
43642
43646
|
}
|
|
43643
43647
|
}
|
|
43644
43648
|
|
|
43645
|
-
function createDrawing(
|
|
43649
|
+
function createDrawing(drawingRelIds, sheet, figures) {
|
|
43646
43650
|
const namespaces = [
|
|
43647
43651
|
["xmlns:xdr", NAMESPACE.drawing],
|
|
43648
43652
|
["xmlns:r", RELATIONSHIP_NSR],
|
|
@@ -43651,46 +43655,14 @@
|
|
|
43651
43655
|
];
|
|
43652
43656
|
const figuresNodes = [];
|
|
43653
43657
|
for (const [figureIndex, figure] of Object.entries(figures)) {
|
|
43654
|
-
|
|
43655
|
-
|
|
43656
|
-
|
|
43657
|
-
|
|
43658
|
-
|
|
43659
|
-
|
|
43660
|
-
|
|
43661
|
-
|
|
43662
|
-
figuresNodes.push(escapeXml /*xml*/ `
|
|
43663
|
-
<xdr:twoCellAnchor>
|
|
43664
|
-
<xdr:from>
|
|
43665
|
-
<xdr:col>${from.col}</xdr:col>
|
|
43666
|
-
<xdr:colOff>${from.colOff}</xdr:colOff>
|
|
43667
|
-
<xdr:row>${from.row}</xdr:row>
|
|
43668
|
-
<xdr:rowOff>${from.rowOff}</xdr:rowOff>
|
|
43669
|
-
</xdr:from>
|
|
43670
|
-
<xdr:to>
|
|
43671
|
-
<xdr:col>${to.col}</xdr:col>
|
|
43672
|
-
<xdr:colOff>${to.colOff}</xdr:colOff>
|
|
43673
|
-
<xdr:row>${to.row}</xdr:row>
|
|
43674
|
-
<xdr:rowOff>${to.rowOff}</xdr:rowOff>
|
|
43675
|
-
</xdr:to>
|
|
43676
|
-
<xdr:graphicFrame>
|
|
43677
|
-
<xdr:nvGraphicFramePr>
|
|
43678
|
-
<xdr:cNvPr ${formatAttributes(cNvPrAttrs)} />
|
|
43679
|
-
<xdr:cNvGraphicFramePr />
|
|
43680
|
-
</xdr:nvGraphicFramePr>
|
|
43681
|
-
<xdr:xfrm>
|
|
43682
|
-
<a:off x="0" y="0"/>
|
|
43683
|
-
<a:ext cx="0" cy="0"/>
|
|
43684
|
-
</xdr:xfrm>
|
|
43685
|
-
<a:graphic>
|
|
43686
|
-
<a:graphicData uri="${DRAWING_NS_C}">
|
|
43687
|
-
<c:chart r:id="${chartRelIds[figureIndex]}" />
|
|
43688
|
-
</a:graphicData>
|
|
43689
|
-
</a:graphic>
|
|
43690
|
-
</xdr:graphicFrame>
|
|
43691
|
-
<xdr:clientData fLocksWithSheet="0"/>
|
|
43692
|
-
</xdr:twoCellAnchor>
|
|
43693
|
-
`);
|
|
43658
|
+
switch (figure === null || figure === void 0 ? void 0 : figure.tag) {
|
|
43659
|
+
case "chart":
|
|
43660
|
+
figuresNodes.push(createChartDrawing(figure, sheet, drawingRelIds[figureIndex]));
|
|
43661
|
+
break;
|
|
43662
|
+
case "image":
|
|
43663
|
+
figuresNodes.push(createImageDrawing(figure, sheet, drawingRelIds[figureIndex]));
|
|
43664
|
+
break;
|
|
43665
|
+
}
|
|
43694
43666
|
}
|
|
43695
43667
|
const xml = escapeXml /*xml*/ `
|
|
43696
43668
|
<xdr:wsDr ${formatAttributes(namespaces)}>
|
|
@@ -43745,6 +43717,93 @@
|
|
|
43745
43717
|
index: headers.length - 1,
|
|
43746
43718
|
offset: convertDotValueToEMU(position - currentPosition + FIGURE_BORDER_SIZE),
|
|
43747
43719
|
};
|
|
43720
|
+
}
|
|
43721
|
+
function createChartDrawing(figure, sheet, chartRelId) {
|
|
43722
|
+
// position
|
|
43723
|
+
const { from, to } = convertFigureData(figure, sheet);
|
|
43724
|
+
const chartId = convertChartId(figure.id);
|
|
43725
|
+
const cNvPrAttrs = [
|
|
43726
|
+
["id", chartId],
|
|
43727
|
+
["name", `Chart ${chartId}`],
|
|
43728
|
+
["title", "Chart"],
|
|
43729
|
+
];
|
|
43730
|
+
return escapeXml /*xml*/ `
|
|
43731
|
+
<xdr:twoCellAnchor>
|
|
43732
|
+
<xdr:from>
|
|
43733
|
+
<xdr:col>${from.col}</xdr:col>
|
|
43734
|
+
<xdr:colOff>${from.colOff}</xdr:colOff>
|
|
43735
|
+
<xdr:row>${from.row}</xdr:row>
|
|
43736
|
+
<xdr:rowOff>${from.rowOff}</xdr:rowOff>
|
|
43737
|
+
</xdr:from>
|
|
43738
|
+
<xdr:to>
|
|
43739
|
+
<xdr:col>${to.col}</xdr:col>
|
|
43740
|
+
<xdr:colOff>${to.colOff}</xdr:colOff>
|
|
43741
|
+
<xdr:row>${to.row}</xdr:row>
|
|
43742
|
+
<xdr:rowOff>${to.rowOff}</xdr:rowOff>
|
|
43743
|
+
</xdr:to>
|
|
43744
|
+
<xdr:graphicFrame>
|
|
43745
|
+
<xdr:nvGraphicFramePr>
|
|
43746
|
+
<xdr:cNvPr ${formatAttributes(cNvPrAttrs)} />
|
|
43747
|
+
<xdr:cNvGraphicFramePr />
|
|
43748
|
+
</xdr:nvGraphicFramePr>
|
|
43749
|
+
<xdr:xfrm>
|
|
43750
|
+
<a:off x="0" y="0"/>
|
|
43751
|
+
<a:ext cx="0" cy="0"/>
|
|
43752
|
+
</xdr:xfrm>
|
|
43753
|
+
<a:graphic>
|
|
43754
|
+
<a:graphicData uri="${DRAWING_NS_C}">
|
|
43755
|
+
<c:chart r:id="${chartRelId}" />
|
|
43756
|
+
</a:graphicData>
|
|
43757
|
+
</a:graphic>
|
|
43758
|
+
</xdr:graphicFrame>
|
|
43759
|
+
<xdr:clientData fLocksWithSheet="0"/>
|
|
43760
|
+
</xdr:twoCellAnchor>
|
|
43761
|
+
`;
|
|
43762
|
+
}
|
|
43763
|
+
function createImageDrawing(figure, sheet, imageRelId) {
|
|
43764
|
+
// position
|
|
43765
|
+
const { from, to } = convertFigureData(figure, sheet);
|
|
43766
|
+
const imageId = convertImageId(figure.id);
|
|
43767
|
+
const cNvPrAttrs = [
|
|
43768
|
+
["id", imageId],
|
|
43769
|
+
["name", `Image ${imageId}`],
|
|
43770
|
+
["title", "Image"],
|
|
43771
|
+
];
|
|
43772
|
+
return escapeXml /*xml*/ `
|
|
43773
|
+
<xdr:twoCellAnchor editAs="oneCell">
|
|
43774
|
+
<xdr:from>
|
|
43775
|
+
<xdr:col>${from.col}</xdr:col>
|
|
43776
|
+
<xdr:colOff>${from.colOff}</xdr:colOff>
|
|
43777
|
+
<xdr:row>${from.row}</xdr:row>
|
|
43778
|
+
<xdr:rowOff>${from.rowOff}</xdr:rowOff>
|
|
43779
|
+
</xdr:from>
|
|
43780
|
+
<xdr:to>
|
|
43781
|
+
<xdr:col>${to.col}</xdr:col>
|
|
43782
|
+
<xdr:colOff>${to.colOff}</xdr:colOff>
|
|
43783
|
+
<xdr:row>${to.row}</xdr:row>
|
|
43784
|
+
<xdr:rowOff>${to.rowOff}</xdr:rowOff>
|
|
43785
|
+
</xdr:to>
|
|
43786
|
+
<xdr:pic>
|
|
43787
|
+
<xdr:nvPicPr>
|
|
43788
|
+
<xdr:cNvPr ${formatAttributes(cNvPrAttrs)}/>
|
|
43789
|
+
<xdr:cNvPicPr preferRelativeResize="0"/>
|
|
43790
|
+
</xdr:nvPicPr>
|
|
43791
|
+
<xdr:blipFill>
|
|
43792
|
+
<a:blip cstate="print" r:embed="${imageRelId}"/>
|
|
43793
|
+
<a:stretch>
|
|
43794
|
+
<a:fillRect/>
|
|
43795
|
+
</a:stretch>
|
|
43796
|
+
</xdr:blipFill>
|
|
43797
|
+
<xdr:spPr>
|
|
43798
|
+
<a:prstGeom prst="rect">
|
|
43799
|
+
<a:avLst/>
|
|
43800
|
+
</a:prstGeom>
|
|
43801
|
+
<a:noFill/>
|
|
43802
|
+
</xdr:spPr>
|
|
43803
|
+
</xdr:pic>
|
|
43804
|
+
<xdr:clientData fLocksWithSheet="0"/>
|
|
43805
|
+
</xdr:twoCellAnchor>
|
|
43806
|
+
`;
|
|
43748
43807
|
}
|
|
43749
43808
|
|
|
43750
43809
|
function addNumberFormats(numFmts) {
|
|
@@ -43940,7 +43999,7 @@
|
|
|
43940
43999
|
function addFilterColumns(table) {
|
|
43941
44000
|
const tableZone = toZone(table.range);
|
|
43942
44001
|
const columns = [];
|
|
43943
|
-
for (const i of range(0, zoneToDimension(tableZone).
|
|
44002
|
+
for (const i of range(0, zoneToDimension(tableZone).numberOfCols)) {
|
|
43944
44003
|
const filter = table.filters[i];
|
|
43945
44004
|
if (!filter || !filter.filteredValues.length) {
|
|
43946
44005
|
continue;
|
|
@@ -43966,7 +44025,7 @@
|
|
|
43966
44025
|
var _a;
|
|
43967
44026
|
const tableZone = toZone(table.range);
|
|
43968
44027
|
const columns = [];
|
|
43969
|
-
for (const i of range(0, zoneToDimension(tableZone).
|
|
44028
|
+
for (const i of range(0, zoneToDimension(tableZone).numberOfCols)) {
|
|
43970
44029
|
const colHeaderXc = toXC(tableZone.left + i, tableZone.top);
|
|
43971
44030
|
const colName = ((_a = sheetData.cells[colHeaderXc]) === null || _a === void 0 ? void 0 : _a.content) || `col${i}`;
|
|
43972
44031
|
const colAttributes = [
|
|
@@ -44214,23 +44273,37 @@
|
|
|
44214
44273
|
currentTableIndex += sheet.filterTables.length;
|
|
44215
44274
|
// Figures and Charts
|
|
44216
44275
|
let drawingNode = escapeXml ``;
|
|
44276
|
+
const drawingRelIds = [];
|
|
44217
44277
|
const charts = sheet.charts;
|
|
44218
|
-
|
|
44219
|
-
const
|
|
44220
|
-
|
|
44221
|
-
|
|
44222
|
-
|
|
44223
|
-
|
|
44224
|
-
|
|
44225
|
-
|
|
44226
|
-
|
|
44227
|
-
|
|
44228
|
-
|
|
44278
|
+
for (const chart of charts) {
|
|
44279
|
+
const xlsxChartId = convertChartId(chart.id);
|
|
44280
|
+
const chartRelId = addRelsToFile(construct.relsFiles, `xl/drawings/_rels/drawing${sheetIndex}.xml.rels`, {
|
|
44281
|
+
target: `../charts/chart${xlsxChartId}.xml`,
|
|
44282
|
+
type: XLSX_RELATION_TYPE.chart,
|
|
44283
|
+
});
|
|
44284
|
+
drawingRelIds.push(chartRelId);
|
|
44285
|
+
files.push(createXMLFile(createChart(chart, sheetIndex, data), `xl/charts/chart${xlsxChartId}.xml`, "chart"));
|
|
44286
|
+
}
|
|
44287
|
+
const images = sheet.images;
|
|
44288
|
+
for (const image of images) {
|
|
44289
|
+
const xlsxImageId = convertImageId(image.id);
|
|
44290
|
+
const imageRelId = addRelsToFile(construct.relsFiles, `xl/drawings/_rels/drawing${sheetIndex}.xml.rels`, {
|
|
44291
|
+
target: `../media/image${xlsxImageId}`,
|
|
44292
|
+
type: XLSX_RELATION_TYPE.image,
|
|
44293
|
+
});
|
|
44294
|
+
drawingRelIds.push(imageRelId);
|
|
44295
|
+
files.push({
|
|
44296
|
+
path: `xl/media/image${xlsxImageId}`,
|
|
44297
|
+
imagePath: image.data.path,
|
|
44298
|
+
});
|
|
44299
|
+
}
|
|
44300
|
+
const drawings = [...charts, ...images];
|
|
44301
|
+
if (drawings.length) {
|
|
44229
44302
|
const drawingRelId = addRelsToFile(construct.relsFiles, `xl/worksheets/_rels/sheet${sheetIndex}.xml.rels`, {
|
|
44230
44303
|
target: `../drawings/drawing${sheetIndex}.xml`,
|
|
44231
44304
|
type: XLSX_RELATION_TYPE.drawing,
|
|
44232
44305
|
});
|
|
44233
|
-
files.push(createXMLFile(createDrawing(
|
|
44306
|
+
files.push(createXMLFile(createDrawing(drawingRelIds, sheet, drawings), `xl/drawings/drawing${sheetIndex}.xml`, "drawing"));
|
|
44234
44307
|
drawingNode = escapeXml /*xml*/ `<drawing r:id="${drawingRelId}" />`;
|
|
44235
44308
|
}
|
|
44236
44309
|
const sheetXml = escapeXml /*xml*/ `
|
|
@@ -44344,7 +44417,7 @@
|
|
|
44344
44417
|
function createContentTypes(files) {
|
|
44345
44418
|
const overrideNodes = [];
|
|
44346
44419
|
for (const file of files) {
|
|
44347
|
-
if (file.contentType) {
|
|
44420
|
+
if ("contentType" in file && file.contentType) {
|
|
44348
44421
|
overrideNodes.push(createOverride("/" + file.path, CONTENT_TYPES[file.contentType]));
|
|
44349
44422
|
}
|
|
44350
44423
|
}
|
|
@@ -44902,9 +44975,9 @@
|
|
|
44902
44975
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
44903
44976
|
|
|
44904
44977
|
|
|
44905
|
-
__info__.version = '16.2.0-alpha.
|
|
44906
|
-
__info__.date = '2023-03-
|
|
44907
|
-
__info__.hash = '
|
|
44978
|
+
__info__.version = '16.2.0-alpha.4';
|
|
44979
|
+
__info__.date = '2023-03-14T09:24:38.219Z';
|
|
44980
|
+
__info__.hash = '2c6fe88';
|
|
44908
44981
|
|
|
44909
44982
|
|
|
44910
44983
|
})(this.o_spreadsheet = this.o_spreadsheet || {}, owl);
|