From 7578cfa1092c68e8fac8978219aeeb81a8cb1f38 Mon Sep 17 00:00:00 2001 From: Pierre Rousseau Date: Thu, 6 Oct 2022 09:08:49 +0000 Subject: [PATCH] [IMP] spreadsheet: update o_spreadsheet to latest version ### Contains the following commits: https://github.com/odoo/o-spreadsheet/commit/808bd19f [IMP] figure: select figure on insertion https://github.com/odoo/o-spreadsheet/commit/a6675aa4 [IMP] clickable cell: give env as condition argument https://github.com/odoo/o-spreadsheet/commit/b8420475 [IMP] dashboard: clickable cells https://github.com/odoo/o-spreadsheet/commit/7b1c16b0 [REF] components: use dedicated component for dashboard mode https://github.com/odoo/o-spreadsheet/commit/b3f48d65 [REF] grid: move focusable element to grid https://github.com/odoo/o-spreadsheet/commit/cd083d9b [REF] grid: resize sheet view independently from grid https://github.com/odoo/o-spreadsheet/commit/ed4948b8 [REF] tests: globally mock grid clientX and clientY https://github.com/odoo/o-spreadsheet/commit/011aa0d4 [IMP] spreadsheet: export positionToZone helper https://github.com/odoo/o-spreadsheet/commit/b24e6483 [IMP] formula: ignore space when caching compiled formula https://github.com/odoo/o-spreadsheet/commit/65a67b51 [IMP] package.json: upgrade Typescript https://github.com/odoo/o-spreadsheet/commit/af4f2f58 [IMP] package.json: upgrade prettier https://github.com/odoo/o-spreadsheet/commit/4c15328d [IMP] functions: improve performance of lookup functions https://github.com/odoo/o-spreadsheet/commit/baafa638 [IMP] functions: add XLOOKUP function https://github.com/odoo/o-spreadsheet/commit/3d5c662e [IMP] functions: add MID function https://github.com/odoo/o-spreadsheet/commit/26f24529 [IMP] functions: add PROPER function https://github.com/odoo/o-spreadsheet/commit/07b4201f [IMP] functions: add CLEAN function https://github.com/odoo/o-spreadsheet/commit/adee58ff [IMP] functions: add IFNA function https://github.com/odoo/o-spreadsheet/commit/089f3b8d [IMP] functions: add ISBLANK function https://github.com/odoo/o-spreadsheet/commit/b638a3d5 [IMP] grid: use correct event attribute Part-of: odoo/odoo#102358 --- .../static/src/o_spreadsheet/o_spreadsheet.js | 2332 ++++++++++------- .../src/o_spreadsheet/o_spreadsheet.xml | 326 ++- 2 files changed, 1575 insertions(+), 1083 deletions(-) diff --git a/addons/spreadsheet/static/src/o_spreadsheet/o_spreadsheet.js b/addons/spreadsheet/static/src/o_spreadsheet/o_spreadsheet.js index 20c8d91d420..c9e1e32853a 100644 --- a/addons/spreadsheet/static/src/o_spreadsheet/o_spreadsheet.js +++ b/addons/spreadsheet/static/src/o_spreadsheet/o_spreadsheet.js @@ -2629,19 +2629,19 @@ let x = 0; let y = 0; switch (direction) { - case 0 /* UP */: + case 0 /* DIRECTION.UP */: x = 0; y = -rule.current; break; - case 1 /* DOWN */: + case 1 /* DIRECTION.DOWN */: x = 0; y = rule.current; break; - case 2 /* LEFT */: + case 2 /* DIRECTION.LEFT */: x = -rule.current; y = 0; break; - case 3 /* RIGHT */: + case 3 /* DIRECTION.RIGHT */: x = rule.current; y = 0; break; @@ -2789,7 +2789,7 @@ results = [results]; } results = [...new Set(results)]; - this.reasons = results.filter((result) => result !== 0 /* Success */); + this.reasons = results.filter((result) => result !== 0 /* CommandResult.Success */); } /** * Static helper which returns a successful DispatchResult @@ -2875,15 +2875,15 @@ CommandResult[CommandResult["MergeOverlap"] = 62] = "MergeOverlap"; CommandResult[CommandResult["TooManyHiddenElements"] = 63] = "TooManyHiddenElements"; CommandResult[CommandResult["Readonly"] = 64] = "Readonly"; - CommandResult[CommandResult["InvalidOffset"] = 65] = "InvalidOffset"; - CommandResult[CommandResult["InvalidViewportSize"] = 66] = "InvalidViewportSize"; - CommandResult[CommandResult["InvalidScrollingDirection"] = 67] = "InvalidScrollingDirection"; - CommandResult[CommandResult["FigureDoesNotExist"] = 68] = "FigureDoesNotExist"; - CommandResult[CommandResult["InvalidConditionalFormatId"] = 69] = "InvalidConditionalFormatId"; - CommandResult[CommandResult["InvalidCellPopover"] = 70] = "InvalidCellPopover"; - CommandResult[CommandResult["EmptyTarget"] = 71] = "EmptyTarget"; - CommandResult[CommandResult["InvalidFreezeQuantity"] = 72] = "InvalidFreezeQuantity"; - CommandResult[CommandResult["FrozenPaneOverlap"] = 73] = "FrozenPaneOverlap"; + CommandResult[CommandResult["InvalidViewportSize"] = 65] = "InvalidViewportSize"; + CommandResult[CommandResult["InvalidScrollingDirection"] = 66] = "InvalidScrollingDirection"; + CommandResult[CommandResult["FigureDoesNotExist"] = 67] = "FigureDoesNotExist"; + CommandResult[CommandResult["InvalidConditionalFormatId"] = 68] = "InvalidConditionalFormatId"; + CommandResult[CommandResult["InvalidCellPopover"] = 69] = "InvalidCellPopover"; + CommandResult[CommandResult["EmptyTarget"] = 70] = "EmptyTarget"; + CommandResult[CommandResult["InvalidFreezeQuantity"] = 71] = "InvalidFreezeQuantity"; + CommandResult[CommandResult["FrozenPaneOverlap"] = 72] = "FrozenPaneOverlap"; + CommandResult[CommandResult["ValuesNotChanged"] = 73] = "ValuesNotChanged"; })(exports.CommandResult || (exports.CommandResult = {})); var DIRECTION; @@ -3068,6 +3068,41 @@ sheet.setAttribute("component", id); document.head.appendChild(sheet); } + function getTextDecoration({ strikethrough, underline, }) { + if (!strikethrough && !underline) { + return "none"; + } + return `${strikethrough ? "line-through" : ""} ${underline ? "underline" : ""}`; + } + /** + * Convert the cell text style to CSS properties. + */ + function cellTextStyleToCss(style) { + const attributes = {}; + if (!style) + return attributes; + if (style.bold) { + attributes["font-weight"] = "bold"; + } + if (style.italic) { + attributes["font-style"] = "italic"; + } + if (style.strikethrough || style.underline) { + let decoration = style.strikethrough ? "line-through" : ""; + decoration = style.underline ? decoration + " underline" : decoration; + attributes["text-decoration"] = decoration; + } + if (style.textColor) { + attributes["color"] = style.textColor; + } + return attributes; + } + function cssPropertiesToCss(attributes) { + const str = Object.entries(attributes) + .map(([attName, attValue]) => `${attName}: ${attValue};`) + .join("\n"); + return "\n" + str + "\n"; + } const ERROR_TOOLTIP_HEIGHT = 40; const ERROR_TOOLTIP_WIDTH = 180; @@ -3129,41 +3164,6 @@ function isChildEvent(parent, ev) { return !!ev.target && parent.contains(ev.target); } - function getTextDecoration({ strikethrough, underline, }) { - if (!strikethrough && !underline) { - return "none"; - } - return `${strikethrough ? "line-through" : ""} ${underline ? "underline" : ""}`; - } - /** - * Convert the cell text style to CSS properties. - */ - function cellTextStyleToCss(style) { - const attributes = {}; - if (!style) - return attributes; - if (style.bold) { - attributes["font-weight"] = "bold"; - } - if (style.italic) { - attributes["font-style"] = "italic"; - } - if (style.strikethrough || style.underline) { - let decoration = style.strikethrough ? "line-through" : ""; - decoration = style.underline ? decoration + " underline" : decoration; - attributes["text-decoration"] = decoration; - } - if (style.textColor) { - attributes["color"] = style.textColor; - } - return attributes; - } - function cssPropertiesToCss(attributes) { - const str = Object.entries(attributes) - .map(([attName, attValue]) => `${attName}: ${attValue};`) - .join("\n"); - return "\n" + str + "\n"; - } function gridOverlayPosition() { const spreadsheetElement = document.querySelector(".o-grid-overlay"); if (spreadsheetElement) { @@ -4070,7 +4070,7 @@ }); } } - if (result.isCancelledBecause(60 /* InvalidSortZone */)) { + if (result.isCancelledBecause(60 /* CommandResult.InvalidSortZone */)) { const { col, row } = anchor; env.model.selection.selectZone({ cell: { col, row }, zone }); env.raiseError(_lt("Cannot sort. To sort, select only cells or only merges that have the same size.")); @@ -4080,7 +4080,7 @@ function interactiveCut(env) { const result = env.model.dispatch("CUT"); if (!result.isSuccessful) { - if (result.isCancelledBecause(17 /* WrongCutSelection */)) { + if (result.isCancelledBecause(17 /* CommandResult.WrongCutSelection */)) { env.raiseError(_lt("This operation is not allowed with multiple selections.")); } } @@ -4094,16 +4094,16 @@ }; function handlePasteResult(env, result) { if (!result.isSuccessful) { - if (result.reasons.includes(18 /* WrongPasteSelection */)) { + if (result.reasons.includes(18 /* CommandResult.WrongPasteSelection */)) { env.raiseError(PasteInteractiveContent.wrongPasteSelection); } - else if (result.reasons.includes(2 /* WillRemoveExistingMerge */)) { + else if (result.reasons.includes(2 /* CommandResult.WillRemoveExistingMerge */)) { env.raiseError(PasteInteractiveContent.willRemoveExistingMerge); } - else if (result.reasons.includes(20 /* WrongFigurePasteOption */)) { + else if (result.reasons.includes(20 /* CommandResult.WrongFigurePasteOption */)) { env.raiseError(PasteInteractiveContent.wrongFigurePasteOption); } - else if (result.reasons.includes(73 /* FrozenPaneOverlap */)) { + else if (result.reasons.includes(72 /* CommandResult.FrozenPaneOverlap */)) { env.raiseError(PasteInteractiveContent.frozenPaneOverlap); } } @@ -4679,7 +4679,8 @@ legendPosition: newLegendPos, }, }); - env.openSidePanel("ChartPanel", { figureId: id }); + env.model.dispatch("SELECT_FIGURE", { id }); + env.openSidePanel("ChartPanel"); }; //------------------------------------------------------------------------------ // Style/Format @@ -4942,8 +4943,6 @@ action: OPEN_CF_SIDEPANEL_ACTION, }); - const dashboardMenuRegistry = new MenuItemRegistry(); - const rowMenuRegistry = new MenuItemRegistry(); rowMenuRegistry .add("cut", { @@ -5044,10 +5043,10 @@ } const result = env.model.dispatch("RENAME_SHEET", { sheetId, name }); if (!result.isSuccessful) { - if (result.reasons.includes(10 /* DuplicatedSheetName */)) { + if (result.reasons.includes(10 /* CommandResult.DuplicatedSheetName */)) { interactiveRenameSheet(env, sheetId, _lt("A sheet with the name %s already exists. Please select another name.", name)); } - if (result.reasons.includes(11 /* ForbiddenCharactersInSheetName */)) { + if (result.reasons.includes(11 /* CommandResult.ForbiddenCharactersInSheetName */)) { interactiveRenameSheet(env, sheetId, _lt("Some used characters are not allowed in a sheet name (Forbidden characters are %s).", FORBIDDEN_SHEET_CHARS.join(" "))); } } @@ -5122,24 +5121,24 @@ const CfTerms = { Errors: { - [23 /* InvalidRange */]: _lt("The range is invalid"), - [48 /* FirstArgMissing */]: _lt("The argument is missing. Please provide a value"), - [49 /* SecondArgMissing */]: _lt("The second argument is missing. Please provide a value"), - [50 /* MinNaN */]: _lt("The minpoint must be a number"), - [51 /* MidNaN */]: _lt("The midpoint must be a number"), - [52 /* MaxNaN */]: _lt("The maxpoint must be a number"), - [53 /* ValueUpperInflectionNaN */]: _lt("The first value must be a number"), - [54 /* ValueLowerInflectionNaN */]: _lt("The second value must be a number"), - [44 /* MinBiggerThanMax */]: _lt("Minimum must be smaller then Maximum"), - [47 /* MinBiggerThanMid */]: _lt("Minimum must be smaller then Midpoint"), - [46 /* MidBiggerThanMax */]: _lt("Midpoint must be smaller then Maximum"), - [45 /* LowerBiggerThanUpper */]: _lt("Lower inflection point must be smaller than upper inflection point"), - [55 /* MinInvalidFormula */]: _lt("Invalid Minpoint formula"), - [57 /* MaxInvalidFormula */]: _lt("Invalid Maxpoint formula"), - [56 /* MidInvalidFormula */]: _lt("Invalid Midpoint formula"), - [58 /* ValueUpperInvalidFormula */]: _lt("Invalid upper inflection point formula"), - [59 /* ValueLowerInvalidFormula */]: _lt("Invalid lower inflection point formula"), - [22 /* EmptyRange */]: _lt("A range needs to be defined"), + [23 /* CommandResult.InvalidRange */]: _lt("The range is invalid"), + [48 /* CommandResult.FirstArgMissing */]: _lt("The argument is missing. Please provide a value"), + [49 /* CommandResult.SecondArgMissing */]: _lt("The second argument is missing. Please provide a value"), + [50 /* CommandResult.MinNaN */]: _lt("The minpoint must be a number"), + [51 /* CommandResult.MidNaN */]: _lt("The midpoint must be a number"), + [52 /* CommandResult.MaxNaN */]: _lt("The maxpoint must be a number"), + [53 /* CommandResult.ValueUpperInflectionNaN */]: _lt("The first value must be a number"), + [54 /* CommandResult.ValueLowerInflectionNaN */]: _lt("The second value must be a number"), + [44 /* CommandResult.MinBiggerThanMax */]: _lt("Minimum must be smaller then Maximum"), + [47 /* CommandResult.MinBiggerThanMid */]: _lt("Minimum must be smaller then Midpoint"), + [46 /* CommandResult.MidBiggerThanMax */]: _lt("Midpoint must be smaller then Maximum"), + [45 /* CommandResult.LowerBiggerThanUpper */]: _lt("Lower inflection point must be smaller than upper inflection point"), + [55 /* CommandResult.MinInvalidFormula */]: _lt("Invalid Minpoint formula"), + [57 /* CommandResult.MaxInvalidFormula */]: _lt("Invalid Maxpoint formula"), + [56 /* CommandResult.MidInvalidFormula */]: _lt("Invalid Midpoint formula"), + [58 /* CommandResult.ValueUpperInvalidFormula */]: _lt("Invalid upper inflection point formula"), + [59 /* CommandResult.ValueLowerInvalidFormula */]: _lt("Invalid lower inflection point formula"), + [22 /* CommandResult.EmptyRange */]: _lt("A range needs to be defined"), Unexpected: _lt("The rule is invalid for an unknown reason"), }, ColorScale: _lt("Color scale"), @@ -5166,20 +5165,20 @@ Errors: { Unexpected: _lt("The chart definition is invalid for an unknown reason"), // BASIC CHART ERRORS (LINE | BAR | PIE) - [29 /* InvalidDataSet */]: _lt("The dataset is invalid"), - [30 /* InvalidLabelRange */]: _lt("Labels are invalid"), + [29 /* CommandResult.InvalidDataSet */]: _lt("The dataset is invalid"), + [30 /* CommandResult.InvalidLabelRange */]: _lt("Labels are invalid"), // SCORECARD CHART ERRORS - [31 /* InvalidScorecardKeyValue */]: _lt("The key value is invalid"), - [32 /* InvalidScorecardBaseline */]: _lt("The baseline value is invalid"), + [31 /* CommandResult.InvalidScorecardKeyValue */]: _lt("The key value is invalid"), + [32 /* CommandResult.InvalidScorecardBaseline */]: _lt("The baseline value is invalid"), // GAUGE CHART ERRORS - [33 /* InvalidGaugeDataRange */]: _lt("The data range is invalid"), - [34 /* EmptyGaugeRangeMin */]: _lt("A minimum range limit value is needed"), - [35 /* GaugeRangeMinNaN */]: _lt("The minimum range limit value must be a number"), - [36 /* EmptyGaugeRangeMax */]: _lt("A maximum range limit value is needed"), - [37 /* GaugeRangeMaxNaN */]: _lt("The maximum range limit value must be a number"), - [38 /* GaugeRangeMinBiggerThanRangeMax */]: _lt("Minimum range limit must be smaller than maximum range limit"), - [39 /* GaugeLowerInflectionPointNaN */]: _lt("The lower inflection point value must be a number"), - [40 /* GaugeUpperInflectionPointNaN */]: _lt("The upper inflection point value must be a number"), + [33 /* CommandResult.InvalidGaugeDataRange */]: _lt("The data range is invalid"), + [34 /* CommandResult.EmptyGaugeRangeMin */]: _lt("A minimum range limit value is needed"), + [35 /* CommandResult.GaugeRangeMinNaN */]: _lt("The minimum range limit value must be a number"), + [36 /* CommandResult.EmptyGaugeRangeMax */]: _lt("A maximum range limit value is needed"), + [37 /* CommandResult.GaugeRangeMaxNaN */]: _lt("The maximum range limit value must be a number"), + [38 /* CommandResult.GaugeRangeMinBiggerThanRangeMax */]: _lt("Minimum range limit must be smaller than maximum range limit"), + [39 /* CommandResult.GaugeLowerInflectionPointNaN */]: _lt("The lower inflection point value must be a number"), + [40 /* CommandResult.GaugeUpperInflectionPointNaN */]: _lt("The upper inflection point value must be a number"), }, }; const NumberFormatTerms = { @@ -5203,7 +5202,7 @@ const sheetId = env.model.getters.getActiveSheetId(); const cmd = dimension === "COL" ? "FREEZE_COLUMNS" : "FREEZE_ROWS"; const result = env.model.dispatch(cmd, { sheetId, quantity: base }); - if (result.isCancelledBecause(62 /* MergeOverlap */)) { + if (result.isCancelledBecause(62 /* CommandResult.MergeOverlap */)) { env.raiseError(MergeErrorMessage); } } @@ -5824,11 +5823,11 @@ } get isDatasetInvalid() { var _a; - return !!((_a = this.state.datasetDispatchResult) === null || _a === void 0 ? void 0 : _a.isCancelledBecause(29 /* InvalidDataSet */)); + return !!((_a = this.state.datasetDispatchResult) === null || _a === void 0 ? void 0 : _a.isCancelledBecause(29 /* CommandResult.InvalidDataSet */)); } get isLabelInvalid() { var _a; - return !!((_a = this.state.labelsDispatchResult) === null || _a === void 0 ? void 0 : _a.isCancelledBecause(30 /* InvalidLabelRange */)); + return !!((_a = this.state.labelsDispatchResult) === null || _a === void 0 ? void 0 : _a.isCancelledBecause(30 /* CommandResult.InvalidLabelRange */)); } onUpdateDataSetsHaveTitle(ev) { this.props.updateChart({ @@ -6176,23 +6175,23 @@ if (definition.dataSets) { const invalidRanges = definition.dataSets.find((range) => !rangeReference.test(range)) !== undefined; if (invalidRanges) { - return 29 /* InvalidDataSet */; + return 29 /* CommandResult.InvalidDataSet */; } const zones = definition.dataSets.map(toUnboundedZone); if (zones.some((zone) => zone.top !== zone.bottom && isFullRow(zone))) { - return 29 /* InvalidDataSet */; + return 29 /* CommandResult.InvalidDataSet */; } } - return 0 /* Success */; + return 0 /* CommandResult.Success */; } function checkLabelRange(definition) { if (definition.labelRange) { const invalidLabels = !rangeReference.test(definition.labelRange || ""); if (invalidLabels) { - return 30 /* InvalidLabelRange */; + return 30 /* CommandResult.InvalidLabelRange */; } } - return 0 /* Success */; + return 0 /* CommandResult.Success */; } // --------------------------------------------------------------------------- // Scorecard @@ -6647,20 +6646,20 @@ }); function isDataRangeValid(definition) { return definition.dataRange && !rangeReference.test(definition.dataRange) - ? 33 /* InvalidGaugeDataRange */ - : 0 /* Success */; + ? 33 /* CommandResult.InvalidGaugeDataRange */ + : 0 /* CommandResult.Success */; } function checkRangeLimits(check, batchValidations) { return batchValidations((definition) => { if (definition.sectionRule) { return check(definition.sectionRule.rangeMin, "rangeMin"); } - return 0 /* Success */; + return 0 /* CommandResult.Success */; }, (definition) => { if (definition.sectionRule) { return check(definition.sectionRule.rangeMax, "rangeMax"); } - return 0 /* Success */; + return 0 /* CommandResult.Success */; }); } function checkInflectionPointsValue(check, batchValidations) { @@ -6668,47 +6667,47 @@ if (definition.sectionRule) { return check(definition.sectionRule.lowerInflectionPoint.value, "lowerInflectionPointValue"); } - return 0 /* Success */; + return 0 /* CommandResult.Success */; }, (definition) => { if (definition.sectionRule) { return check(definition.sectionRule.upperInflectionPoint.value, "upperInflectionPointValue"); } - return 0 /* Success */; + return 0 /* CommandResult.Success */; }); } function checkRangeMinBiggerThanRangeMax(definition) { if (definition.sectionRule) { if (Number(definition.sectionRule.rangeMin) >= Number(definition.sectionRule.rangeMax)) { - return 38 /* GaugeRangeMinBiggerThanRangeMax */; + return 38 /* CommandResult.GaugeRangeMinBiggerThanRangeMax */; } } - return 0 /* Success */; + return 0 /* CommandResult.Success */; } function checkEmpty(value, valueName) { if (value === "") { switch (valueName) { case "rangeMin": - return 34 /* EmptyGaugeRangeMin */; + return 34 /* CommandResult.EmptyGaugeRangeMin */; case "rangeMax": - return 36 /* EmptyGaugeRangeMax */; + return 36 /* CommandResult.EmptyGaugeRangeMax */; } } - return 0 /* Success */; + return 0 /* CommandResult.Success */; } function checkNaN(value, valueName) { if (isNaN(value)) { switch (valueName) { case "rangeMin": - return 35 /* GaugeRangeMinNaN */; + return 35 /* CommandResult.GaugeRangeMinNaN */; case "rangeMax": - return 37 /* GaugeRangeMaxNaN */; + return 37 /* CommandResult.GaugeRangeMaxNaN */; case "lowerInflectionPointValue": - return 39 /* GaugeLowerInflectionPointNaN */; + return 39 /* CommandResult.GaugeLowerInflectionPointNaN */; case "upperInflectionPointValue": - return 40 /* GaugeUpperInflectionPointNaN */; + return 40 /* CommandResult.GaugeUpperInflectionPointNaN */; } } - return 0 /* Success */; + return 0 /* CommandResult.Success */; } class GaugeChart extends AbstractChart { constructor(definition, sheetId, getters) { @@ -7445,13 +7444,13 @@ }); function checkKeyValue(definition) { return definition.keyValue && !rangeReference.test(definition.keyValue) - ? 31 /* InvalidScorecardKeyValue */ - : 0 /* Success */; + ? 31 /* CommandResult.InvalidScorecardKeyValue */ + : 0 /* CommandResult.Success */; } function checkBaseline(definition) { return definition.baseline && !rangeReference.test(definition.baseline) - ? 32 /* InvalidScorecardBaseline */ - : 0 /* Success */; + ? 32 /* CommandResult.InvalidScorecardBaseline */ + : 0 /* CommandResult.Success */; } class ScorecardChart$1 extends AbstractChart { constructor(definition, sheetId, getters) { @@ -7849,7 +7848,7 @@ } get isDataRangeInvalid() { var _a; - return !!((_a = this.state.dataRangeDispatchResult) === null || _a === void 0 ? void 0 : _a.isCancelledBecause(33 /* InvalidGaugeDataRange */)); + return !!((_a = this.state.dataRangeDispatchResult) === null || _a === void 0 ? void 0 : _a.isCancelledBecause(33 /* CommandResult.InvalidGaugeDataRange */)); } onDataRangeChanged(ranges) { this.dataRange = ranges[0]; @@ -7930,28 +7929,28 @@ } isRangeMinInvalid() { var _a, _b, _c; - return !!(((_a = this.state.sectionRuleDispatchResult) === null || _a === void 0 ? void 0 : _a.isCancelledBecause(34 /* EmptyGaugeRangeMin */)) || - ((_b = this.state.sectionRuleDispatchResult) === null || _b === void 0 ? void 0 : _b.isCancelledBecause(35 /* GaugeRangeMinNaN */)) || - ((_c = this.state.sectionRuleDispatchResult) === null || _c === void 0 ? void 0 : _c.isCancelledBecause(38 /* GaugeRangeMinBiggerThanRangeMax */))); + return !!(((_a = this.state.sectionRuleDispatchResult) === null || _a === void 0 ? void 0 : _a.isCancelledBecause(34 /* CommandResult.EmptyGaugeRangeMin */)) || + ((_b = this.state.sectionRuleDispatchResult) === null || _b === void 0 ? void 0 : _b.isCancelledBecause(35 /* CommandResult.GaugeRangeMinNaN */)) || + ((_c = this.state.sectionRuleDispatchResult) === null || _c === void 0 ? void 0 : _c.isCancelledBecause(38 /* CommandResult.GaugeRangeMinBiggerThanRangeMax */))); } isRangeMaxInvalid() { var _a, _b, _c; - return !!(((_a = this.state.sectionRuleDispatchResult) === null || _a === void 0 ? void 0 : _a.isCancelledBecause(36 /* EmptyGaugeRangeMax */)) || - ((_b = this.state.sectionRuleDispatchResult) === null || _b === void 0 ? void 0 : _b.isCancelledBecause(37 /* GaugeRangeMaxNaN */)) || - ((_c = this.state.sectionRuleDispatchResult) === null || _c === void 0 ? void 0 : _c.isCancelledBecause(38 /* GaugeRangeMinBiggerThanRangeMax */))); + return !!(((_a = this.state.sectionRuleDispatchResult) === null || _a === void 0 ? void 0 : _a.isCancelledBecause(36 /* CommandResult.EmptyGaugeRangeMax */)) || + ((_b = this.state.sectionRuleDispatchResult) === null || _b === void 0 ? void 0 : _b.isCancelledBecause(37 /* CommandResult.GaugeRangeMaxNaN */)) || + ((_c = this.state.sectionRuleDispatchResult) === null || _c === void 0 ? void 0 : _c.isCancelledBecause(38 /* CommandResult.GaugeRangeMinBiggerThanRangeMax */))); } // --------------------------------------------------------------------------- // COLOR_SECTION_TEMPLATE // --------------------------------------------------------------------------- get isLowerInflectionPointInvalid() { var _a, _b; - return !!(((_a = this.state.sectionRuleDispatchResult) === null || _a === void 0 ? void 0 : _a.isCancelledBecause(39 /* GaugeLowerInflectionPointNaN */)) || - ((_b = this.state.sectionRuleDispatchResult) === null || _b === void 0 ? void 0 : _b.isCancelledBecause(41 /* GaugeLowerBiggerThanUpper */))); + return !!(((_a = this.state.sectionRuleDispatchResult) === null || _a === void 0 ? void 0 : _a.isCancelledBecause(39 /* CommandResult.GaugeLowerInflectionPointNaN */)) || + ((_b = this.state.sectionRuleDispatchResult) === null || _b === void 0 ? void 0 : _b.isCancelledBecause(41 /* CommandResult.GaugeLowerBiggerThanUpper */))); } get isUpperInflectionPointInvalid() { var _a, _b; - return !!(((_a = this.state.sectionRuleDispatchResult) === null || _a === void 0 ? void 0 : _a.isCancelledBecause(40 /* GaugeUpperInflectionPointNaN */)) || - ((_b = this.state.sectionRuleDispatchResult) === null || _b === void 0 ? void 0 : _b.isCancelledBecause(41 /* GaugeLowerBiggerThanUpper */))); + return !!(((_a = this.state.sectionRuleDispatchResult) === null || _a === void 0 ? void 0 : _a.isCancelledBecause(40 /* CommandResult.GaugeUpperInflectionPointNaN */)) || + ((_b = this.state.sectionRuleDispatchResult) === null || _b === void 0 ? void 0 : _b.isCancelledBecause(41 /* CommandResult.GaugeLowerBiggerThanUpper */))); } updateInflectionPointValue(attr, ev) { const sectionRule = deepCopy(this.props.definition.sectionRule); @@ -8049,11 +8048,11 @@ } get isKeyValueInvalid() { var _a; - return !!((_a = this.state.keyValueDispatchResult) === null || _a === void 0 ? void 0 : _a.isCancelledBecause(31 /* InvalidScorecardKeyValue */)); + return !!((_a = this.state.keyValueDispatchResult) === null || _a === void 0 ? void 0 : _a.isCancelledBecause(31 /* CommandResult.InvalidScorecardKeyValue */)); } get isBaselineInvalid() { var _a; - return !!((_a = this.state.keyValueDispatchResult) === null || _a === void 0 ? void 0 : _a.isCancelledBecause(32 /* InvalidScorecardBaseline */)); + return !!((_a = this.state.keyValueDispatchResult) === null || _a === void 0 ? void 0 : _a.isCancelledBecause(32 /* CommandResult.InvalidScorecardBaseline */)); } onKeyValueRangeChanged(ranges) { this.keyValue = ranges[0]; @@ -8170,14 +8169,23 @@ `; class ChartPanel extends owl.Component { get figureId() { - return this.props.figureId; + return this.state.figureId; } setup() { + const selectedFigureId = this.env.model.getters.getSelectedFigureId(); + if (!selectedFigureId) { + throw new Error(_lt("Cannot open the chart side panel while no chart are selected")); + } this.state = owl.useState({ panel: "configuration", + figureId: selectedFigureId, }); - owl.onWillUpdateProps((nextProps) => { - if (!this.env.model.getters.isChartDefined(nextProps.figureId)) { + owl.onWillUpdateProps(() => { + const selectedFigureId = this.env.model.getters.getSelectedFigureId(); + if (selectedFigureId && selectedFigureId !== this.state.figureId) { + this.state.figureId = selectedFigureId; + } + if (!this.env.model.getters.isChartDefined(this.figureId)) { this.props.onCloseSidePanel(); return; } @@ -8657,7 +8665,7 @@ return this.env.model.getters.getConditionalFormats(this.env.model.getters.getActiveSheetId()); } get isRangeValid() { - return this.state.errors.includes(22 /* EmptyRange */); + return this.state.errors.includes(22 /* CommandResult.EmptyRange */); } errorMessage(error) { return CfTerms.Errors[error] || CfTerms.Errors.Unexpected; @@ -8719,7 +8727,7 @@ if (this.state.currentCF) { const invalidRanges = this.state.currentCF.ranges.some((xc) => !xc.match(rangeReference)); if (invalidRanges) { - this.state.errors = [23 /* InvalidRange */]; + this.state.errors = [23 /* CommandResult.InvalidRange */]; return; } const sheetId = this.env.model.getters.getActiveSheetId(); @@ -8875,11 +8883,11 @@ ****************************************************************************/ get isValue1Invalid() { var _a; - return !!((_a = this.state.errors) === null || _a === void 0 ? void 0 : _a.includes(48 /* FirstArgMissing */)); + return !!((_a = this.state.errors) === null || _a === void 0 ? void 0 : _a.includes(48 /* CommandResult.FirstArgMissing */)); } get isValue2Invalid() { var _a; - return !!((_a = this.state.errors) === null || _a === void 0 ? void 0 : _a.includes(49 /* SecondArgMissing */)); + return !!((_a = this.state.errors) === null || _a === void 0 ? void 0 : _a.includes(49 /* CommandResult.SecondArgMissing */)); } toggleStyle(tool) { const style = this.state.rules.cellIs.style; @@ -8896,17 +8904,17 @@ isValueInvalid(threshold) { switch (threshold) { case "minimum": - return (this.state.errors.includes(55 /* MinInvalidFormula */) || - this.state.errors.includes(47 /* MinBiggerThanMid */) || - this.state.errors.includes(44 /* MinBiggerThanMax */) || - this.state.errors.includes(50 /* MinNaN */)); + return (this.state.errors.includes(55 /* CommandResult.MinInvalidFormula */) || + this.state.errors.includes(47 /* CommandResult.MinBiggerThanMid */) || + this.state.errors.includes(44 /* CommandResult.MinBiggerThanMax */) || + this.state.errors.includes(50 /* CommandResult.MinNaN */)); case "midpoint": - return (this.state.errors.includes(56 /* MidInvalidFormula */) || - this.state.errors.includes(51 /* MidNaN */) || - this.state.errors.includes(46 /* MidBiggerThanMax */)); + return (this.state.errors.includes(56 /* CommandResult.MidInvalidFormula */) || + this.state.errors.includes(51 /* CommandResult.MidNaN */) || + this.state.errors.includes(46 /* CommandResult.MidBiggerThanMax */)); case "maximum": - return (this.state.errors.includes(57 /* MaxInvalidFormula */) || - this.state.errors.includes(52 /* MaxNaN */)); + return (this.state.errors.includes(57 /* CommandResult.MaxInvalidFormula */) || + this.state.errors.includes(52 /* CommandResult.MaxNaN */)); default: return false; } @@ -8955,13 +8963,13 @@ isInflectionPointInvalid(inflectionPoint) { switch (inflectionPoint) { case "lowerInflectionPoint": - return (this.state.errors.includes(54 /* ValueLowerInflectionNaN */) || - this.state.errors.includes(59 /* ValueLowerInvalidFormula */) || - this.state.errors.includes(45 /* LowerBiggerThanUpper */)); + return (this.state.errors.includes(54 /* CommandResult.ValueLowerInflectionNaN */) || + this.state.errors.includes(59 /* CommandResult.ValueLowerInvalidFormula */) || + this.state.errors.includes(45 /* CommandResult.LowerBiggerThanUpper */)); case "upperInflectionPoint": - return (this.state.errors.includes(53 /* ValueUpperInflectionNaN */) || - this.state.errors.includes(58 /* ValueUpperInvalidFormula */) || - this.state.errors.includes(45 /* LowerBiggerThanUpper */)); + return (this.state.errors.includes(53 /* CommandResult.ValueUpperInflectionNaN */) || + this.state.errors.includes(58 /* CommandResult.ValueUpperInvalidFormula */) || + this.state.errors.includes(45 /* CommandResult.LowerBiggerThanUpper */)); default: return true; } @@ -9555,7 +9563,10 @@ registry.add("edit", { name: _lt("Edit"), sequence: 1, - action: () => this.env.openSidePanel("ChartPanel", { figureId: this.props.figure.id }), + action: () => { + this.env.model.dispatch("SELECT_FIGURE", { id: this.props.figure.id }); + this.env.openSidePanel("ChartPanel"); + }, }); registry.add("copy", { name: _lt("Copy"), @@ -9584,7 +9595,7 @@ id: this.props.figure.id, }); if (this.props.sidePanelIsOpen) { - this.env.toggleSidePanel("ChartPanel", { figureId: this.props.figure.id }); + this.env.toggleSidePanel("ChartPanel"); } this.props.onFigureDeleted(); }, @@ -9676,7 +9687,6 @@ if (timeOutId) { return; } - const { maxOffsetX, maxOffsetY } = getters.getMaximumSheetOffset(); const { x: offsetCorrectionX, y: offsetCorrectionY } = getters.getMainViewportCoordinates(); let { top, left, bottom, right } = getters.getActiveMainViewport(); let { offsetScrollbarX: offsetX, offsetScrollbarY: offsetY } = getters.getActiveSheetScrollInfo(); @@ -9706,7 +9716,7 @@ newTarget = left - 1; break; } - offsetX = Math.min(maxOffsetX, getters.getColDimensions(sheetId, newTarget).start - offsetCorrectionX); + offsetX = getters.getColDimensions(sheetId, newTarget).start - offsetCorrectionX; } } const y = currentEv.clientY - position.top; @@ -9732,7 +9742,7 @@ newTarget = top + edgeScrollInfoY.direction; break; } - offsetY = Math.min(maxOffsetY, env.model.getters.getRowDimensions(sheetId, newTarget).start - offsetCorrectionY); + offsetY = env.model.getters.getRowDimensions(sheetId, newTarget).start - offsetCorrectionY; } } cbMouseMove(colIndex, rowIndex, currentEv); @@ -10062,6 +10072,7 @@ } // HELPERS + const SORT_TYPES_ORDER = ["number", "string", "boolean", "undefined"]; function assert(condition, message) { if (!condition()) { throw new Error(message); @@ -10108,6 +10119,20 @@ return ""; } } + /** Normalize string by setting it to lowercase and replacing accent letters with plain letters */ + function normalizeString(str) { + return str + .toLowerCase() + .normalize("NFD") + .replace(/[\u0300-\u036f]/g, ""); + } + /** + * Normalize a value. + * If the cell value is a string, this will set it to lowercase and replacing accent letters with plain letters + */ + function normalizeValue(value) { + return typeof value === "string" ? normalizeString(value) : value; + } const expectBooleanValueError = (value) => _lt("The function [[FUNCTION_NAME]] expects a boolean value, but '%s' is a text, and cannot be coerced to a number.", value); function toBoolean(value) { switch (typeof value) { @@ -10429,62 +10454,81 @@ // ----------------------------------------------------------------------------- // COMMON FUNCTIONS // ----------------------------------------------------------------------------- + function getNormalizedValueFromColumnRange(range, index) { + return normalizeValue(range[0][index]); + } + function getNormalizedValueFromRowRange(range, index) { + return normalizeValue(range[index][0]); + } /** - * Perform a dichotomic search and return the index of the nearest match less than - * or equal to the target. If all values in the range are greater than the target, - * -1 is returned. - * If the range is not in sorted order, an incorrect value might be returned. + * Perform a dichotomic search on an array and return the index of the nearest match. * - * Example: - * - [3, 6, 10], 3 => 0 - * - [3, 6, 10], 6 => 1 - * - [3, 6, 10], 9 => 1 - * - [3, 6, 10], 42 => 2 - * - [3, 6, 10], 2 => -1 - * - [3, undefined, 6, undefined, 10], 9 => 2 - * - [3, 6, undefined, undefined, undefined, 10], 2 => -1 + * The array should be sorted, if not an incorrect value might be returned. In the case where multiple + * element of the array match the target, the method will return the first match if the array is sorted + * in descending order, and the last match if the array is in ascending order. + * + * + * @param data the array in which to search. + * @param target the value to search. + * @param mode "nextGreater/nextSmaller" : return next greater/smaller value if no exact match is found. + * @param sortOrder whether the array is sorted in ascending or descending order. + * @param rangeLength the number of elements to consider in the search array. + * @param getValueInData function returning the element at index i in the search array. */ - function dichotomicPredecessorSearch(range, target) { - if (target === null) { + function dichotomicSearch(data, target, mode, sortOrder, rangeLength, getValueInData) { + if (target === null || target === undefined) { return -1; } const targetType = typeof target; - let valMin = undefined; - let valMinIndex = undefined; + let matchVal = undefined; + let matchValIndex = undefined; let indexLeft = 0; - let indexRight = range.length - 1; - if (typeof range[indexLeft] === targetType && target < range[indexLeft]) { - return -1; - } - if (typeof range[indexRight] === targetType && range[indexRight] <= target) { - return indexRight; - } + let indexRight = rangeLength - 1; let indexMedian; let currentIndex; let currentVal; let currentType; while (indexRight - indexLeft >= 0) { - indexMedian = Math.ceil((indexLeft + indexRight) / 2); + indexMedian = Math.floor((indexLeft + indexRight) / 2); currentIndex = indexMedian; - currentVal = range[currentIndex]; + currentVal = getValueInData(data, currentIndex); currentType = typeof currentVal; // 1 - linear search to find value with the same type while (indexLeft <= currentIndex && targetType !== currentType) { currentIndex--; - currentVal = range[currentIndex]; + currentVal = getValueInData(data, currentIndex); currentType = typeof currentVal; } + if (currentType !== targetType || currentVal === undefined) { + indexLeft = indexMedian + 1; + continue; + } // 2 - check if value match - if (currentType === targetType && currentVal <= target) { - if (valMin === undefined || - valMin < currentVal || - (valMin === currentVal && valMinIndex < currentIndex)) { - valMin = currentVal; - valMinIndex = currentIndex; + if (mode === "strict" && currentVal === target) { + matchVal = currentVal; + matchValIndex = currentIndex; + } + else if (mode === "nextSmaller" && currentVal <= target) { + if (matchVal === undefined || + matchVal < currentVal || + (matchVal === currentVal && sortOrder === "asc" && matchValIndex < currentIndex) || + (matchVal === currentVal && sortOrder === "desc" && matchValIndex > currentIndex)) { + matchVal = currentVal; + matchValIndex = currentIndex; } } - // 3 - give new indexs for the Binary search - if (currentType === targetType && currentVal > target) { + else if (mode === "nextGreater" && currentVal >= target) { + if (matchVal === undefined || + matchVal > currentVal || + (matchVal === currentVal && sortOrder === "asc" && matchValIndex < currentIndex) || + (matchVal === currentVal && sortOrder === "desc" && matchValIndex > currentIndex)) { + matchVal = currentVal; + matchValIndex = currentIndex; + } + } + // 3 - give new indexes for the Binary search + if ((sortOrder === "asc" && currentVal > target) || + (sortOrder === "desc" && currentVal <= target)) { indexRight = currentIndex - 1; } else { @@ -10492,69 +10536,71 @@ } } // note that valMinIndex could be 0 - return valMinIndex !== undefined ? valMinIndex : -1; + return matchValIndex !== undefined ? matchValIndex : -1; } /** - * Perform a dichotomic search and return the index of the nearest match more than - * or equal to the target. If all values in the range are smaller than the target, - * -1 is returned. - * If the range is not in sorted order, an incorrect value might be returned. + * Perform a linear search and return the index of the match. + * -1 is returned if no value is found. * * Example: - * - [10, 6, 3], 3 => 2 - * - [10, 6, 3], 6 => 1 - * - [10, 6, 3], 9 => 0 - * - [10, 6, 3], 42 => -1 - * - [10, 6, 3], 2 => 2 - * - [10, undefined, 6, undefined, 3], 9 => 0 - * - [10, 6, undefined, undefined, undefined, 3], 2 => 5 + * - [3, 6, 10], 3 => 0 + * - [3, 6, 10], 6 => 1 + * - [3, 6, 10], 9 => -1 + * - [3, 6, 10], 2 => -1 + * + * @param data the array to search in. + * @param target the value to search in the array. + * @param mode if "strict" return exact match index. "nextGreater" returns the next greater + * element from the target and "nextSmaller" the next smaller + * @param numberOfValues the number of elements to consider in the search array. + * @param getValueInData function returning the element at index i in the search array. + * @param reverseSearch if true, search in the array starting from the end. + */ - function dichotomicSuccessorSearch(range, target) { - const targetType = typeof target; - let valMax; - let valMaxIndex = undefined; - let indexLeft = 0; - let indexRight = range.length - 1; - if (typeof range[indexLeft] === targetType && target > range[indexLeft]) { + function linearSearch(data, target, mode, numberOfValues, getValueInData, reverseSearch = false) { + if (target === null || target === undefined) return -1; - } - if (typeof range[indexRight] === targetType && range[indexRight] > target) { - return indexRight; - } - let indexMedian; - let currentIndex; - let currentVal; - let currentType; - while (indexRight - indexLeft >= 0) { - indexMedian = Math.ceil((indexLeft + indexRight) / 2); - currentIndex = indexMedian; - currentVal = range[currentIndex]; - currentType = typeof currentVal; - // 1 - linear search to find value with the same type - while (indexLeft <= currentIndex && targetType !== currentType) { - currentIndex--; - currentVal = range[currentIndex]; - currentType = typeof currentVal; + const getValue = reverseSearch + ? (data, i) => getValueInData(data, numberOfValues - i - 1) + : getValueInData; + let closestMatch = undefined; + let closestMatchIndex = -1; + for (let i = 0; i < numberOfValues; i++) { + const value = getValue(data, i); + if (value === target) { + return reverseSearch ? numberOfValues - i - 1 : i; } - // 2 - check if value match - if (currentType === targetType && currentVal >= target) { - if (valMax === undefined || - valMax > currentVal || - (valMax === currentVal && valMaxIndex > currentIndex)) { - valMax = currentVal; - valMaxIndex = currentIndex; + if (mode === "nextSmaller") { + if ((!closestMatch && compareCellValues(target, value) >= 0) || + (compareCellValues(target, value) >= 0 && compareCellValues(value, closestMatch) > 0)) { + closestMatch = value; + closestMatchIndex = i; } } - // 3 - give new indexs for the Binary search - if (currentType === targetType && currentVal <= target) { - indexRight = currentIndex - 1; - } - else { - indexLeft = indexMedian + 1; + else if (mode === "nextGreater") { + if ((!closestMatch && compareCellValues(target, value) <= 0) || + (compareCellValues(target, value) <= 0 && compareCellValues(value, closestMatch) < 0)) { + closestMatch = value; + closestMatchIndex = i; + } } } - // note that valMaxIndex could be 0 - return valMaxIndex !== undefined ? valMaxIndex : -1; + return reverseSearch ? numberOfValues - closestMatchIndex - 1 : closestMatchIndex; + } + function compareCellValues(left, right) { + let typeOrder = SORT_TYPES_ORDER.indexOf(typeof left) - SORT_TYPES_ORDER.indexOf(typeof right); + if (typeOrder === 0) { + if (typeof left === "string" && typeof right === "string") { + typeOrder = left.localeCompare(right); + } + else if (typeof left === "number" && typeof right === "number") { + typeOrder = left - right; + } + else if (typeof left === "boolean" && typeof right === "boolean") { + typeOrder = Number(left) - Number(right); + } + } + return typeOrder; } // ----------------------------------------------------------------------------- @@ -11767,7 +11813,7 @@ let count = 0; visitAny(data, (d) => { if (typeof d === "number") { - index = dichotomicPredecessorSearch(sortedArray, d); + index = dichotomicSearch(sortedArray, d, "nextSmaller", "asc", sortedArray.length, (array, i) => array[i]); sortedArray.splice(index + 1, 0, d); count++; } @@ -12084,7 +12130,7 @@ let count = 0; visitAny([data], (d) => { if (typeof d === "number") { - index = dichotomicPredecessorSearch(largests, d); + index = dichotomicSearch(largests, d, "nextSmaller", "asc", largests.length, (array, i) => array[i]); largests.splice(index + 1, 0, d); count++; if (count > _n) { @@ -12394,7 +12440,7 @@ let count = 0; visitAny([data], (d) => { if (typeof d === "number") { - index = dichotomicPredecessorSearch(largests, d); + index = dichotomicSearch(largests, d, "nextSmaller", "asc", largests.length, (array, i) => array[i]); largests.splice(index + 1, 0, d); count++; if (count > _n) { @@ -14373,6 +14419,24 @@ isExported: true, }; // ----------------------------------------------------------------------------- + // ISBLANK + // ----------------------------------------------------------------------------- + const ISBLANK = { + description: _lt("Whether the referenced cell is empty"), + args: args(`value (any, lazy) ${_lt("Reference to the cell that will be checked for emptiness.")}`), + returns: ["BOOLEAN"], + compute: function (value) { + try { + const val = value(); + return val === null; + } + catch (e) { + return false; + } + }, + isExported: true, + }; + // ----------------------------------------------------------------------------- // NA // ----------------------------------------------------------------------------- const NA = { @@ -14394,6 +14458,7 @@ ISNONTEXT: ISNONTEXT, ISNUMBER: ISNUMBER, ISTEXT: ISTEXT, + ISBLANK: ISBLANK, NA: NA }); @@ -14469,6 +14534,33 @@ isExported: true, }; // ----------------------------------------------------------------------------- + // IFNA + // ----------------------------------------------------------------------------- + const IFNA = { + description: _lt("Value if it is not an #N/A error, otherwise 2nd argument."), + args: args(` + value (any, lazy) ${_lt("The value to return if value itself is not #N/A an error.")} + value_if_error (any, lazy, default=${_lt("An empty value")}) ${_lt("The value the function returns if value is an #N/A error.")} + `), + returns: ["ANY"], + compute: function (value, valueIfError = () => "") { + let result; + try { + result = value(); + } + catch (e) { + if (e.errorType === CellErrorType.NotAvailable) { + result = valueIfError(); + } + else { + result = value(); + } + } + return result === null || result === undefined ? "" : result; + }, + isExported: true, + }; + // ----------------------------------------------------------------------------- // IFS // ----------------------------------------------------------------------------- const IFS = { @@ -14557,6 +14649,7 @@ AND: AND, IF: IF, IFERROR: IFERROR, + IFNA: IFNA, IFS: IFS, NOT: NOT, OR: OR, @@ -14564,25 +14657,8 @@ }); const DEFAULT_IS_SORTED = true; - /** - * Perform a linear search and return the index of the perfect match. - * -1 is returned if no value is found. - * - * Example: - * - [3, 6, 10], 3 => 0 - * - [3, 6, 10], 6 => 1 - * - [3, 6, 10], 9 => -1 - * - [3, 6, 10], 2 => -1 - */ - function linearSearch(range, target) { - for (let i = 0; i < range.length; i++) { - if (range[i] === target) { - return i; - } - } - // no value is found, -1 is returned - return -1; - } + const DEFAULT_MATCH_MODE = 0; + const DEFAULT_SEARCH_MODE = 1; // ----------------------------------------------------------------------------- // COLUMN // ----------------------------------------------------------------------------- @@ -14627,15 +14703,15 @@ returns: ["ANY"], compute: function (searchKey, range, index, isSorted = DEFAULT_IS_SORTED) { const _index = Math.trunc(toNumber(index)); + const _searchKey = normalizeValue(searchKey); assert(() => 1 <= _index && _index <= range[0].length, _lt("[[FUNCTION_NAME]] evaluates to an out of bounds range.")); const _isSorted = toBoolean(isSorted); - const firstRow = range.map((col) => col[0]); let colIndex; if (_isSorted) { - colIndex = dichotomicPredecessorSearch(firstRow, searchKey); + colIndex = dichotomicSearch(range, _searchKey, "nextSmaller", "asc", range.length, getNormalizedValueFromRowRange); } else { - colIndex = linearSearch(firstRow, searchKey); + colIndex = linearSearch(range, _searchKey, "strict", range.length, getNormalizedValueFromRowRange); } assert(() => colIndex > -1, _lt("Did not find value '%s' in [[FUNCTION_NAME]] evaluation.", toString(searchKey))); return range[colIndex][_index - 1]; @@ -14656,9 +14732,13 @@ compute: function (searchKey, searchArray, resultRange) { let nbCol = searchArray.length; let nbRow = searchArray[0].length; + const _searchKey = normalizeValue(searchKey); const verticalSearch = nbRow >= nbCol; - const searchRange = verticalSearch ? searchArray[0] : searchArray.map((c) => c[0]); - const index = dichotomicPredecessorSearch(searchRange, searchKey); + const getElement = verticalSearch + ? getNormalizedValueFromColumnRange + : getNormalizedValueFromRowRange; + const rangeLength = verticalSearch ? nbRow : nbCol; + const index = dichotomicSearch(searchArray, _searchKey, "nextSmaller", "asc", rangeLength, getElement); assert(() => index >= 0, _lt("Did not find value '%s' in [[FUNCTION_NAME]] evaluation.", toString(searchKey))); if (resultRange === undefined) { return (verticalSearch ? searchArray[nbCol - 1][index] : searchArray[index][nbRow - 1]); @@ -14689,21 +14769,23 @@ returns: ["NUMBER"], compute: function (searchKey, range, searchType = DEFAULT_SEARCH_TYPE) { let _searchType = toNumber(searchType); + const _searchKey = normalizeValue(searchKey); const nbCol = range.length; const nbRow = range[0].length; assert(() => nbCol === 1 || nbRow === 1, _lt("The range must be a single row or a single column.")); let index = -1; - const _range = range.flat(); + const getElement = nbCol === 1 ? getNormalizedValueFromColumnRange : getNormalizedValueFromRowRange; + const rangeLen = nbCol === 1 ? range[0].length : range.length; _searchType = Math.sign(_searchType); switch (_searchType) { case 1: - index = dichotomicPredecessorSearch(_range, searchKey); + index = dichotomicSearch(range, _searchKey, "nextSmaller", "asc", rangeLen, getElement); break; case 0: - index = linearSearch(_range, searchKey); + index = linearSearch(range, _searchKey, "strict", rangeLen, getElement); break; case -1: - index = dichotomicSuccessorSearch(_range, searchKey); + index = dichotomicSearch(range, _searchKey, "nextGreater", "desc", rangeLen, getElement); break; } assert(() => index >= 0, _lt("Did not find value '%s' in [[FUNCTION_NAME]] evaluation.", toString(searchKey))); @@ -14754,21 +14836,71 @@ returns: ["ANY"], compute: function (searchKey, range, index, isSorted = DEFAULT_IS_SORTED) { const _index = Math.trunc(toNumber(index)); + const _searchKey = normalizeValue(searchKey); assert(() => 1 <= _index && _index <= range.length, _lt("[[FUNCTION_NAME]] evaluates to an out of bounds range.")); const _isSorted = toBoolean(isSorted); - const firstCol = range[0]; let rowIndex; if (_isSorted) { - rowIndex = dichotomicPredecessorSearch(firstCol, searchKey); + rowIndex = dichotomicSearch(range, _searchKey, "nextSmaller", "asc", range[0].length, getNormalizedValueFromColumnRange); } else { - rowIndex = linearSearch(firstCol, searchKey); + rowIndex = linearSearch(range, _searchKey, "strict", range[0].length, getNormalizedValueFromColumnRange); } assert(() => rowIndex > -1, _lt("Did not find value '%s' in [[FUNCTION_NAME]] evaluation.", toString(searchKey))); return range[_index - 1][rowIndex]; }, isExported: true, }; + // ----------------------------------------------------------------------------- + // XLOOKUP + // ----------------------------------------------------------------------------- + const XLOOKUP = { + description: _lt(`Search a range for a match and return the corresponding item from a second range.`), + args: args(` + search_key (any) ${_lt("The value to search for.")} + lookup_range (any, range) ${_lt("The range to consider for the search. Should be a single column or a single row.")} + return_range (any, range) ${_lt("The range containing the return value. Should have the same dimensions as lookup_range.")} + if_not_found (any, lazy, optional) ${_lt("If a valid match is not found, return this value.")} + match_mode (any, default=${DEFAULT_MATCH_MODE}) ${_lt("(0) Exact match. (-1) Return next smaller item if no match. (1) Return next greater item if no match.")} + search_mode (any, default=${DEFAULT_SEARCH_MODE}) ${_lt("(1) Search starting at first item. \ + (-1) Search starting at last item. \ + (2) Perform a binary search that relies on lookup_array being sorted in ascending order. If not sorted, invalid results will be returned. \ + (-2) Perform a binary search that relies on lookup_array being sorted in descending order. If not sorted, invalid results will be returned.\ + ")} + + `), + returns: ["ANY"], + compute: function (searchKey, lookupRange, returnRange, defaultValue, matchMode = DEFAULT_MATCH_MODE, searchMode = DEFAULT_SEARCH_MODE) { + const _matchMode = Math.trunc(toNumber(matchMode)); + const _searchMode = Math.trunc(toNumber(searchMode)); + const _searchKey = normalizeValue(searchKey); + assert(() => lookupRange.length === 1 || lookupRange[0].length === 1, _lt("lookup_range should be either a single row or single column.")); + assert(() => returnRange.length === 1 || returnRange[0].length === 1, _lt("return_range should be either a single row or single column.")); + assert(() => returnRange.length === lookupRange.length && + returnRange[0].length === lookupRange[0].length, _lt("return_range should have the same dimensions as lookup_range.")); + assert(() => [-1, 1, -2, 2].includes(_searchMode), _lt("searchMode should be a value in [-1, 1, -2, 2].")); + assert(() => [-1, 0, 1].includes(_matchMode), _lt("matchMode should be a value in [-1, 0, 1].")); + const getElement = lookupRange.length === 1 ? getNormalizedValueFromColumnRange : getNormalizedValueFromRowRange; + const rangeLen = lookupRange.length === 1 ? lookupRange[0].length : lookupRange.length; + const mode = _matchMode === 0 ? "strict" : _matchMode === 1 ? "nextGreater" : "nextSmaller"; + const reverseSearch = _searchMode === -1; + let index; + if (_searchMode === 2 || _searchMode === -2) { + const sortOrder = _searchMode === 2 ? "asc" : "desc"; + index = dichotomicSearch(lookupRange, _searchKey, mode, sortOrder, rangeLen, getElement); + } + else { + index = linearSearch(lookupRange, _searchKey, mode, rangeLen, getElement, reverseSearch); + } + if (index !== -1) { + return (lookupRange.length === 1 ? returnRange[0][index] : returnRange[index][0]); + } + const _defaultValue = defaultValue === null || defaultValue === void 0 ? void 0 : defaultValue(); + assert(() => !!_defaultValue, _lt("Did not find value '%s' in [[FUNCTION_NAME]] evaluation.", toString(searchKey))); + return _defaultValue; + }, + isExported: true, + }; var lookup = /*#__PURE__*/Object.freeze({ __proto__: null, @@ -14779,7 +14911,8 @@ MATCH: MATCH, ROW: ROW, ROWS: ROWS, - VLOOKUP: VLOOKUP + VLOOKUP: VLOOKUP, + XLOOKUP: XLOOKUP }); // ----------------------------------------------------------------------------- @@ -15054,6 +15187,8 @@ }); const DEFAULT_STARTING_AT = 1; + /** Regex matching all the words in a string */ + const wordRegex = /[A-Za-zÀ-ÖØ-öø-ÿ]+/g; // ----------------------------------------------------------------------------- // CHAR // ----------------------------------------------------------------------------- @@ -15071,6 +15206,27 @@ isExported: true, }; // ----------------------------------------------------------------------------- + // CLEAN + // ----------------------------------------------------------------------------- + const CLEAN = { + description: _lt("Remove non-printable characters from a piece of text."), + args: args(` + text (string) ${_lt("The text whose non-printable characters are to be removed.")} + `), + returns: ["STRING"], + compute: function (text) { + const _text = toString(text); + let cleanedStr = ""; + for (const char of _text) { + if (char && char.charCodeAt(0) > 31) { + cleanedStr += char; + } + } + return cleanedStr; + }, + isExported: true, + }; + // ----------------------------------------------------------------------------- // CONCATENATE // ----------------------------------------------------------------------------- const CONCATENATE = { @@ -15185,6 +15341,44 @@ isExported: true, }; // ----------------------------------------------------------------------------- + // MID + // ----------------------------------------------------------------------------- + const MID = { + description: _lt("A segment of a string."), + args: args(` + text (string) ${_lt("The string to extract a segment from.")} + starting_at (number) ${_lt("The index from the left of string from which to begin extracting. The first character in string has the index 1.")} + extract_length (number) ${_lt("The length of the segment to extract.")} + `), + returns: ["STRING"], + compute: function (text, starting_at, extract_length) { + const _text = toString(text); + const _starting_at = toNumber(starting_at); + const _extract_length = toNumber(extract_length); + assert(() => _starting_at >= 1, _lt("The starting_at argument (%s) must be positive greater than one.", _starting_at.toString())); + assert(() => _extract_length >= 0, _lt("The extract_length argument (%s) must be positive or null.", _extract_length.toString())); + return _text.slice(_starting_at - 1, _starting_at + _extract_length - 1); + }, + isExported: true, + }; + // ----------------------------------------------------------------------------- + // PROPER + // ----------------------------------------------------------------------------- + const PROPER = { + description: _lt("Capitalizes each word in a specified string."), + args: args(` + text_to_capitalize (string) ${_lt("The text which will be returned with the first letter of each word in uppercase and all other letters in lowercase.")} + `), + returns: ["STRING"], + compute: function (text) { + const _text = toString(text); + return _text.replace(wordRegex, (word) => { + return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(); + }); + }, + isExported: true, + }; + // ----------------------------------------------------------------------------- // REPLACE // ----------------------------------------------------------------------------- const REPLACE = { @@ -15346,6 +15540,7 @@ var text = /*#__PURE__*/Object.freeze({ __proto__: null, CHAR: CHAR, + CLEAN: CLEAN, CONCATENATE: CONCATENATE, EXACT: EXACT, FIND: FIND, @@ -15353,6 +15548,8 @@ LEFT: LEFT, LEN: LEN, LOWER: LOWER, + MID: MID, + PROPER: PROPER, REPLACE: REPLACE, RIGHT: RIGHT, SEARCH: SEARCH, @@ -16287,7 +16484,9 @@ * the compiled formula does not depend on their actual value. * Both `=A1+1+"2"` and `=A2+2+"3"` are compiled to the exact same function. * - * A formula `=A1+A2+SUM(2, 2, "2")` have the cache key `=|0|+|1|+SUM(|N0|, |N0|, |S0|)` + * Spaces are also ignored to compute the cache key. + * + * A formula `=A1+A2+SUM(2, 2, "2")` have the cache key `=|0|+|1|+SUM(|N0|,|N0|,|S0|)` */ function compilationCacheKey(tokens, dependencies, constantValues) { return concat(tokens.map((token) => { @@ -16300,6 +16499,8 @@ case "REFERENCE": case "INVALID_REFERENCE": return `|${dependencies.indexOf(token.value)}|`; + case "SPACE": + return ""; default: return token.value; } @@ -16555,7 +16756,7 @@ * There should not be any side effects in this method. */ allowDispatch(command) { - return 0 /* Success */; + return 0 /* CommandResult.Success */; } /** * This method is useful when a plugin need to perform some action before a @@ -16593,12 +16794,12 @@ if (!Array.isArray(results)) { results = [results]; } - const cancelledReasons = results.filter((result) => result !== 0 /* Success */); + const cancelledReasons = results.filter((result) => result !== 0 /* CommandResult.Success */); if (cancelledReasons.length) { return cancelledReasons; } } - return 0 /* Success */; + return 0 /* CommandResult.Success */; }; } checkValidations(command, ...validations) { @@ -16656,7 +16857,7 @@ return this.validateSelection(cmd.content.length, cmd.selection.start, cmd.selection.end); } else { - return 0 /* Success */; + return 0 /* CommandResult.Success */; } case "START_EDITION": if (cmd.selection) { @@ -16665,10 +16866,10 @@ return this.validateSelection(content.length, cmd.selection.start, cmd.selection.end); } else { - return 0 /* Success */; + return 0 /* CommandResult.Success */; } default: - return 0 /* Success */; + return 0 /* CommandResult.Success */; } } handleEvent(event) { @@ -16865,8 +17066,8 @@ } validateSelection(length, start, end) { return start >= 0 && start <= length && end >= 0 && end <= length - ? 0 /* Success */ - : 43 /* WrongComposerSelection */; + ? 0 /* CommandResult.Success */ + : 43 /* CommandResult.WrongComposerSelection */; } onColumnsRemoved(cmd) { if (cmd.elements.includes(this.col) && this.mode !== "inactive") { @@ -18090,16 +18291,12 @@ const ACTIVE_BORDER_WIDTH = 2; const MIN_FIG_SIZE = 80; css /*SCSS*/ ` - .o-figure-wrapper { - position: absolute; - width: 100%; - height: 100%; - overflow: hidden; - } - div.o-figure { box-sizing: content-box; position: absolute; + width: 100%; + height: 100%; + bottom: 0px; right: 0px; border: solid ${FIGURE_BORDER_COLOR}; @@ -18116,10 +18313,16 @@ } } - .o-figure-container { + .o-figure-wrapper { position: absolute; box-sizing: content-box; + .o-figure-overflow-wrapper { + position: absolute; + overflow: hidden; + width: 100%; + height: 100%; + } .o-anchor { z-index: ${ComponentsImportance.ChartAnchor}; position: absolute; @@ -18155,64 +18358,47 @@ } } `; - class FiguresContainer extends owl.Component { + class FigureComponent extends owl.Component { constructor() { super(...arguments); this.figureRegistry = figureRegistry; + this.figureRef = owl.useRef("figure"); this.dnd = owl.useState({ - figureId: "", + isActive: false, x: 0, y: 0, width: 0, height: 0, }); } - getVisibleFigures() { - const selectedId = this.env.model.getters.getSelectedFigureId(); - return this.env.model.getters.getVisibleFigures().map((f) => { - let figure = f; - // Returns current state of drag&drop figure instead of its stored state - if (this.dnd.figureId === f.id) { - figure = { - ...f, - x: this.dnd.x, - y: this.dnd.y, - width: this.dnd.width, - height: this.dnd.height, - }; - } - return { - id: f.id, - isSelected: f.id === selectedId, - figure: figure, - }; - }); + get displayedFigure() { + return this.dnd.isActive ? { ...this.props.figure, ...this.dnd } : this.props.figure; + } + get isSelected() { + return this.env.model.getters.getSelectedFigureId() === this.props.figure.id; } /** Get the current figure size, which is either the stored figure size of the DnD figure size */ - getFigureSize(info) { - const { figure, isSelected } = info; - const target = figure.id === (isSelected && this.dnd.figureId) ? this.dnd : figure; - const { width, height } = target; + getFigureSize() { + const { width, height } = this.displayedFigure; return { width, height }; } - getFigureSizeWithBorders(info) { - const { width, height } = this.getFigureSize(info); - const borders = this.getBorderWidth(info) * 2; + getFigureSizeWithBorders() { + const { width, height } = this.getFigureSize(); + const borders = this.getBorderWidth() * 2; return { width: width + borders, height: height + borders }; } - getBorderWidth(info) { - return info.isSelected ? ACTIVE_BORDER_WIDTH : this.env.isDashboard() ? 0 : BORDER_WIDTH; + getBorderWidth() { + return this.isSelected ? ACTIVE_BORDER_WIDTH : this.env.isDashboard() ? 0 : BORDER_WIDTH; } - getFigureStyle(info) { - const { width, height } = info.figure; - return `width:${width}px;height:${height}px;border-width: ${this.getBorderWidth(info)}px;`; + getFigureStyle() { + const { width, height } = this.displayedFigure; + return `width:${width}px;height:${height}px;border-width: ${this.getBorderWidth()}px;`; } - getContainerStyle(info) { - const { figure, isSelected } = info; - const target = figure.id === (isSelected && this.dnd.figureId) ? this.dnd : figure; + getContainerStyle() { + const target = this.displayedFigure; const { x: offsetCorrectionX, y: offsetCorrectionY } = this.env.model.getters.getMainViewportCoordinates(); const { offsetX, offsetY } = this.env.model.getters.getActiveSheetScrollInfo(); - let { width, height } = this.getFigureSizeWithBorders(info); + let { width, height } = this.getFigureSizeWithBorders(); let x, y; // Visually, the content of the container is slightly shifted as it includes borders and/or corners. // If we want to make assertions on the position of the content, we need to take this shift into account @@ -18240,19 +18426,18 @@ if (width < 0 || height < 0) { return `display:none;`; } - const borderOffset = BORDER_WIDTH - this.getBorderWidth(info); + const borderOffset = BORDER_WIDTH - this.getBorderWidth(); // TODO : remove the +1 once 2951210 is fixed return (`top:${y + borderOffset + 1}px;` + `left:${x + borderOffset}px;` + `width:${width}px;` + `height:${height}px;` + - `z-index: ${ComponentsImportance.Figure + (info.isSelected ? 1 : 0)}`); + `z-index: ${ComponentsImportance.Figure + (this.isSelected ? 1 : 0)}`); } - getAnchorPosition(anchor, info) { - let { width, height } = this.getFigureSizeWithBorders(info); + getAnchorPosition(anchor) { + let { width, height } = this.getFigureSizeWithBorders(); const anchorCenteringOffset = (ANCHOR_SIZE - ACTIVE_BORDER_WIDTH) / 2; - const { figure, isSelected } = info; - const target = figure.id === (isSelected && this.dnd.figureId) ? this.dnd : figure; + const target = this.displayedFigure; let x = 0; let y = 0; const { x: offsetCorrectionX, y: offsetCorrectionY } = this.env.model.getters.getMainViewportCoordinates(); @@ -18301,22 +18486,18 @@ return `visibility:${visibility};top:${y}px; left:${x}px;`; } setup() { - owl.onMounted(() => { - // horrible, but necessary - // the following line ensures that we render the figures with the correct - // viewport. The reason is that whenever we initialize the grid - // component, we do not know yet the actual size of the viewport, so the - // first owl rendering is done with an empty viewport. Only then we can - // compute which figures should be displayed, so we have to force a - // new rendering - this.render(); - }); + owl.useEffect((selectedFigureId, thisFigureId, el) => { + if (selectedFigureId === thisFigureId) { + el === null || el === void 0 ? void 0 : el.focus(); + } + }, () => [this.env.model.getters.getSelectedFigureId(), this.props.figure.id, this.figureRef.el]); } - resize(figure, dirX, dirY, ev) { + resize(dirX, dirY, ev) { + const figure = this.props.figure; ev.stopPropagation(); const initialX = ev.clientX; const initialY = ev.clientY; - this.dnd.figureId = figure.id; + this.dnd.isActive = true; this.dnd.x = figure.x; this.dnd.y = figure.y; this.dnd.width = figure.width; @@ -18334,7 +18515,7 @@ } }; const onMouseUp = (ev) => { - this.dnd.figureId = ""; + this.dnd.isActive = false; const update = { x: this.dnd.x, y: this.dnd.y, @@ -18353,7 +18534,8 @@ }; startDnd(onMouseMove, onMouseUp); } - onMouseDown(figure, ev) { + onMouseDown(ev) { + const figure = this.props.figure; if (ev.button > 0 || this.env.model.getters.isReadonly()) { // not main button, probably a context menu return; @@ -18363,14 +18545,14 @@ return; } if (this.props.sidePanelIsOpen) { - this.env.openSidePanel("ChartPanel", { figureId: figure.id }); + this.env.openSidePanel("ChartPanel"); } const position = gridOverlayPosition(); const { x: offsetCorrectionX, y: offsetCorrectionY } = this.env.model.getters.getMainViewportCoordinates(); const { offsetX, offsetY } = this.env.model.getters.getActiveSheetScrollInfo(); const initialX = ev.clientX - position.left; const initialY = ev.clientY - position.top; - this.dnd.figureId = figure.id; + this.dnd.isActive = true; this.dnd.x = figure.x; this.dnd.y = figure.y; this.dnd.width = figure.width; @@ -18396,7 +18578,7 @@ this.dnd.y = Math.max(figure.y + deltaY, 0); }; const onMouseUp = (ev) => { - this.dnd.figureId = ""; + this.dnd.isActive = false; this.env.model.dispatch("UPDATE_FIGURE", { sheetId: this.env.model.getters.getActiveSheetId(), id: figure.id, @@ -18406,7 +18588,8 @@ }; startDnd(onMouseMove, onMouseUp); } - onKeyDown(figure, ev) { + onKeyDown(ev) { + const figure = this.props.figure; switch (ev.key) { case "Delete": this.env.model.dispatch("DELETE_FIGURE", { @@ -18438,10 +18621,229 @@ } } } + FigureComponent.template = "o-spreadsheet-FigureComponent"; + FigureComponent.components = {}; + + class FiguresContainer extends owl.Component { + getVisibleFigures() { + return this.env.model.getters.getVisibleFigures(); + } + setup() { + owl.onMounted(() => { + // horrible, but necessary + // the following line ensures that we render the figures with the correct + // viewport. The reason is that whenever we initialize the grid + // component, we do not know yet the actual size of the viewport, so the + // first owl rendering is done with an empty viewport. Only then we can + // compute which figures should be displayed, so we have to force a + // new rendering + this.render(); + }); + } + } FiguresContainer.template = "o-spreadsheet-FiguresContainer"; - FiguresContainer.components = {}; + FiguresContainer.components = { FigureComponent }; figureRegistry.add("chart", { Component: ChartFigure, SidePanelComponent: "ChartPanel" }); + /** + * Repeatedly calls a callback function with a time delay between calls. + */ + function useInterval(callback, delay) { + let intervalId; + const { setInterval, clearInterval } = window; + owl.useEffect(() => { + intervalId = setInterval(callback, delay); + return () => clearInterval(intervalId); + }, () => [delay]); + return { + pause: () => { + clearInterval(intervalId); + intervalId = undefined; + }, + resume: () => { + if (intervalId === undefined) { + intervalId = setInterval(callback, delay); + } + }, + }; + } + + function useCellHovered(env, gridRef, callback) { + let hoveredPosition = { + col: undefined, + row: undefined, + }; + const { Date } = window; + let x = 0; + let y = 0; + let lastMoved = 0; + function getPosition() { + const col = env.model.getters.getColIndex(x); + const row = env.model.getters.getRowIndex(y); + return { col, row }; + } + const { pause, resume } = useInterval(checkTiming, 200); + function checkTiming() { + const { col, row } = getPosition(); + const delta = Date.now() - lastMoved; + if (delta > 300 && (col !== hoveredPosition.col || row !== hoveredPosition.row)) { + setPosition(undefined, undefined); + } + if (delta > 300) { + if (col < 0 || row < 0) { + return; + } + setPosition(col, row); + } + } + function updateMousePosition(e) { + x = e.offsetX; + y = e.offsetY; + lastMoved = Date.now(); + } + function recompute() { + const { col, row } = getPosition(); + if (col !== hoveredPosition.col || row !== hoveredPosition.row) { + setPosition(undefined, undefined); + } + } + owl.onMounted(() => { + const grid = gridRef.el; + grid.addEventListener("mousemove", updateMousePosition); + grid.addEventListener("mouseleave", pause); + grid.addEventListener("mouseenter", resume); + grid.addEventListener("mousedown", recompute); + }); + owl.onWillUnmount(() => { + const grid = gridRef.el; + grid.removeEventListener("mousemove", updateMousePosition); + grid.removeEventListener("mouseleave", pause); + grid.removeEventListener("mouseenter", resume); + grid.removeEventListener("mousedown", recompute); + }); + function setPosition(col, row) { + if (col !== hoveredPosition.col || row !== hoveredPosition.row) { + hoveredPosition.col = col; + hoveredPosition.row = row; + callback({ col, row }); + } + } + return hoveredPosition; + } + function useTouchMove(gridRef, handler, canMoveUp) { + let x = null; + let y = null; + function onTouchStart(ev) { + if (ev.touches.length !== 1) + return; + x = ev.touches[0].clientX; + y = ev.touches[0].clientY; + } + function onTouchEnd() { + x = null; + y = null; + } + function onTouchMove(ev) { + if (ev.touches.length !== 1) + return; + // On mobile browsers, swiping down is often associated with "pull to refresh". + // We only want this behavior if the grid is already at the top. + // Otherwise we only want to move the canvas up, without triggering any refresh. + if (canMoveUp()) { + ev.preventDefault(); + ev.stopPropagation(); + } + const currentX = ev.touches[0].clientX; + const currentY = ev.touches[0].clientY; + handler(x - currentX, y - currentY); + x = currentX; + y = currentY; + } + owl.onMounted(() => { + gridRef.el.addEventListener("touchstart", onTouchStart); + gridRef.el.addEventListener("touchend", onTouchEnd); + gridRef.el.addEventListener("touchmove", onTouchMove); + }); + owl.onWillUnmount(() => { + gridRef.el.removeEventListener("touchstart", onTouchStart); + gridRef.el.removeEventListener("touchend", onTouchEnd); + gridRef.el.removeEventListener("touchmove", onTouchMove); + }); + } + class GridOverlay extends owl.Component { + setup() { + this.gridOverlay = owl.useRef("gridOverlay"); + useCellHovered(this.env, this.gridOverlay, this.props.onCellHovered); + owl.useEffect(() => this.props.onGridResized({ + height: this.gridOverlayEl.clientHeight, + width: this.gridOverlayEl.clientWidth, + }), () => [this.gridOverlayEl.clientHeight, this.gridOverlayEl.clientWidth]); + useTouchMove(this.gridOverlay, this.props.onGridMoved, () => { + const { offsetScrollbarY } = this.env.model.getters.getActiveSheetScrollInfo(); + return offsetScrollbarY > 0; + }); + } + get gridOverlayEl() { + if (!this.gridOverlay.el) { + throw new Error("GridOverlay el is not defined."); + } + return this.gridOverlay.el; + } + onMouseDown(ev) { + if (ev.button > 0) { + // not main button, probably a context menu + return; + } + const [col, row] = this.getCartesianCoordinates(ev); + this.props.onCellClicked(col, row, { shiftKey: ev.shiftKey, ctrlKey: ev.ctrlKey }); + } + onDoubleClick(ev) { + const [col, row] = this.getCartesianCoordinates(ev); + this.props.onCellDoubleClicked(col, row); + } + onContextMenu(ev) { + ev.preventDefault(); + const [col, row] = this.getCartesianCoordinates(ev); + this.props.onCellRightClicked(col, row, { x: ev.clientX, y: ev.clientY }); + } + getCartesianCoordinates(ev) { + const colIndex = this.env.model.getters.getColIndex(ev.offsetX); + const rowIndex = this.env.model.getters.getRowIndex(ev.offsetY); + return [colIndex, rowIndex]; + } + } + GridOverlay.template = "o-spreadsheet-GridOverlay"; + GridOverlay.components = { FiguresContainer }; + GridOverlay.defaultProps = { + onCellHovered: () => { }, + onCellDoubleClicked: () => { }, + onCellClicked: () => { }, + onCellRightClicked: () => { }, + onGridResized: () => { }, + onFigureDeleted: () => { }, + sidePanelIsOpen: false, + }; + + class GridPopover extends owl.Component { + get cellPopover() { + const popover = this.env.model.getters.getCellPopover(this.props.hoveredCell); + if (!popover.isOpen) { + return { isOpen: false }; + } + const coordinates = popover.coordinates; + return { + ...popover, + // transform from the "canvas coordinate system" to the "body coordinate system" + coordinates: { + x: coordinates.x + this.props.gridPosition.x, + y: coordinates.y + this.props.gridPosition.y, + }, + }; + } + } + GridPopover.template = "o-spreadsheet-GridPopover"; + GridPopover.components = { Popover }; + class AbstractResizer extends owl.Component { constructor() { super(...arguments); @@ -18759,7 +19161,7 @@ base: this.state.base, elements, }); - if (!result.isSuccessful && result.reasons.includes(2 /* WillRemoveExistingMerge */)) { + if (!result.isSuccessful && result.reasons.includes(2 /* CommandResult.WillRemoveExistingMerge */)) { this.env.raiseError(MergeErrorMessage); } } @@ -18928,7 +19330,7 @@ base: this.state.base, elements, }); - if (!result.isSuccessful && result.reasons.includes(2 /* WillRemoveExistingMerge */)) { + if (!result.isSuccessful && result.reasons.includes(2 /* CommandResult.WillRemoveExistingMerge */)) { this.env.raiseError(MergeErrorMessage); } } @@ -18993,27 +19395,47 @@ HeadersOverlay.template = "o-spreadsheet-HeadersOverlay"; HeadersOverlay.components = { ColResizer, RowResizer }; - /** - * Repeatedly calls a callback function with a time delay between calls. - */ - function useInterval(callback, delay) { - let intervalId; - const { setInterval, clearInterval } = window; - owl.useEffect(() => { - intervalId = setInterval(callback, delay); - return () => clearInterval(intervalId); - }, () => [delay]); - return { - pause: () => { - clearInterval(intervalId); - intervalId = undefined; - }, - resume: () => { - if (intervalId === undefined) { - intervalId = setInterval(callback, delay); - } - }, + function useGridDrawing(refName, model, canvasSize) { + const canvasRef = owl.useRef(refName); + owl.useEffect(() => drawGrid()); + function drawGrid() { + const canvas = canvasRef.el; + const dpr = window.devicePixelRatio || 1; + const ctx = canvas.getContext("2d", { alpha: false }); + const thinLineWidth = 0.4 * dpr; + const renderingContext = { + ctx, + dpr, + thinLineWidth, + }; + const { width, height } = canvasSize(); + canvas.style.width = `${width}px`; + canvas.style.height = `${height}px`; + canvas.width = width * dpr; + canvas.height = height * dpr; + canvas.setAttribute("style", `width:${width}px;height:${height}px;`); + // Imagine each pixel as a large square. The whole-number coordinates (0, 1, 2…) + // are the edges of the squares. If you draw a one-unit-wide line between whole-number + // coordinates, it will overlap opposite sides of the pixel square, and the resulting + // line will be drawn two pixels wide. To draw a line that is only one pixel wide, + // you need to shift the coordinates by 0.5 perpendicular to the line's direction. + // http://diveintohtml5.info/canvas.html#pixel-madness + ctx.translate(-CANVAS_SHIFT, -CANVAS_SHIFT); + ctx.scale(dpr, dpr); + model.drawGrid(renderingContext); + } + } + + function useWheelHandler(handler) { + function normalize(val, deltaMode) { + return val * (deltaMode === 0 ? 1 : DEFAULT_CELL_HEIGHT); + } + const onMouseWheel = (ev) => { + const deltaX = normalize(ev.shiftKey ? ev.deltaY : ev.deltaX, ev.deltaMode); + const deltaY = normalize(ev.shiftKey ? ev.deltaX : ev.deltaY, ev.deltaMode); + handler(deltaX, deltaY); }; + return onMouseWheel; } css /* scss */ ` @@ -19222,7 +19644,7 @@ Border, }; - class ScrollBar { + class ScrollBar$1 { constructor(el, direction) { this.el = el; this.direction = direction; @@ -19240,186 +19662,177 @@ } } + css /* scss */ ` + .o-scrollbar { + position: absolute; + overflow: auto; + z-index: ${ComponentsImportance.ScrollBar}; + background-color: ${BACKGROUND_GRAY_COLOR}; + + // &.vertical { + // right: 0; + // bottom: ${SCROLLBAR_WIDTH$1}px; + // width: ${SCROLLBAR_WIDTH$1}px; + // overflow-x: hidden; + // } + // &.horizontal { + // bottom: 0; + // height: ${SCROLLBAR_WIDTH$1}px; + // right: ${SCROLLBAR_WIDTH$1}px; + // overflow-y: hidden; + // } + &.corner { + right: 0px; + bottom: 0px; + height: ${SCROLLBAR_WIDTH$1}px; + width: ${SCROLLBAR_WIDTH$1}px; + border-top: 1px solid #e2e3e3; + border-left: 1px solid #e2e3e3; + } + } +`; + class ScrollBar extends owl.Component { + setup() { + this.scrollbarRef = owl.useRef("scrollbar"); + this.scrollbar = new ScrollBar$1(this.scrollbarRef.el, this.props.direction); + owl.onMounted(() => { + this.scrollbar.el = this.scrollbarRef.el; + }); + // TODO improve useEffect dependencies typing in owl + owl.useEffect(() => { + if (this.scrollbar.scroll !== this.props.offset) { + this.scrollbar.scroll = this.props.offset; + } + }, () => [this.scrollbar.scroll, this.props.offset]); + } + get sizeCss() { + return cssPropertiesToCss({ + width: `${this.props.width}px`, + height: `${this.props.height}px`, + }); + } + get positionCss() { + return cssPropertiesToCss(this.props.position); + } + onScroll(ev) { + if (this.props.offset !== this.scrollbar.scroll) { + this.props.onScroll(this.scrollbar.scroll); + } + } + } + ScrollBar.template = owl.xml /*xml*/ ` +
+
+
+ `; + ScrollBar.defaultProps = { + width: 1, + height: 1, + }; + + class HorizontalScrollBar extends owl.Component { + get offset() { + return this.env.model.getters.getActiveSheetScrollInfo().offsetScrollbarX; + } + get width() { + return this.env.model.getters.getMainViewportRect().width; + } + get isDisplayed() { + const { xRatio } = this.env.model.getters.getFrozenSheetViewRatio(this.env.model.getters.getActiveSheetId()); + return xRatio < 1; + } + get position() { + const { x } = this.env.model.getters.getMainViewportRect(); + return { + left: `${this.props.position.left + x}px`, + bottom: "0px", + right: `${SCROLLBAR_WIDTH$1}px`, + }; + } + onScroll(offset) { + const { offsetScrollbarY } = this.env.model.getters.getActiveSheetScrollInfo(); + this.env.model.dispatch("SET_VIEWPORT_OFFSET", { + offsetX: offset, + offsetY: offsetScrollbarY, // offsetY is the same + }); + } + } + HorizontalScrollBar.components = { ScrollBar }; + HorizontalScrollBar.template = owl.xml /*xml*/ ` + `; + HorizontalScrollBar.defaultProps = { + position: { left: 0 }, + }; + + class VerticalScrollBar extends owl.Component { + get offset() { + return this.env.model.getters.getActiveSheetScrollInfo().offsetScrollbarY; + } + get height() { + return this.env.model.getters.getMainViewportRect().height; + } + get isDisplayed() { + const { yRatio } = this.env.model.getters.getFrozenSheetViewRatio(this.env.model.getters.getActiveSheetId()); + return yRatio < 1; + } + get position() { + const { y } = this.env.model.getters.getMainViewportRect(); + return { + top: `${this.props.position.top + y}px`, + right: "0px", + bottom: `${SCROLLBAR_WIDTH$1}px`, + }; + } + onScroll(offset) { + const { offsetScrollbarX } = this.env.model.getters.getActiveSheetScrollInfo(); + this.env.model.dispatch("SET_VIEWPORT_OFFSET", { + offsetX: offsetScrollbarX, + offsetY: offset, + }); + } + } + VerticalScrollBar.components = { ScrollBar }; + VerticalScrollBar.template = owl.xml /*xml*/ ` + `; + VerticalScrollBar.defaultProps = { + position: { top: 0 }, + }; + const registries$1 = { ROW: rowMenuRegistry, COL: colMenuRegistry, CELL: cellMenuRegistry, - DASHBOARD: dashboardMenuRegistry, }; // copy and paste are specific events that should not be managed by the keydown event, // but they shouldn't be preventDefault and stopped (else copy and paste events will not trigger) // and also should not result in typing the character C or V in the composer const keyDownMappingIgnore = ["CTRL+C", "CTRL+V"]; - // ----------------------------------------------------------------------------- - // Error Tooltip Hook - // ----------------------------------------------------------------------------- - function useCellHovered(env) { - const hoveredPosition = owl.useState({}); - const { Date } = window; - const gridRef = owl.useRef("gridOverlay"); - const vScrollbarRef = owl.useRef("vscrollbar"); - const hScrollbarRef = owl.useRef("hscrollbar"); - let x = 0; - let y = 0; - let lastMoved = 0; - function getPosition() { - const col = env.model.getters.getColIndex(x); - const row = env.model.getters.getRowIndex(y); - return { col, row }; - } - const { pause, resume } = useInterval(checkTiming, 200); - function checkTiming() { - const { col, row } = getPosition(); - const delta = Date.now() - lastMoved; - if (delta > 300 && (col !== hoveredPosition.col || row !== hoveredPosition.row)) { - hoveredPosition.col = undefined; - hoveredPosition.row = undefined; - } - if (delta > 300) { - if (col < 0 || row < 0) { - return; - } - hoveredPosition.col = col; - hoveredPosition.row = row; - } - } - function updateMousePosition(e) { - x = e.offsetX; - y = e.offsetY; - lastMoved = Date.now(); - } - function recompute() { - const { col, row } = getPosition(); - if (col !== hoveredPosition.col || row !== hoveredPosition.row) { - hoveredPosition.col = undefined; - hoveredPosition.row = undefined; - } - } - function reset() { - hoveredPosition.col = undefined; - hoveredPosition.row = undefined; - } - owl.onMounted(() => { - const grid = gridRef.el; - grid.addEventListener("mousemove", updateMousePosition); - grid.addEventListener("mouseleave", pause); - grid.addEventListener("mouseenter", resume); - grid.addEventListener("mousedown", recompute); - vScrollbarRef.el.addEventListener("scroll", reset); - hScrollbarRef.el.addEventListener("scroll", reset); - }); - owl.onWillUnmount(() => { - const grid = gridRef.el; - grid.removeEventListener("mousemove", updateMousePosition); - grid.removeEventListener("mouseleave", pause); - grid.removeEventListener("mouseenter", resume); - grid.removeEventListener("mousedown", recompute); - vScrollbarRef.el.removeEventListener("scroll", reset); - hScrollbarRef.el.removeEventListener("scroll", reset); - }); - return hoveredPosition; - } - function useTouchMove(handler, canMoveUp) { - const canvasRef = owl.useRef("canvas"); - let x = null; - let y = null; - function onTouchStart(ev) { - if (ev.touches.length !== 1) - return; - x = ev.touches[0].clientX; - y = ev.touches[0].clientY; - } - function onTouchEnd() { - x = null; - y = null; - } - function onTouchMove(ev) { - if (ev.touches.length !== 1) - return; - // On mobile browsers, swiping down is often associated with "pull to refresh". - // We only want this behavior if the grid is already at the top. - // Otherwise we only want to move the canvas up, without triggering any refresh. - if (canMoveUp()) { - ev.preventDefault(); - ev.stopPropagation(); - } - const currentX = ev.touches[0].clientX; - const currentY = ev.touches[0].clientY; - handler(x - currentX, y - currentY); - x = currentX; - y = currentY; - } - owl.onMounted(() => { - canvasRef.el.addEventListener("touchstart", onTouchStart); - canvasRef.el.addEventListener("touchend", onTouchEnd); - canvasRef.el.addEventListener("touchmove", onTouchMove); - }); - owl.onWillUnmount(() => { - canvasRef.el.removeEventListener("touchstart", onTouchStart); - canvasRef.el.removeEventListener("touchend", onTouchEnd); - canvasRef.el.removeEventListener("touchmove", onTouchMove); - }); - } - // ----------------------------------------------------------------------------- - // TEMPLATE - // ----------------------------------------------------------------------------- - // ----------------------------------------------------------------------------- - // STYLE - // ----------------------------------------------------------------------------- - css /* scss */ ` - .o-grid { - position: relative; - overflow: hidden; - background-color: ${BACKGROUND_GRAY_COLOR}; - - > canvas { - border-top: 1px solid #e2e3e3; - border-bottom: 1px solid #e2e3e3; - - &:focus { - outline: none; - } - } - .o-scrollbar { - position: absolute; - overflow: auto; - z-index: ${ComponentsImportance.ScrollBar}; - background-color: ${BACKGROUND_GRAY_COLOR}; - - &.vertical { - right: 0; - bottom: ${SCROLLBAR_WIDTH$1}px; - width: ${SCROLLBAR_WIDTH$1}px; - overflow-x: hidden; - } - &.horizontal { - bottom: 0; - height: ${SCROLLBAR_WIDTH$1}px; - right: ${SCROLLBAR_WIDTH$1}px; - overflow-y: hidden; - } - &.corner { - right: 0px; - bottom: 0px; - height: ${SCROLLBAR_WIDTH$1}px; - width: ${SCROLLBAR_WIDTH$1}px; - border-top: 1px solid #e2e3e3; - border-left: 1px solid #e2e3e3; - } - } - - .o-grid-overlay { - position: absolute; - outline: none; - } - } -`; // ----------------------------------------------------------------------------- // JS // ----------------------------------------------------------------------------- class Grid extends owl.Component { constructor() { super(...arguments); + this.HEADER_HEIGHT = HEADER_HEIGHT; + this.HEADER_WIDTH = HEADER_WIDTH; // this map will handle most of the actions that should happen on key down. The arrow keys are managed in the key // down itself this.keyDownMapping = { @@ -19532,87 +19945,41 @@ position: null, menuItems: [], }); - this.vScrollbarRef = owl.useRef("vscrollbar"); - this.hScrollbarRef = owl.useRef("hscrollbar"); this.gridRef = owl.useRef("grid"); - this.gridOverlay = owl.useRef("gridOverlay"); - this.canvas = owl.useRef("canvas"); - this.canvasPosition = useAbsolutePosition(this.canvas); - this.vScrollbar = new ScrollBar(this.vScrollbarRef.el, "vertical"); - this.hScrollbar = new ScrollBar(this.hScrollbarRef.el, "horizontal"); - this.currentSheet = this.env.model.getters.getActiveSheetId(); - this.clickedCol = 0; - this.clickedRow = 0; - this.hoveredCell = useCellHovered(this.env); + this.canvasPosition = useAbsolutePosition(this.gridRef); + this.hoveredCell = owl.useState({ col: undefined, row: undefined }); owl.useExternalListener(document.body, "cut", this.copy.bind(this, true)); owl.useExternalListener(document.body, "copy", this.copy.bind(this, false)); owl.useExternalListener(document.body, "paste", this.paste); - useTouchMove(this.moveCanvas.bind(this), () => this.vScrollbar.scroll > 0); - owl.onMounted(() => this.initGrid()); - owl.onPatched(() => { - this.drawGrid(); - this.resizeGrid(); - }); + owl.onMounted(() => this.focus()); this.props.exposeFocus(() => this.focus()); + useGridDrawing("canvas", this.env.model, () => this.env.model.getters.getSheetViewDimensionWithHeaders()); + owl.useEffect(() => this.focus(), () => [this.env.model.getters.getActiveSheetId()]); + this.onMouseWheel = useWheelHandler((deltaX, deltaY) => { + this.moveCanvas(deltaX, deltaY); + this.hoveredCell.col = undefined; + this.hoveredCell.row = undefined; + }); } - initGrid() { - this.vScrollbar.el = this.vScrollbarRef.el; - this.hScrollbar.el = this.hScrollbarRef.el; - this.focus(); - this.resizeGrid(); - this.drawGrid(); + onCellHovered({ col, row }) { + this.hoveredCell.col = col; + this.hoveredCell.row = row; } - get gridOverlayStyle() { + get gridOverlayDimensions() { return ` - top: ${this.env.isDashboard() ? 0 : HEADER_HEIGHT}px; - left: ${this.env.isDashboard() ? 0 : HEADER_WIDTH}px; - height: calc(100% - ${this.env.isDashboard() ? 0 : HEADER_HEIGHT}px); - width: calc(100% - ${this.env.isDashboard() ? 0 : HEADER_WIDTH}px); + top: ${HEADER_HEIGHT}px; + left: ${HEADER_WIDTH}px; + height: calc(100% - ${HEADER_HEIGHT + SCROLLBAR_WIDTH$1}px); + width: calc(100% - ${HEADER_WIDTH + SCROLLBAR_WIDTH$1}px); `; } - get vScrollbarStyle() { - const { y } = this.env.model.getters.getMainViewportRect(); - const { yRatio } = this.env.model.getters.getFrozenSheetViewRatio(this.env.model.getters.getActiveSheetId()); - return ` - ${this.env.isDashboard() || yRatio >= 1 ? "width: 0px;" : ""} - top: ${y + (this.env.isDashboard() ? 0 : HEADER_HEIGHT)}px;`; - } - get hScrollbarStyle() { - const { x } = this.env.model.getters.getMainViewportRect(); - const { xRatio } = this.env.model.getters.getFrozenSheetViewRatio(this.env.model.getters.getActiveSheetId()); - return ` - ${this.env.isDashboard() || xRatio >= 1 ? "width: 0px;" : ""} - left: ${x + (this.env.isDashboard() ? 0 : HEADER_WIDTH)}px;`; - } - get cellPopover() { - if (this.menuState.isOpen) { - return { isOpen: false }; - } - const popover = this.env.model.getters.getCellPopover(this.hoveredCell); - if (!popover.isOpen) { - return { isOpen: false }; - } - const coordinates = popover.coordinates; - return { - ...popover, - // transform from the "canvas coordinate system" to the "body coordinate system" - coordinates: { - x: coordinates.x + this.canvasPosition.x, - y: coordinates.y + this.canvasPosition.y, - }, - }; - } - get activeCellPosition() { - const { col, row } = this.env.model.getters.getPosition(); - return this.env.model.getters.getMainCellPosition(this.env.model.getters.getActiveSheetId(), col, row); - } onClosePopover() { this.closeOpenedPopover(); this.focus(); } focus() { if (!this.env.model.getters.getSelectedFigureId()) { - this.gridOverlay.el.focus(); + this.gridRef.el.focus(); } } get gridEl() { @@ -19621,45 +19988,6 @@ } return this.gridRef.el; } - getGridBoundingClientRect() { - return this.gridEl.getBoundingClientRect(); - } - resizeGrid() { - const scrollBarWidth = this.env.isDashboard() ? 0 : SCROLLBAR_WIDTH$1; - const currentHeight = this.gridEl.clientHeight - scrollBarWidth; - const currentWidth = this.gridEl.clientWidth - scrollBarWidth; - const { height: viewportHeight, width: viewportWidth } = this.env.model.getters.getSheetViewDimensionWithHeaders(); - if (currentHeight != viewportHeight || currentWidth !== viewportWidth) { - const { top: gridTop, left: gridLeft } = this.gridEl.getBoundingClientRect(); - const { top, left } = this.gridOverlay.el.getBoundingClientRect(); - const gridOffsetX = left - gridLeft; - const gridOffsetY = top - gridTop; - this.env.model.dispatch("RESIZE_SHEETVIEW", { - width: currentWidth - gridOffsetX, - height: currentHeight - gridOffsetY, - gridOffsetX, - gridOffsetY, - }); - } - } - onScroll() { - const { offsetScrollbarX, offsetScrollbarY } = this.env.model.getters.getActiveSheetScrollInfo(); - if (offsetScrollbarX !== this.hScrollbar.scroll || - offsetScrollbarY !== this.vScrollbar.scroll) { - const { maxOffsetX, maxOffsetY } = this.env.model.getters.getMaximumSheetOffset(); - this.env.model.dispatch("SET_VIEWPORT_OFFSET", { - offsetX: Math.min(this.hScrollbar.scroll, maxOffsetX), - offsetY: Math.min(this.vScrollbar.scroll, maxOffsetY), - }); - } - } - checkSheetChanges() { - const currentSheet = this.env.model.getters.getActiveSheetId(); - if (currentSheet !== this.currentSheet) { - this.focus(); - this.currentSheet = currentSheet; - } - } getAutofillPosition() { const zone = this.env.model.getters.getSelectedZone(); const rect = this.env.model.getters.getVisibleRect(zone); @@ -19678,115 +20006,48 @@ }); return !(rect.width === 0 || rect.height === 0); } - drawGrid() { - //reposition scrollbar - const { offsetScrollbarX, offsetScrollbarY } = this.env.model.getters.getActiveSheetScrollInfo(); - this.hScrollbar.scroll = offsetScrollbarX; - this.vScrollbar.scroll = offsetScrollbarY; - // check for position changes - this.checkSheetChanges(); - // drawing grid on canvas - const canvas = this.canvas.el; - const dpr = window.devicePixelRatio || 1; - const ctx = canvas.getContext("2d", { alpha: false }); - const thinLineWidth = 0.4 * dpr; - const renderingContext = { - ctx, - dpr, - thinLineWidth, - }; - const { width, height } = this.env.model.getters.getSheetViewDimensionWithHeaders(); - canvas.style.width = `${width}px`; - canvas.style.height = `${height}px`; - canvas.width = width * dpr; - canvas.height = height * dpr; - canvas.setAttribute("style", `width:${width}px;height:${height}px;`); - // Imagine each pixel as a large square. The whole-number coordinates (0, 1, 2…) - // are the edges of the squares. If you draw a one-unit-wide line between whole-number - // coordinates, it will overlap opposite sides of the pixel square, and the resulting - // line will be drawn two pixels wide. To draw a line that is only one pixel wide, - // you need to shift the coordinates by 0.5 perpendicular to the line's direction. - // http://diveintohtml5.info/canvas.html#pixel-madness - ctx.translate(-CANVAS_SHIFT, -CANVAS_SHIFT); - ctx.scale(dpr, dpr); - this.env.model.drawGrid(renderingContext); + onGridResized({ height, width }) { + this.env.model.dispatch("RESIZE_SHEETVIEW", { + width: width, + height: height, + gridOffsetX: HEADER_WIDTH, + gridOffsetY: HEADER_HEIGHT, + }); } moveCanvas(deltaX, deltaY) { - this.vScrollbar.scroll = this.vScrollbar.scroll + deltaY; - this.hScrollbar.scroll = this.hScrollbar.scroll + deltaX; + const { offsetScrollbarX, offsetScrollbarY } = this.env.model.getters.getActiveSheetScrollInfo(); this.env.model.dispatch("SET_VIEWPORT_OFFSET", { - offsetX: this.hScrollbar.scroll, - offsetY: this.vScrollbar.scroll, + offsetX: Math.max(offsetScrollbarX + deltaX, 0), + offsetY: Math.max(offsetScrollbarY + deltaY, 0), }); } getClientPositionKey(client) { var _a, _b, _c; return `${client.id}-${(_a = client.position) === null || _a === void 0 ? void 0 : _a.sheetId}-${(_b = client.position) === null || _b === void 0 ? void 0 : _b.col}-${(_c = client.position) === null || _c === void 0 ? void 0 : _c.row}`; } - onMouseWheel(ev) { - if (ev.ctrlKey) { - return; - } - function normalize(val) { - return val * (ev.deltaMode === 0 ? 1 : DEFAULT_CELL_HEIGHT); - } - const deltaX = ev.shiftKey ? ev.deltaY : ev.deltaX; - const deltaY = ev.shiftKey ? ev.deltaX : ev.deltaY; - this.moveCanvas(normalize(deltaX), normalize(deltaY)); - } isCellHovered(col, row) { return this.hoveredCell.col === col && this.hoveredCell.row === row; } // --------------------------------------------------------------------------- // Zone selection with mouse // --------------------------------------------------------------------------- - /** - * Get the coordinates in pixels, with 0,0 being the top left of the grid itself - */ - getCoordinates(ev) { - const rect = this.gridOverlay.el.getBoundingClientRect(); - const x = ev.pageX - rect.left; - const y = ev.pageY - rect.top; - return [x, y]; - } - getCartesianCoordinates(ev) { - const [x, y] = this.getCoordinates(ev); - const colIndex = this.env.model.getters.getColIndex(x); - const rowIndex = this.env.model.getters.getRowIndex(y); - return [colIndex, rowIndex]; - } - onMouseDown(ev) { - if (ev.button > 0) { - // not main button, probably a context menu - return; - } - if (ev.ctrlKey) { + onCellClicked(col, row, { ctrlKey, shiftKey }) { + if (ctrlKey) { this.env.model.dispatch("PREPARE_SELECTION_INPUT_EXPANSION"); } - const [col, row] = this.getCartesianCoordinates(ev); - if (col < 0 || row < 0) { - return; - } - this.clickedCol = col; - this.clickedRow = row; - if (this.env.model.getters.isDashboard()) { - this.env.model.selection.selectCell(col, row); - return; - } this.closeOpenedPopover(); if (this.env.model.getters.getEditionMode() === "editing") { this.env.model.dispatch("STOP_EDITION"); } - if (ev.shiftKey) { + if (shiftKey) { this.env.model.selection.setAnchorCorner(col, row); } - else if (ev.ctrlKey) { + else if (ctrlKey) { this.env.model.selection.addCellToSelection(col, row); } else { this.env.model.selection.selectCell(col, row); } - this.checkSheetChanges(); let prevCol = col; let prevRow = row; const onMouseMove = (col, row) => { @@ -19806,13 +20067,14 @@ }; dragAndDropBeyondTheViewport(this.env, onMouseMove, onMouseUp); } - onDoubleClick(ev) { - const [col, row] = this.getCartesianCoordinates(ev); - if (this.clickedCol === col && this.clickedRow === row) { - const cell = this.env.model.getters.getActiveCell(); - !cell || cell.isEmpty() - ? this.props.onGridComposerCellFocused() - : this.props.onComposerContentFocused(); + onCellDoubleClicked(col, row) { + const sheetId = this.env.model.getters.getActiveSheetId(); + const cell = this.env.model.getters.getCell(sheetId, col, row); + if (!cell || cell.isEmpty()) { + this.props.onGridComposerCellFocused(); + } + else { + this.props.onComposerContentFocused(); } } closeOpenedPopover() { @@ -19845,9 +20107,6 @@ } } onKeydown(ev) { - if (this.env.isDashboard()) { - return; - } if (ev.key.startsWith("Arrow")) { this.processArrows(ev); return; @@ -19882,12 +20141,7 @@ // --------------------------------------------------------------------------- // Context Menu // --------------------------------------------------------------------------- - onCanvasContextMenu(ev) { - ev.preventDefault(); - const [col, row] = this.getCartesianCoordinates(ev); - if (col < 0 || row < 0) { - return; - } + onCellRightClicked(col, row, { x, y }) { const zones = this.env.model.getters.getSelectedZones(); const lastZone = zones[zones.length - 1]; let type = "CELL"; @@ -19904,13 +20158,10 @@ type = "ROW"; } } - this.toggleContextMenu(type, ev.clientX, ev.clientY); + this.toggleContextMenu(type, x, y); } toggleContextMenu(type, x, y) { this.closeOpenedPopover(); - if (this.env.model.getters.isDashboard()) { - type = "DASHBOARD"; - } this.menuState.isOpen = true; this.menuState.position = { x, y }; this.menuState.menuItems = registries$1[type] @@ -19961,13 +20212,16 @@ Grid.template = "o-spreadsheet-Grid"; Grid.components = { GridComposer, + GridOverlay, + GridPopover, HeadersOverlay, Menu, Autofill, - FiguresContainer, ClientTag, Highlight, Popover, + VerticalScrollBar, + HorizontalScrollBar, }; /** @@ -24462,7 +24716,7 @@ case "UPDATE_CELL": return this.checkCellOutOfSheet(cmd.sheetId, cmd.col, cmd.row); default: - return 0 /* Success */; + return 0 /* CommandResult.Success */; } } handle(cmd) { @@ -24798,9 +25052,9 @@ checkCellOutOfSheet(sheetId, col, row) { const sheet = this.getters.tryGetSheet(sheetId); if (!sheet) - return 25 /* InvalidSheetId */; + return 25 /* CommandResult.InvalidSheetId */; const sheetZone = this.getters.getSheetZone(sheetId); - return isInside(col, row, sheetZone) ? 0 /* Success */ : 16 /* TargetOutOfSheet */; + return isInside(col, row, sheetZone) ? 0 /* CommandResult.Success */ : 16 /* CommandResult.TargetOutOfSheet */; } } CellPlugin.getters = [ @@ -24834,7 +25088,7 @@ case "UPDATE_CHART": return this.validateChartDefinition(cmd.definition); default: - return 0 /* Success */; + return 0 /* CommandResult.Success */; } } handle(cmd) { @@ -25058,7 +25312,7 @@ case "MOVE_CONDITIONAL_FORMAT": return this.checkValidReordering(cmd.cfId, cmd.direction, cmd.sheetId); } - return 0 /* Success */; + return 0 /* CommandResult.Success */; } handle(cmd) { switch (cmd.type) { @@ -25190,18 +25444,18 @@ } checkValidReordering(cfId, direction, sheetId) { if (!this.cfRules[sheetId]) - return 25 /* InvalidSheetId */; + return 25 /* CommandResult.InvalidSheetId */; const ruleIndex = this.cfRules[sheetId].findIndex((cf) => cf.id === cfId); if (ruleIndex === -1) - return 69 /* InvalidConditionalFormatId */; + return 68 /* CommandResult.InvalidConditionalFormatId */; const cfIndex2 = direction === "up" ? ruleIndex - 1 : ruleIndex + 1; if (cfIndex2 < 0 || cfIndex2 >= this.cfRules[sheetId].length) { - return 69 /* InvalidConditionalFormatId */; + return 68 /* CommandResult.InvalidConditionalFormatId */; } - return 0 /* Success */; + return 0 /* CommandResult.Success */; } checkEmptyRange(cmd) { - return cmd.ranges.length ? 0 /* Success */ : 22 /* EmptyRange */; + return cmd.ranges.length ? 0 /* CommandResult.Success */ : 22 /* CommandResult.EmptyRange */; } checkCFRule(cmd) { const rule = cmd.cf.rule; @@ -25226,7 +25480,7 @@ return this.checkValidations(rule, this.chainValidations(this.checkInflectionPoints(this.checkNaN), this.checkLowerBiggerThanUpper), this.chainValidations(this.checkInflectionPoints(this.checkFormulaCompilation))); } } - return 0 /* Success */; + return 0 /* CommandResult.Success */; } checkOperatorArgsNumber(expectedNumber, operators) { if (expectedNumber > 2) { @@ -25237,14 +25491,14 @@ const errors = []; const isEmpty = (value) => value === undefined || value === ""; if (expectedNumber >= 1 && isEmpty(rule.values[0])) { - errors.push(48 /* FirstArgMissing */); + errors.push(48 /* CommandResult.FirstArgMissing */); } if (expectedNumber >= 2 && isEmpty(rule.values[1])) { - errors.push(49 /* SecondArgMissing */); + errors.push(49 /* CommandResult.SecondArgMissing */); } - return errors.length ? errors : 0 /* Success */; + return errors.length ? errors : 0 /* CommandResult.Success */; } - return 0 /* Success */; + return 0 /* CommandResult.Success */; }; } checkNaN(threshold, thresholdName) { @@ -25252,43 +25506,43 @@ (threshold.value === "" || isNaN(threshold.value))) { switch (thresholdName) { case "min": - return 50 /* MinNaN */; + return 50 /* CommandResult.MinNaN */; case "max": - return 52 /* MaxNaN */; + return 52 /* CommandResult.MaxNaN */; case "mid": - return 51 /* MidNaN */; + return 51 /* CommandResult.MidNaN */; case "upperInflectionPoint": - return 53 /* ValueUpperInflectionNaN */; + return 53 /* CommandResult.ValueUpperInflectionNaN */; case "lowerInflectionPoint": - return 54 /* ValueLowerInflectionNaN */; + return 54 /* CommandResult.ValueLowerInflectionNaN */; } } - return 0 /* Success */; + return 0 /* CommandResult.Success */; } checkFormulaCompilation(threshold, thresholdName) { if (threshold.type !== "formula") - return 0 /* Success */; + return 0 /* CommandResult.Success */; try { compile(threshold.value || ""); } catch (error) { switch (thresholdName) { case "min": - return 55 /* MinInvalidFormula */; + return 55 /* CommandResult.MinInvalidFormula */; case "max": - return 57 /* MaxInvalidFormula */; + return 57 /* CommandResult.MaxInvalidFormula */; case "mid": - return 56 /* MidInvalidFormula */; + return 56 /* CommandResult.MidInvalidFormula */; case "upperInflectionPoint": - return 58 /* ValueUpperInvalidFormula */; + return 58 /* CommandResult.ValueUpperInvalidFormula */; case "lowerInflectionPoint": - return 59 /* ValueLowerInvalidFormula */; + return 59 /* CommandResult.ValueLowerInvalidFormula */; } } - return 0 /* Success */; + return 0 /* CommandResult.Success */; } checkThresholds(check) { - return this.batchValidations((rule) => check(rule.minimum, "min"), (rule) => check(rule.maximum, "max"), (rule) => (rule.midpoint ? check(rule.midpoint, "mid") : 0 /* Success */)); + return this.batchValidations((rule) => check(rule.minimum, "min"), (rule) => check(rule.maximum, "max"), (rule) => (rule.midpoint ? check(rule.midpoint, "mid") : 0 /* CommandResult.Success */)); } checkInflectionPoints(check) { return this.batchValidations((rule) => check(rule.lowerInflectionPoint, "lowerInflectionPoint"), (rule) => check(rule.upperInflectionPoint, "upperInflectionPoint")); @@ -25299,9 +25553,9 @@ if (["number", "percentage", "percentile"].includes(rule.lowerInflectionPoint.type) && rule.lowerInflectionPoint.type === rule.upperInflectionPoint.type && Number(minValue) > Number(maxValue)) { - return 45 /* LowerBiggerThanUpper */; + return 45 /* CommandResult.LowerBiggerThanUpper */; } - return 0 /* Success */; + return 0 /* CommandResult.Success */; } checkMinBiggerThanMax(rule) { const minValue = rule.minimum.value; @@ -25309,9 +25563,9 @@ if (["number", "percentage", "percentile"].includes(rule.minimum.type) && rule.minimum.type === rule.maximum.type && stringToNumber(minValue) >= stringToNumber(maxValue)) { - return 44 /* MinBiggerThanMax */; + return 44 /* CommandResult.MinBiggerThanMax */; } - return 0 /* Success */; + return 0 /* CommandResult.Success */; } checkMidBiggerThanMax(rule) { var _a; @@ -25321,9 +25575,9 @@ ["number", "percentage", "percentile"].includes(rule.midpoint.type) && rule.midpoint.type === rule.maximum.type && stringToNumber(midValue) >= stringToNumber(maxValue)) { - return 46 /* MidBiggerThanMax */; + return 46 /* CommandResult.MidBiggerThanMax */; } - return 0 /* Success */; + return 0 /* CommandResult.Success */; } checkMinBiggerThanMid(rule) { var _a; @@ -25333,9 +25587,9 @@ ["number", "percentage", "percentile"].includes(rule.midpoint.type) && rule.minimum.type === rule.midpoint.type && stringToNumber(minValue) >= stringToNumber(midValue)) { - return 47 /* MinBiggerThanMid */; + return 47 /* CommandResult.MinBiggerThanMid */; } - return 0 /* Success */; + return 0 /* CommandResult.Success */; } removeConditionalFormatting(id, sheet) { const cfIndex = this.cfRules[sheet].findIndex((s) => s.id === id); @@ -25375,7 +25629,7 @@ case "DELETE_FIGURE": return this.checkFigureExists(cmd.sheetId, cmd.id); default: - return 0 /* Success */; + return 0 /* CommandResult.Success */; } } handle(cmd) { @@ -25432,9 +25686,9 @@ checkFigureExists(sheetId, figureId) { var _a; if (((_a = this.figures[sheetId]) === null || _a === void 0 ? void 0 : _a[figureId]) === undefined) { - return 68 /* FigureDoesNotExist */; + return 67 /* CommandResult.FigureDoesNotExist */; } - return 0 /* Success */; + return 0 /* CommandResult.Success */; } // --------------------------------------------------------------------------- // Getters @@ -25719,7 +25973,7 @@ switch (cmd.type) { case "HIDE_COLUMNS_ROWS": { if (!this.hiddenHeaders[cmd.sheetId]) { - return 25 /* InvalidSheetId */; + return 25 /* CommandResult.InvalidSheetId */; } const hiddenGroup = cmd.dimension === "COL" ? this.getHiddenColsGroups(cmd.sheetId) @@ -25728,11 +25982,11 @@ ? this.getters.getNumberCols(cmd.sheetId) : this.getters.getNumberRows(cmd.sheetId); return (hiddenGroup || []).flat().concat(cmd.elements).length < elements - ? 0 /* Success */ - : 63 /* TooManyHiddenElements */; + ? 0 /* CommandResult.Success */ + : 63 /* CommandResult.TooManyHiddenElements */; } } - return 0 /* Success */; + return 0 /* CommandResult.Success */; } handle(cmd) { switch (cmd.type) { @@ -25936,7 +26190,7 @@ case "UPDATE_CELL": return this.checkMergedContentUpdate(cmd); default: - return 0 /* Success */; + return 0 /* CommandResult.Success */; } } handle(cmd) { @@ -26143,32 +26397,32 @@ checkDestructiveMerge({ sheetId, target }) { const sheet = this.getters.tryGetSheet(sheetId); if (!sheet) - return 0 /* Success */; + return 0 /* CommandResult.Success */; const isDestructive = target.some((zone) => this.isMergeDestructive(sheetId, zone)); - return isDestructive ? 3 /* MergeIsDestructive */ : 0 /* Success */; + return isDestructive ? 3 /* CommandResult.MergeIsDestructive */ : 0 /* CommandResult.Success */; } checkOverlap({ target }) { for (const zone of target) { for (const zone2 of target) { if (zone !== zone2 && overlap(zone, zone2)) { - return 62 /* MergeOverlap */; + return 62 /* CommandResult.MergeOverlap */; } } } - return 0 /* Success */; + return 0 /* CommandResult.Success */; } checkFrozenPanes({ sheetId, target }) { const sheet = this.getters.tryGetSheet(sheetId); if (!sheet) - return 0 /* Success */; + return 0 /* CommandResult.Success */; const { xSplit, ySplit } = this.getters.getPaneDivisions(sheetId); for (const zone of target) { if ((zone.left < xSplit && zone.right >= xSplit) || (zone.top < ySplit && zone.bottom >= ySplit)) { - return 73 /* FrozenPaneOverlap */; + return 72 /* CommandResult.FrozenPaneOverlap */; } } - return 0 /* Success */; + return 0 /* CommandResult.Success */; } /** * The content of a merged cell should always be empty. @@ -26177,13 +26431,13 @@ checkMergedContentUpdate(cmd) { const { col, row, sheetId, content } = cmd; if (content === undefined) { - return 0 /* Success */; + return 0 /* CommandResult.Success */; } const { col: mainCol, row: mainRow } = this.getMainCellPosition(sheetId, col, row); if (mainCol === col && mainRow === row) { - return 0 /* Success */; + return 0 /* CommandResult.Success */; } - return 4 /* CellIsMerged */; + return 4 /* CommandResult.CellIsMerged */; } /** * Merge the current selection. Note that: @@ -26359,15 +26613,15 @@ // --------------------------------------------------------------------------- allowDispatch(cmd) { const genericChecks = this.chainValidations(this.checkSheetExists, this.checkZones)(cmd); - if (genericChecks !== 0 /* Success */) { + if (genericChecks !== 0 /* CommandResult.Success */) { return genericChecks; } switch (cmd.type) { case "HIDE_SHEET": { if (this.getVisibleSheetIds().length === 1) { - return 8 /* NotEnoughSheets */; + return 8 /* CommandResult.NotEnoughSheets */; } - return 0 /* Success */; + return 0 /* CommandResult.Success */; } case "CREATE_SHEET": { return this.checkValidations(cmd, this.checkSheetName, this.checkSheetPosition); @@ -26379,43 +26633,43 @@ .slice(0, currentIndex) .map((id) => !this.isSheetVisible(id)); return leftSheets.every((isHidden) => isHidden) - ? 12 /* WrongSheetMove */ - : 0 /* Success */; + ? 12 /* CommandResult.WrongSheetMove */ + : 0 /* CommandResult.Success */; } else { const rightSheets = this.orderedSheetIds .slice(currentIndex + 1) .map((id) => !this.isSheetVisible(id)); return rightSheets.every((isHidden) => isHidden) - ? 12 /* WrongSheetMove */ - : 0 /* Success */; + ? 12 /* CommandResult.WrongSheetMove */ + : 0 /* CommandResult.Success */; } case "RENAME_SHEET": return this.isRenameAllowed(cmd); case "DELETE_SHEET": return this.orderedSheetIds.length > 1 - ? 0 /* Success */ - : 8 /* NotEnoughSheets */; + ? 0 /* CommandResult.Success */ + : 8 /* CommandResult.NotEnoughSheets */; case "REMOVE_COLUMNS_ROWS": { const length = cmd.dimension === "COL" ? this.getNumberCols(cmd.sheetId) : this.getNumberRows(cmd.sheetId); return length > cmd.elements.length - ? 0 /* Success */ - : 7 /* NotEnoughElements */; + ? 0 /* CommandResult.Success */ + : 7 /* CommandResult.NotEnoughElements */; } case "FREEZE_ROWS": { return cmd.quantity >= 1 && cmd.quantity < this.getNumberRows(cmd.sheetId) - ? 0 /* Success */ - : 72 /* InvalidFreezeQuantity */; + ? 0 /* CommandResult.Success */ + : 71 /* CommandResult.InvalidFreezeQuantity */; } case "FREEZE_COLUMNS": { return cmd.quantity >= 1 && cmd.quantity < this.getNumberCols(cmd.sheetId) - ? 0 /* Success */ - : 72 /* InvalidFreezeQuantity */; + ? 0 /* CommandResult.Success */ + : 71 /* CommandResult.InvalidFreezeQuantity */; } default: - return 0 /* Success */; + return 0 /* CommandResult.Success */; } } handle(cmd) { @@ -26824,24 +27078,24 @@ const { orderedSheetIds, sheets } = this; const name = cmd.name && cmd.name.trim().toLowerCase(); if (orderedSheetIds.find((id) => { var _a; return ((_a = sheets[id]) === null || _a === void 0 ? void 0 : _a.name.toLowerCase()) === name; })) { - return 10 /* DuplicatedSheetName */; + return 10 /* CommandResult.DuplicatedSheetName */; } if (FORBIDDEN_IN_EXCEL_REGEX.test(name)) { - return 11 /* ForbiddenCharactersInSheetName */; + return 11 /* CommandResult.ForbiddenCharactersInSheetName */; } - return 0 /* Success */; + return 0 /* CommandResult.Success */; } checkSheetPosition(cmd) { const { orderedSheetIds } = this; if (cmd.position > orderedSheetIds.length || cmd.position < 0) { - return 13 /* WrongSheetPosition */; + return 13 /* CommandResult.WrongSheetPosition */; } - return 0 /* Success */; + return 0 /* CommandResult.Success */; } isRenameAllowed(cmd) { const name = cmd.name && cmd.name.trim().toLowerCase(); if (!name) { - return 9 /* MissingSheetName */; + return 9 /* CommandResult.MissingSheetName */; } return this.checkSheetName(cmd); } @@ -27147,9 +27401,9 @@ */ checkSheetExists(cmd) { if (cmd.type !== "CREATE_SHEET" && "sheetId" in cmd && this.sheets[cmd.sheetId] === undefined) { - return 25 /* InvalidSheetId */; + return 25 /* CommandResult.InvalidSheetId */; } - return 0 /* Success */; + return 0 /* CommandResult.Success */; } /** * Check if zones in the command are well formed and @@ -27167,15 +27421,15 @@ zones.push(...cmd.ranges.map((rangeData) => this.getters.getRangeFromRangeData(rangeData).zone)); } if (!zones.every(isZoneValid)) { - return 23 /* InvalidRange */; + return 23 /* CommandResult.InvalidRange */; } else if (zones.length && "sheetId" in cmd) { const sheetZone = this.getSheetZone(cmd.sheetId); return zones.every((zone) => isZoneInside(zone, sheetZone)) - ? 0 /* Success */ - : 16 /* TargetOutOfSheet */; + ? 0 /* CommandResult.Success */ + : 16 /* CommandResult.TargetOutOfSheet */; } - return 0 /* Success */; + return 0 /* CommandResult.Success */; } } SheetPlugin.getters = [ @@ -27282,16 +27536,16 @@ ? this.lastCellSelected.row : clip(cmd.row, 0, this.getters.getNumberRows(sheetId)); if (this.lastCellSelected.col !== undefined && this.lastCellSelected.row !== undefined) { - return 0 /* Success */; + return 0 /* CommandResult.Success */; } - return 42 /* InvalidAutofillSelection */; + return 42 /* CommandResult.InvalidAutofillSelection */; case "AUTOFILL_AUTO": const zone = this.getters.getSelectedZone(); return zone.top === zone.bottom - ? 0 /* Success */ - : 1 /* CancelledForUnknownReason */; + ? 0 /* CommandResult.Success */ + : 1 /* CommandResult.CancelledForUnknownReason */; } - return 0 /* Success */; + return 0 /* CommandResult.Success */; } handle(cmd) { switch (cmd.type) { @@ -27345,7 +27599,7 @@ const source = this.getters.getSelectedZone(); const target = this.autofillZone; switch (this.direction) { - case 1 /* DOWN */: + case 1 /* DIRECTION.DOWN */: for (let col = source.left; col <= source.right; col++) { const xcs = []; for (let row = source.top; row <= source.bottom; row++) { @@ -27357,7 +27611,7 @@ } } break; - case 0 /* UP */: + case 0 /* DIRECTION.UP */: for (let col = source.left; col <= source.right; col++) { const xcs = []; for (let row = source.bottom; row >= source.top; row--) { @@ -27369,7 +27623,7 @@ } } break; - case 2 /* LEFT */: + case 2 /* DIRECTION.LEFT */: for (let row = source.top; row <= source.bottom; row++) { const xcs = []; for (let col = source.right; col >= source.left; col--) { @@ -27381,7 +27635,7 @@ } } break; - case 3 /* RIGHT */: + case 3 /* DIRECTION.RIGHT */: for (let row = source.top; row <= source.bottom; row++) { const xcs = []; for (let col = source.left; col <= source.right; col++) { @@ -27414,16 +27668,16 @@ } this.direction = this.getDirection(col, row); switch (this.direction) { - case 0 /* UP */: + case 0 /* DIRECTION.UP */: this.saveZone(row, source.top - 1, source.left, source.right); break; - case 1 /* DOWN */: + case 1 /* DIRECTION.DOWN */: this.saveZone(source.bottom + 1, row, source.left, source.right); break; - case 2 /* LEFT */: + case 2 /* DIRECTION.LEFT */: this.saveZone(source.top, source.bottom, col, source.left - 1); break; - case 3 /* RIGHT */: + case 3 /* DIRECTION.RIGHT */: this.saveZone(source.top, source.bottom, source.right + 1, col); break; } @@ -27531,10 +27785,10 @@ getDirection(col, row) { const source = this.getters.getSelectedZone(); const position = { - up: { number: source.top - row, value: 0 /* UP */ }, - down: { number: row - source.bottom, value: 1 /* DOWN */ }, - left: { number: source.left - col, value: 2 /* LEFT */ }, - right: { number: col - source.right, value: 3 /* RIGHT */ }, + up: { number: source.top - row, value: 0 /* DIRECTION.UP */ }, + down: { number: row - source.bottom, value: 1 /* DIRECTION.DOWN */ }, + left: { number: source.left - col, value: 2 /* DIRECTION.LEFT */ }, + right: { number: col - source.right, value: 3 /* DIRECTION.RIGHT */ }, }; if (Object.values(position) .map((x) => (x.number > 0 ? 1 : 0)) @@ -27592,7 +27846,7 @@ } } } - AutofillPlugin.layers = [6 /* Autofill */]; + AutofillPlugin.layers = [6 /* LAYERS.Autofill */]; AutofillPlugin.getters = ["getAutofillTooltip"]; class AutomaticSumPlugin extends UIPlugin { @@ -27879,11 +28133,11 @@ cellPopoverRegistry.get(cmd.popoverType); } catch (error) { - return 70 /* InvalidCellPopover */; + return 69 /* CommandResult.InvalidCellPopover */; } - return 0 /* Success */; + return 0 /* CommandResult.Success */; default: - return 0 /* Success */; + return 0 /* CommandResult.Success */; } } handle(cmd) { @@ -27973,10 +28227,10 @@ this.sheetId = getters.getActiveSheetId(); } isCutAllowed(target) { - return 0 /* Success */; + return 0 /* CommandResult.Success */; } isPasteAllowed(target, clipboardOption) { - return 0 /* Success */; + return 0 /* CommandResult.Success */; } /** * Add columns and/or rows to ensure that col + width and row + height are still @@ -28051,21 +28305,21 @@ } isCutAllowed(target) { if (target.length !== 1) { - return 17 /* WrongCutSelection */; + return 17 /* CommandResult.WrongCutSelection */; } - return 0 /* Success */; + return 0 /* CommandResult.Success */; } isPasteAllowed(target, clipboardOption) { const sheetId = this.getters.getActiveSheetId(); if (this.operation === "CUT" && (clipboardOption === null || clipboardOption === void 0 ? void 0 : clipboardOption.pasteOption) !== undefined) { // cannot paste only format or only value if the previous operation is a CUT - return 19 /* WrongPasteOption */; + return 19 /* CommandResult.WrongPasteOption */; } if (target.length > 1) { // cannot paste if we have a clipped zone larger than a cell and multiple // zones selected if (this.cells.length > 1 || this.cells[0].length > 1) { - return 18 /* WrongPasteSelection */; + return 18 /* CommandResult.WrongPasteSelection */; } } const clipboardHeight = this.cells.length; @@ -28075,7 +28329,7 @@ if (target.length > 1 || !this.getters.isSingleCellOrMerge(sheetId, target[0]) || clipboardHeight * clipboardWidth !== 1) { - return 2 /* WillRemoveExistingMerge */; + return 2 /* CommandResult.WillRemoveExistingMerge */; } } } @@ -28083,10 +28337,10 @@ for (const zone of this.getPasteZones(target)) { if ((zone.left < xSplit && zone.right >= xSplit) || (zone.top < ySplit && zone.bottom >= ySplit)) { - return 73 /* FrozenPaneOverlap */; + return 72 /* CommandResult.FrozenPaneOverlap */; } } - return 0 /* Success */; + return 0 /* CommandResult.Success */; } /** * Paste the clipboard content in the given target @@ -28407,16 +28661,16 @@ this.operation = operation; } isCutAllowed(target) { - return 0 /* Success */; + return 0 /* CommandResult.Success */; } isPasteAllowed(target, option) { if (target.length === 0) { - return 71 /* EmptyTarget */; + return 70 /* CommandResult.EmptyTarget */; } if ((option === null || option === void 0 ? void 0 : option.pasteOption) !== undefined) { - return 20 /* WrongFigurePasteOption */; + return 20 /* CommandResult.WrongFigurePasteOption */; } - return 0 /* Success */; + return 0 /* CommandResult.Success */; } /** * Paste the clipboard content in the given target @@ -28442,6 +28696,7 @@ id: this.copiedFigure.id, }); } + this.dispatch("SELECT_FIGURE", { id: newId }); } getClipboardContent() { return "\t"; @@ -28465,9 +28720,9 @@ const sheetId = this.getters.getActiveSheetId(); const pasteZone = this.getPasteZone(target); if (this.getters.doesIntersectMerge(sheetId, pasteZone)) { - return 2 /* WillRemoveExistingMerge */; + return 2 /* CommandResult.WillRemoveExistingMerge */; } - return 0 /* Success */; + return 0 /* CommandResult.Success */; } paste(target) { const values = this.values; @@ -28533,7 +28788,7 @@ return state.isCutAllowed(zones); case "PASTE": if (!this.state) { - return 21 /* EmptyClipboard */; + return 21 /* CommandResult.EmptyClipboard */; } const pasteOption = cmd.pasteOption || (this._isPaintingFormat ? "onlyFormat" : undefined); return this.state.isPasteAllowed(cmd.target, { pasteOption }); @@ -28552,7 +28807,7 @@ return state.isPasteAllowed(paste); } } - return 0 /* Success */; + return 0 /* CommandResult.Success */; } handle(cmd) { var _a, _b; @@ -28726,7 +28981,7 @@ this.state.drawClipboard(renderingContext); } } - ClipboardPlugin.layers = [2 /* Clipboard */]; + ClipboardPlugin.layers = [2 /* LAYERS.Clipboard */]; ClipboardPlugin.getters = ["getClipboardContent", "isCutOperation", "isPaintingFormat"]; /** @@ -29794,7 +30049,7 @@ } } } - FindAndReplacePlugin.layers = [3 /* Search */]; + FindAndReplacePlugin.layers = [3 /* LAYERS.Search */]; FindAndReplacePlugin.getters = ["getSearchMatches", "getCurrentSelectedMatchIndex"]; class FormatPlugin extends UIPlugin { @@ -29920,7 +30175,7 @@ } } } - HighlightPlugin.layers = [1 /* Highlights */]; + HighlightPlugin.layers = [1 /* LAYERS.Highlights */]; HighlightPlugin.getters = ["getHighlights"]; class RendererPlugin extends UIPlugin { @@ -29977,7 +30232,7 @@ // --------------------------------------------------------------------------- drawGrid(renderingContext, layer) { switch (layer) { - case 0 /* Background */: + case 0 /* LAYERS.Background */: this.boxes = this.getGridBoxes(); this.drawBackground(renderingContext); this.drawCellBackground(renderingContext); @@ -29986,7 +30241,7 @@ this.drawIcon(renderingContext); this.drawFrozenPanes(renderingContext); break; - case 7 /* Headers */: + case 7 /* LAYERS.Headers */: if (!this.getters.isDashboard()) { this.drawHeaders(renderingContext); this.drawFrozenPanesHeaders(renderingContext); @@ -30475,7 +30730,7 @@ return boxes; } } - RendererPlugin.layers = [0 /* Background */, 7 /* Headers */]; + RendererPlugin.layers = [0 /* LAYERS.Background */, 7 /* LAYERS.Headers */]; RendererPlugin.getters = ["getColDimensionsInViewport", "getRowDimensionsInViewport"]; const selectionStatisticFunctions = [ @@ -30541,12 +30796,12 @@ break; } catch (error) { - return 25 /* InvalidSheetId */; + return 25 /* CommandResult.InvalidSheetId */; } case "MOVE_COLUMNS_ROWS": return this.isMoveElementAllowed(cmd); } - return 0 /* Success */; + return 0 /* CommandResult.Success */; } handleEvent(event) { const anchor = event.anchor; @@ -30986,9 +31241,9 @@ if (doesElementsHaveCommonMerges(id, start - 1, start) || doesElementsHaveCommonMerges(id, end, end + 1) || doesElementsHaveCommonMerges(id, cmd.base - 1, cmd.base)) { - return 2 /* WillRemoveExistingMerge */; + return 2 /* CommandResult.WillRemoveExistingMerge */; } - return 0 /* Success */; + return 0 /* CommandResult.Success */; } //------------------------------------------- // Helpers for extensions @@ -31079,7 +31334,7 @@ } } } - GridSelectionPlugin.layers = [5 /* Selection */]; + GridSelectionPlugin.layers = [5 /* LAYERS.Selection */]; GridSelectionPlugin.getters = [ "getActiveSheet", "getActiveSheetId", @@ -31128,11 +31383,11 @@ switch (cmd.type) { case "ADD_EMPTY_RANGE": if (this.inputHasSingleRange && this.ranges.length === 1) { - return 27 /* MaximumRangesReached */; + return 27 /* CommandResult.MaximumRangesReached */; } break; } - return 0 /* Success */; + return 0 /* CommandResult.Success */; } handleEvent(event) { const xc = zoneToXc(event.anchor.zone); @@ -31317,7 +31572,7 @@ return index >= 0 ? index : null; } } - SelectionInputPlugin.layers = [1 /* Highlights */]; + SelectionInputPlugin.layers = [1 /* LAYERS.Highlights */]; SelectionInputPlugin.getters = []; /** @@ -31347,14 +31602,14 @@ case "FOCUS_RANGE": const index = (_a = this.currentInput) === null || _a === void 0 ? void 0 : _a.getIndex(cmd.rangeId); if (this.focusedInputId === cmd.id && ((_b = this.currentInput) === null || _b === void 0 ? void 0 : _b.focusedRangeIndex) === index) { - return 26 /* InputAlreadyFocused */; + return 26 /* CommandResult.InputAlreadyFocused */; } break; } if (this.currentInput) { return this.currentInput.allowDispatch(cmd); } - return 0 /* Success */; + return 0 /* CommandResult.Success */; } handle(cmd) { var _a; @@ -31444,7 +31699,7 @@ this.focusedInputId = null; } } - SelectionInputsManagerPlugin.layers = [1 /* Highlights */]; + SelectionInputsManagerPlugin.layers = [1 /* LAYERS.Highlights */]; SelectionInputsManagerPlugin.getters = [ "getSelectionInput", "getSelectionInputValue", @@ -31957,7 +32212,7 @@ } } SelectionMultiUserPlugin.getters = ["getClientsToDisplay"]; - SelectionMultiUserPlugin.layers = [5 /* Selection */]; + SelectionMultiUserPlugin.layers = [5 /* LAYERS.Selection */]; class InternalViewport { constructor(getters, sheetId, boundaries, sizeInGrid, options, offsets) { @@ -32281,34 +32536,31 @@ allowDispatch(cmd) { switch (cmd.type) { case "SET_VIEWPORT_OFFSET": - return this.checkValidations(cmd, this.checkOffsetValidity, this.checkScrollingDirection); + return this.checkScrollingDirection(cmd); case "RESIZE_SHEETVIEW": - if (cmd.width < 0 || cmd.height < 0) { - return 66 /* InvalidViewportSize */; - } - return 0 /* Success */; + return this.chainValidations(this.checkValuesAreDifferent, this.checkPositiveDimension)(cmd); case "FREEZE_COLUMNS": { const sheetId = this.getters.getActiveSheetId(); const merges = this.getters.getMerges(sheetId); for (let merge of merges) { if (merge.left < cmd.quantity && cmd.quantity <= merge.right) { - return 62 /* MergeOverlap */; + return 62 /* CommandResult.MergeOverlap */; } } - return 0 /* Success */; + return 0 /* CommandResult.Success */; } case "FREEZE_ROWS": { const sheetId = this.getters.getActiveSheetId(); const merges = this.getters.getMerges(sheetId); for (let merge of merges) { if (merge.top < cmd.quantity && cmd.quantity <= merge.bottom) { - return 62 /* MergeOverlap */; + return 62 /* CommandResult.MergeOverlap */; } } - return 0 /* Success */; + return 0 /* CommandResult.Success */; } default: - return 0 /* Success */; + return 0 /* CommandResult.Success */; } } handleEvent(event) { @@ -32607,20 +32859,29 @@ this.ensureMainViewportExist(sheetId); return Object.values(this.viewports[sheetId]).filter(isDefined$1); } + checkPositiveDimension(cmd) { + if (cmd.width < 0 || cmd.height < 0) { + return 65 /* CommandResult.InvalidViewportSize */; + } + return 0 /* CommandResult.Success */; + } + checkValuesAreDifferent(cmd) { + const { height, width } = this.getSheetViewDimension(); + if (cmd.gridOffsetX === this.gridOffsetX && + cmd.gridOffsetY === this.gridOffsetY && + cmd.width === width && + cmd.height === height) { + return 73 /* CommandResult.ValuesNotChanged */; + } + return 0 /* CommandResult.Success */; + } checkScrollingDirection({ offsetX, offsetY, }) { const pane = this.getMainInternalViewport(this.getters.getActiveSheetId()); if ((!pane.canScrollHorizontally && offsetX > 0) || (!pane.canScrollVertically && offsetY > 0)) { - return 67 /* InvalidScrollingDirection */; + return 66 /* CommandResult.InvalidScrollingDirection */; } - return 0 /* Success */; - } - checkOffsetValidity({ offsetX, offsetY, }) { - const { maxOffsetX, maxOffsetY } = this.getMaximumSheetOffset(); - if (offsetX < 0 || offsetY < 0 || offsetY > maxOffsetY || offsetX > maxOffsetX) { - return 65 /* InvalidOffset */; - } - return 0 /* Success */; + return 0 /* CommandResult.Success */; } getMainViewport(sheetId) { const viewport = this.getMainInternalViewport(sheetId); @@ -32677,7 +32938,8 @@ } setSheetViewOffset(offsetX, offsetY) { const sheetId = this.getters.getActiveSheetId(); - Object.values(this.getSubViewports(sheetId)).forEach((viewport) => viewport.setViewportOffset(offsetX, offsetY)); + const { maxOffsetX, maxOffsetY } = this.getMaximumSheetOffset(); + Object.values(this.getSubViewports(sheetId)).forEach((viewport) => viewport.setViewportOffset(clip(offsetX, 0, maxOffsetX), clip(offsetY, 0, maxOffsetY))); } /** * Clip the vertical offset within the allowed range. @@ -32788,7 +33050,6 @@ "getSheetViewDimension", "getSheetViewDimensionWithHeaders", "getMainViewportRect", - "getMaximumSheetOffset", "isVisibleInViewport", "getEdgeScrollCol", "getEdgeScrollRow", @@ -32811,7 +33072,7 @@ } return this.checkValidations(cmd, this.checkMerge, this.checkMergeSizes); } - return 0 /* Success */; + return 0 /* CommandResult.Success */; } handle(cmd) { switch (cmd.type) { @@ -32822,21 +33083,21 @@ } checkMerge({ sheetId, zone }) { if (!this.getters.doesIntersectMerge(sheetId, zone)) { - return 0 /* Success */; + return 0 /* CommandResult.Success */; } /*Test the presence of single cells*/ for (let row = zone.top; row <= zone.bottom; row++) { for (let col = zone.left; col <= zone.right; col++) { if (!this.getters.isInMerge(sheetId, col, row)) { - return 60 /* InvalidSortZone */; + return 60 /* CommandResult.InvalidSortZone */; } } } - return 0 /* Success */; + return 0 /* CommandResult.Success */; } checkMergeSizes({ sheetId, zone }) { if (!this.getters.doesIntersectMerge(sheetId, zone)) { - return 0 /* Success */; + return 0 /* CommandResult.Success */; } const merges = this.getters.getMerges(sheetId).filter((merge) => overlap(merge, zone)); /*Test the presence of merges of different sizes*/ @@ -32849,9 +33110,9 @@ ]; return widthCurrent === widthFirst && heightCurrent === heightFirst; })) { - return 60 /* InvalidSortZone */; + return 60 /* CommandResult.InvalidSortZone */; } - return 0 /* Success */; + return 0 /* CommandResult.Success */; } // getContiguousZone helpers /** @@ -33134,10 +33395,10 @@ break; } catch (error) { - return 25 /* InvalidSheetId */; + return 25 /* CommandResult.InvalidSheetId */; } } - return 0 /* Success */; + return 0 /* CommandResult.Success */; } handle(cmd) { switch (cmd.type) { @@ -33365,6 +33626,15 @@ .add("selection_multiuser", SelectionMultiUserPlugin) .add("custom_colors", CustomColorsPlugin); + const clickableCellRegistry = new Registry(); + clickableCellRegistry.add("link", { + condition: (cell) => cell.isLink(), + action: (cell, env) => { + cell.action(env); + }, + sequence: 5, + }); + // ----------------------------------------------------------------------------- // SpreadSheet // ----------------------------------------------------------------------------- @@ -33580,6 +33850,133 @@ BottomBar.template = "o-spreadsheet-BottomBar"; BottomBar.components = { Menu }; + css /* scss */ ` + .o-dashboard-clickable-cell { + position: absolute; + cursor: pointer; + } +`; + class SpreadsheetDashboard extends owl.Component { + setup() { + const gridRef = owl.useRef("grid"); + this.canvasPosition = useAbsolutePosition(gridRef); + this.hoveredCell = owl.useState({ col: undefined, row: undefined }); + useGridDrawing("canvas", this.env.model, () => this.env.model.getters.getSheetViewDimension()); + this.onMouseWheel = useWheelHandler((deltaX, deltaY) => { + this.moveCanvas(deltaX, deltaY); + this.hoveredCell.col = undefined; + this.hoveredCell.row = undefined; + }); + } + onCellHovered({ col, row }) { + this.hoveredCell.col = col; + this.hoveredCell.row = row; + } + get gridContainer() { + const sheetId = this.env.model.getters.getActiveSheetId(); + const { right } = this.env.model.getters.getSheetZone(sheetId); + const { end } = this.env.model.getters.getColDimensions(sheetId, right); + return ` + max-width: ${end}px; + `; + } + get gridOverlayDimensions() { + return ` + height: 100%; + width: 100% + `; + } + getCellClickableStyle(coordinates) { + return ` + top: ${coordinates.y}px; + left: ${coordinates.x}px; + width: ${coordinates.width}px; + height: ${coordinates.height}px; + `; + } + /** + * Get all the boxes for the cell in the sheet view that are clickable. + * This function is used to render an overlay over each clickable cell in + * order to display a pointer cursor. + * + */ + getClickableCells() { + const cells = []; + const sheetId = this.env.model.getters.getActiveSheetId(); + for (const col of this.env.model.getters.getSheetViewVisibleCols()) { + for (const row of this.env.model.getters.getSheetViewVisibleRows()) { + const cell = this.env.model.getters.getCell(sheetId, col, row); + if (cell) { + const action = this.getClickableAction(cell); + if (!action) { + continue; + } + let zone; + if (this.env.model.getters.isInMerge(sheetId, col, row)) { + zone = this.env.model.getters.getMerge(sheetId, col, row); + } + else { + zone = positionToZone({ col, row }); + } + const rect = this.env.model.getters.getVisibleRect(zone); + cells.push({ + coordinates: rect, + cell, + action, + }); + } + } + } + return cells; + } + getClickableAction(cell) { + for (const items of clickableCellRegistry.getAll().sort((a, b) => a.sequence - b.sequence)) { + if (items.condition(cell, this.env)) { + return items.action; + } + } + return false; + } + selectClickableCell(clickableCell) { + const { cell, action } = clickableCell; + action(cell, this.env); + } + onClosePopover() { + this.env.model.dispatch("CLOSE_CELL_POPOVER"); + } + onGridResized({ height, width }) { + this.env.model.dispatch("RESIZE_SHEETVIEW", { + width: width, + height: height, + gridOffsetX: 0, + gridOffsetY: 0, + }); + } + moveCanvas(deltaX, deltaY) { + const { offsetScrollbarX, offsetScrollbarY } = this.env.model.getters.getActiveSheetScrollInfo(); + this.env.model.dispatch("SET_VIEWPORT_OFFSET", { + offsetX: offsetScrollbarX + deltaX, + offsetY: offsetScrollbarY + deltaY, + }); + } + copy(ev) { + this.env.model.dispatch("COPY"); + const content = this.env.model.getters.getClipboardContent(); + // TODO use env.clipboard + // TODO add a test + ev.clipboardData.setData("text/plain", content); + ev.preventDefault(); + } + } + SpreadsheetDashboard.template = "o-spreadsheet-SpreadsheetDashboard"; + SpreadsheetDashboard.components = { + GridOverlay, + GridPopover, + Popover, + VerticalScrollBar, + HorizontalScrollBar, + }; + css /* scss */ ` .o-sidePanel { display: flex; @@ -33841,7 +34238,7 @@ function interactiveAddMerge(env, sheetId, target) { const result = env.model.dispatch("ADD_MERGE", { sheetId, target }); if (!result.isSuccessful) { - if (result.isCancelledBecause(3 /* MergeIsDestructive */)) { + if (result.isCancelledBecause(3 /* CommandResult.MergeIsDestructive */)) { env.askConfirmation(AddMergeInteractiveContent.MergeIsDestructive, () => { env.model.dispatch("ADD_MERGE", { sheetId, target, force: true }); }); @@ -34280,7 +34677,7 @@ display: grid; grid-template-columns: auto 350px; * { - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Ubuntu, "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + font-family: "Roboto", "RobotoDraft", Helvetica, Arial, sans-serif; } &, *, @@ -34306,6 +34703,39 @@ height: ${CF_ICON_EDGE_LENGTH}px; vertical-align: sub; } +`; + // ----------------------------------------------------------------------------- + // GRID STYLE + // ----------------------------------------------------------------------------- + css /* scss */ ` + .o-grid { + position: relative; + overflow: hidden; + background-color: ${BACKGROUND_GRAY_COLOR}; + &:focus { + outline: none; + } + + > canvas { + border-top: 1px solid #e2e3e3; + border-bottom: 1px solid #e2e3e3; + } + .o-scrollbar { + &.corner { + right: 0px; + bottom: 0px; + height: ${SCROLLBAR_WIDTH$1}px; + width: ${SCROLLBAR_WIDTH$1}px; + border-top: 1px solid #e2e3e3; + border-left: 1px solid #e2e3e3; + } + } + + .o-grid-overlay { + position: absolute; + outline: none; + } + } `; const t = (s) => s; class Spreadsheet extends owl.Component { @@ -34474,7 +34904,7 @@ } } Spreadsheet.template = "o-spreadsheet-Spreadsheet"; - Spreadsheet.components = { TopBar, Grid, BottomBar, SidePanel }; + Spreadsheet.components = { TopBar, Grid, BottomBar, SidePanel, SpreadsheetDashboard }; Spreadsheet._t = t; class LocalTransportService { @@ -35606,21 +36036,21 @@ } allowDispatch(cmd) { if (this.isWaitingForUndoRedo) { - return 61 /* WaitingSessionConfirmation */; + return 61 /* CommandResult.WaitingSessionConfirmation */; } switch (cmd.type) { case "REQUEST_UNDO": if (!this.canUndo()) { - return 5 /* EmptyUndoStack */; + return 5 /* CommandResult.EmptyUndoStack */; } break; case "REQUEST_REDO": if (!this.canRedo()) { - return 6 /* EmptyRedoStack */; + return 6 /* CommandResult.EmptyRedoStack */; } break; } - return 0 /* Success */; + return 0 /* CommandResult.Success */; } beforeHandle(cmd) { } handle(cmd) { @@ -35687,9 +36117,9 @@ // --------------------------------------------------------------------------- allowDispatch(cmd) { if (cmd.type === "MOVE_RANGES") { - return cmd.target.length === 1 ? 0 /* Success */ : 24 /* InvalidZones */; + return cmd.target.length === 1 ? 0 /* CommandResult.Success */ : 24 /* CommandResult.InvalidZones */; } - return 0 /* Success */; + return 0 /* CommandResult.Success */; } beforeHandle(command) { } handle(cmd) { @@ -36439,7 +36869,7 @@ processEvent(newAnchorEvent) { const event = { ...newAnchorEvent, previousAnchor: deepCopy(this.anchor) }; const commandResult = this.checkEventAnchorZone(event); - if (commandResult !== 0 /* Success */) { + if (commandResult !== 0 /* CommandResult.Success */) { return new DispatchResult(commandResult); } this.anchor = event.anchor; @@ -36452,20 +36882,20 @@ checkAnchorZone(anchor) { const { cell, zone } = anchor; if (!isInside(cell.col, cell.row, zone)) { - return 14 /* InvalidAnchorZone */; + return 14 /* CommandResult.InvalidAnchorZone */; } const { left, right, top, bottom } = zone; const sheetId = this.getters.getActiveSheetId(); const refCol = this.getters.findVisibleHeader(sheetId, "COL", range(left, right + 1)); const refRow = this.getters.findVisibleHeader(sheetId, "ROW", range(top, bottom + 1)); if (refRow === undefined || refCol === undefined) { - return 15 /* SelectionOutOfBound */; + return 15 /* CommandResult.SelectionOutOfBound */; } - return 0 /* Success */; + return 0 /* CommandResult.Success */; } checkAnchorZoneOrThrow(anchor) { const result = this.checkAnchorZone(anchor); - if (result === 14 /* InvalidAnchorZone */) { + if (result === 14 /* CommandResult.InvalidAnchorZone */) { throw new Error(_t("The provided anchor is invalid. The cell must be part of the zone.")); } } @@ -38057,7 +38487,7 @@ /** * Internal status of the model. Important for command handling coordination */ - this.status = 0 /* Ready */; + this.status = 0 /* Status.Ready */; /** * The dispatch method is the only entry point to manipulate data in the model. * This is through this method that commands are dispatched most of the time @@ -38076,15 +38506,15 @@ const command = { type, ...payload }; let status = this.status; if (this.getters.isReadonly() && !canExecuteInReadonly(command)) { - return new DispatchResult(64 /* Readonly */); + return new DispatchResult(64 /* CommandResult.Readonly */); } switch (status) { - case 0 /* Ready */: + case 0 /* Status.Ready */: const result = this.checkDispatchAllowed(command); if (!result.isSuccessful) { return result; } - this.status = 1 /* Running */; + this.status = 1 /* Status.Running */; const { changes, commands } = this.state.recordChanges(() => { if (isCoreCommand(command)) { this.state.addCommand(command); @@ -38093,10 +38523,10 @@ this.finalize(); }); this.session.save(commands, changes); - this.status = 0 /* Ready */; + this.status = 0 /* Status.Ready */; this.trigger("update"); break; - case 1 /* Running */: + case 1 /* Status.Running */: if (isCoreCommand(command)) { const dispatchResult = this.checkDispatchAllowed(command); if (!dispatchResult.isSuccessful) { @@ -38109,9 +38539,9 @@ this.dispatchToHandlers(this.handlers, command); } break; - case 3 /* Finalizing */: + case 3 /* Status.Finalizing */: throw new Error(_lt("Cannot dispatch commands in the finalize state")); - case 2 /* RunningCore */: + case 2 /* Status.RunningCore */: throw new Error("A UI plugin cannot dispatch while handling a core command"); } return DispatchResult.Success; @@ -38123,7 +38553,7 @@ this.dispatchFromCorePlugin = (type, payload) => { const command = { type, ...payload }; const previousStatus = this.status; - this.status = 2 /* RunningCore */; + this.status = 2 /* Status.RunningCore */; this.dispatchToHandlers(this.handlers, command); this.status = previousStatus; return DispatchResult.Success; @@ -38274,11 +38704,11 @@ return new DispatchResult(results.flat()); } finalize() { - this.status = 3 /* Finalizing */; + this.status = 3 /* Status.Finalizing */; for (const h of this.handlers) { h.finalize(); } - this.status = 0 /* Ready */; + this.status = 0 /* Status.Ready */; } /** * Dispatch the given command to the given handlers. @@ -38385,7 +38815,6 @@ autofillRulesRegistry, cellMenuRegistry, colMenuRegistry, - dashboardMenuRegistry, linkMenuRegistry, functionRegistry, uiPluginRegistry, @@ -38399,6 +38828,7 @@ chartRegistry, topbarMenuRegistry, topbarComponentRegistry, + clickableCellRegistry, otRegistry, inverseCommandRegistry, cellRegistry, @@ -38434,12 +38864,14 @@ getFillingMode, rgbaToHex, colorToRGBA, + positionToZone, }; const components = { ChartPanel, ChartFigure, ChartJsComponent, Grid, + GridOverlay, ScorecardChart, LineConfigPanel, LineBarPieDesignPanel, @@ -38483,8 +38915,8 @@ Object.defineProperty(exports, '__esModule', { value: true }); exports.__info__.version = '2.0.0'; - exports.__info__.date = '2022-09-28T11:18:35.407Z'; - exports.__info__.hash = '758a22f'; + exports.__info__.date = '2022-10-06T09:08:47.754Z'; + exports.__info__.hash = '808bd19'; })(this.o_spreadsheet = this.o_spreadsheet || {}, owl); //# sourceMappingURL=o_spreadsheet.js.map diff --git a/addons/spreadsheet/static/src/o_spreadsheet/o_spreadsheet.xml b/addons/spreadsheet/static/src/o_spreadsheet/o_spreadsheet.xml index f1a8c5800ad..da61ad865a1 100644 --- a/addons/spreadsheet/static/src/o_spreadsheet/o_spreadsheet.xml +++ b/addons/spreadsheet/static/src/o_spreadsheet/o_spreadsheet.xml @@ -281,6 +281,38 @@
+ +
+
+ + + +
+
+ + +
+
+ +
@@ -323,72 +355,68 @@
- -
- -
-
-
- -
-
- -
-
-
-
-
-
-
-
- + +
+
+
+
+
+ +
+
+
+
+
+
+
+
@@ -422,29 +450,38 @@
+ +
+ + + +
+
+
- -
- -
+ - - - + - + @@ -504,25 +532,47 @@ position="menuState.position" onClose="() => this.closeMenu()" /> - -
-
-
-
-
-
+ +
+ +
+ + +
+
+ + + + + + +
@@ -1297,6 +1347,12 @@ + +
+
+
+ +
- - - - + + + + + + + + +