@odoo/o-spreadsheet 19.5.0-alpha.11 → 19.5.0-alpha.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/material_symbols_outlined.css +9 -0
- package/dist/o_spreadsheet.cjs +2313 -656
- package/dist/o_spreadsheet.css +169 -33
- package/dist/o_spreadsheet.esm.js +2305 -648
- package/dist/o_spreadsheet.iife.js +2313 -656
- package/dist/o_spreadsheet.min.iife.js +344 -344
- package/dist/o_spreadsheet.xml +209 -128
- package/dist/types/actions/data_actions.d.ts +1 -0
- package/dist/types/components/bottom_bar/bottom_bar_statistic/bottom_bar_statistic.d.ts +3 -0
- package/dist/types/components/dashboard/clickable_cell_sort_icon/clickable_cell_sort_icon.d.ts +1 -1
- package/dist/types/components/figures/figure/figure.d.ts +1 -0
- package/dist/types/components/figures/figure_carousel/figure_carousel.d.ts +10 -0
- package/dist/types/components/figures/figure_container/figure_container.d.ts +1 -2
- package/dist/types/components/grid/grid.d.ts +1 -0
- package/dist/types/components/helpers/chart_drag_and_drop.d.ts +15 -0
- package/dist/types/components/helpers/dom_helpers.d.ts +3 -2
- package/dist/types/components/link/link_editor/link_editor.d.ts +1 -1
- package/dist/types/components/side_panel/carousel_panel/carousel_panel.d.ts +1 -1
- package/dist/types/components/side_panel/chart/building_blocks/color_scale/color_scale_picker.d.ts +1 -1
- package/dist/types/components/side_panel/data_analysis/chart_suggestion_preview.d.ts +19 -0
- package/dist/types/components/side_panel/data_analysis/data_analysis_panel.d.ts +17 -0
- package/dist/types/components/side_panel/data_analysis/data_analysis_store.d.ts +15 -0
- package/dist/types/components/side_panel/pivot/pivot_layout_configurator/pivot_layout_configurator.d.ts +1 -6
- package/dist/types/components/side_panel/side_panel/side_panel_store.d.ts +2 -1
- package/dist/types/components/standalone_grid_canvas/carousel_data_view_print.d.ts +32 -0
- package/dist/types/components/standalone_grid_canvas/standalone_grid_canvas.d.ts +6 -0
- package/dist/types/components/standalone_viewport/standalone_viewport.d.ts +6 -3
- package/dist/types/components/standalone_viewport/standalone_viewport_store.d.ts +2 -1
- package/dist/types/components/tables/hovered_table_store.d.ts +6 -7
- package/dist/types/constants.d.ts +13 -1
- package/dist/types/functions/helpers.d.ts +4 -0
- package/dist/types/functions/module_operators.d.ts +1 -1
- package/dist/types/functions/module_text.d.ts +11 -11
- package/dist/types/functions/module_web.d.ts +1 -1
- package/dist/types/helpers/carousel_helpers.d.ts +8 -1
- package/dist/types/helpers/data_analysis.d.ts +16 -0
- package/dist/types/helpers/figures/charts/chart_data_source_helpers.d.ts +10 -0
- package/dist/types/helpers/figures/charts/chart_suggestion_engine.d.ts +8 -0
- package/dist/types/helpers/figures/charts/chart_ui_common.d.ts +8 -1
- package/dist/types/helpers/figures/charts/charts_suggestions_constants.d.ts +37 -0
- package/dist/types/helpers/figures/charts/gauge_chart_rendering.d.ts +6 -2
- package/dist/types/helpers/text_helper.d.ts +1 -1
- package/dist/types/index.d.ts +3 -1
- package/dist/types/registries/figures_registry.d.ts +1 -0
- package/dist/types/stores/cell_hover_overlay_store.d.ts +15 -0
- package/dist/types/stores/chart_drag_store.d.ts +10 -0
- package/dist/types/stores/grid_renderer_store.d.ts +3 -2
- package/dist/types/types/chart/chart_suggestion.d.ts +136 -0
- package/dist/types/types/commands.d.ts +16 -9
- package/package.json +4 -4
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* This file is generated by o-spreadsheet build tools. Do not edit it.
|
|
4
4
|
* @see https://github.com/odoo/o-spreadsheet
|
|
5
|
-
* @version 19.5.0-alpha.
|
|
6
|
-
* @date 2026-08-
|
|
7
|
-
* @hash
|
|
5
|
+
* @version 19.5.0-alpha.12
|
|
6
|
+
* @date 2026-08-19T09:47:03.774Z
|
|
7
|
+
* @hash 68ca68e
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
(function(exports, _odoo_owl) {
|
|
@@ -208,6 +208,16 @@ _odoo_owl = __toESM(_odoo_owl, 1);
|
|
|
208
208
|
height: rect.height
|
|
209
209
|
};
|
|
210
210
|
}
|
|
211
|
+
function getBoundingRectWithMargins(el) {
|
|
212
|
+
const style = getComputedStyle(el);
|
|
213
|
+
const rect = el.getBoundingClientRect();
|
|
214
|
+
return {
|
|
215
|
+
x: rect.x,
|
|
216
|
+
y: rect.y,
|
|
217
|
+
width: rect.width + parseInt(style.marginLeft || "0") + parseInt(style.marginRight || "0"),
|
|
218
|
+
height: rect.height + parseInt(style.marginTop || "0") + parseInt(style.marginBottom || "0")
|
|
219
|
+
};
|
|
220
|
+
}
|
|
211
221
|
/**
|
|
212
222
|
* Iterate over all the children of `el` in the dom tree starting at `el`, depth first.
|
|
213
223
|
*/
|
|
@@ -651,6 +661,14 @@ _odoo_owl = __toESM(_odoo_owl, 1);
|
|
|
651
661
|
fontSize: 18,
|
|
652
662
|
color: TEXT_BODY
|
|
653
663
|
};
|
|
664
|
+
const CAROUSEL_LAYOUT = {
|
|
665
|
+
paddingX: 24,
|
|
666
|
+
paddingY: 8,
|
|
667
|
+
headerPaddingTop: 4,
|
|
668
|
+
separatorWidth: 1,
|
|
669
|
+
headerLineHeight: 1.5,
|
|
670
|
+
minHeaderHeight: 25
|
|
671
|
+
};
|
|
654
672
|
const DEFAULT_TOKEN_COLOR = "light-dark(#000000, #ffffff)";
|
|
655
673
|
const functionColor = DEFAULT_TOKEN_COLOR;
|
|
656
674
|
const operatorColor = "#3da4ab";
|
|
@@ -2635,7 +2653,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
2635
2653
|
*
|
|
2636
2654
|
* The end goal is to eliminate all compatibility shims.
|
|
2637
2655
|
*/
|
|
2638
|
-
const { __ODOO_COMPATIBILITY_LAYER_ADDED__, blockDom: blockDom$1, config, onMounted: onMounted$
|
|
2656
|
+
const { __ODOO_COMPATIBILITY_LAYER_ADDED__, blockDom: blockDom$1, config, onMounted: onMounted$26, onPatched: onPatched$5, onWillUnmount: onWillUnmount$20, App: OwlApp, Component: OwlComponent, EnvPlugin: OwlEnvPlugin, useChildEnv: OwlUseChildEnv, useChildSubEnv: OwlUseChildSubEnv, useComponent: OwlUseComponent, useEnv: OwlUseEnv, useExternalListener: OwlUseExternalListener, useLayoutEffect: OwlUseLayoutEffect, useSubEnv: OwlUseSubEnv, Plugin, plugin, props, providePlugins, useScope, xml: xml$5 } = _odoo_owl;
|
|
2639
2657
|
const isOdooCompatLoaded = __ODOO_COMPATIBILITY_LAYER_ADDED__ === true;
|
|
2640
2658
|
var _Component = class extends OwlComponent {
|
|
2641
2659
|
static template = "";
|
|
@@ -2656,8 +2674,8 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
2656
2674
|
function _useExternalListener(target, eventName, handler, eventParams) {
|
|
2657
2675
|
const node = useScope();
|
|
2658
2676
|
const boundHandler = handler.bind(node.component);
|
|
2659
|
-
onMounted$
|
|
2660
|
-
onWillUnmount$
|
|
2677
|
+
onMounted$26(() => target.addEventListener(eventName, boundHandler, eventParams));
|
|
2678
|
+
onWillUnmount$20(() => target.removeEventListener(eventName, boundHandler, eventParams));
|
|
2661
2679
|
}
|
|
2662
2680
|
function onWillRender(cb) {
|
|
2663
2681
|
const node = useScope();
|
|
@@ -2672,7 +2690,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
2672
2690
|
let cleanup;
|
|
2673
2691
|
/** @type {any[]} */
|
|
2674
2692
|
let dependencies;
|
|
2675
|
-
onMounted$
|
|
2693
|
+
onMounted$26(() => {
|
|
2676
2694
|
dependencies = computeDependencies();
|
|
2677
2695
|
cleanup = effect(...dependencies);
|
|
2678
2696
|
});
|
|
@@ -2689,7 +2707,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
2689
2707
|
cleanup = effect(...dependencies);
|
|
2690
2708
|
}
|
|
2691
2709
|
});
|
|
2692
|
-
onWillUnmount$
|
|
2710
|
+
onWillUnmount$20(() => cleanup && cleanup());
|
|
2693
2711
|
}
|
|
2694
2712
|
var _EnvPlugin = class extends Plugin {
|
|
2695
2713
|
static id = "__ENV__";
|
|
@@ -2762,7 +2780,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
2762
2780
|
}
|
|
2763
2781
|
};
|
|
2764
2782
|
var Portal = class extends OwlComponent {
|
|
2765
|
-
static template = xml$
|
|
2783
|
+
static template = xml$5`<t t-call-slot="default"/>`;
|
|
2766
2784
|
constructor(node) {
|
|
2767
2785
|
super(node);
|
|
2768
2786
|
this.props = props();
|
|
@@ -2772,7 +2790,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
2772
2790
|
const node = this.__owl__;
|
|
2773
2791
|
const renderContent = node.renderFn;
|
|
2774
2792
|
node.renderFn = (...args) => new VPortal(node.props.selector, renderContent(...args));
|
|
2775
|
-
onMounted$
|
|
2793
|
+
onMounted$26(() => {
|
|
2776
2794
|
const portal = node.bdom;
|
|
2777
2795
|
if (!portal.target) {
|
|
2778
2796
|
const target = document.querySelector(node.props.selector);
|
|
@@ -2780,7 +2798,7 @@ stores.inject(MyMetaStore, storeInstance);
|
|
|
2780
2798
|
else throw new Error("invalid portal target");
|
|
2781
2799
|
}
|
|
2782
2800
|
});
|
|
2783
|
-
onWillUnmount$
|
|
2801
|
+
onWillUnmount$20(() => {
|
|
2784
2802
|
node.bdom.remove();
|
|
2785
2803
|
});
|
|
2786
2804
|
}
|
|
@@ -4073,6 +4091,18 @@ set(value) {
|
|
|
4073
4091
|
default: return "";
|
|
4074
4092
|
}
|
|
4075
4093
|
}
|
|
4094
|
+
/**
|
|
4095
|
+
* Only converts the decimal separator, ignore number format such as % or dates
|
|
4096
|
+
*/
|
|
4097
|
+
function toLocaleString(data, locale) {
|
|
4098
|
+
const value = toValue(data);
|
|
4099
|
+
switch (typeof value) {
|
|
4100
|
+
case "string": return value;
|
|
4101
|
+
case "number": return value.toString().replace(".", locale.decimalSeparator);
|
|
4102
|
+
case "boolean": return value ? "TRUE" : "FALSE";
|
|
4103
|
+
default: return "";
|
|
4104
|
+
}
|
|
4105
|
+
}
|
|
4076
4106
|
/** Normalize string by setting it to lowercase and replacing accent letters with plain letters */
|
|
4077
4107
|
const normalizeString = memoize(function normalizeString(str) {
|
|
4078
4108
|
return str.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
|
@@ -9249,8 +9279,8 @@ set(value) {
|
|
|
9249
9279
|
function fontSizeInPixels(fontSize) {
|
|
9250
9280
|
return Math.round(fontSize * 96 / 72);
|
|
9251
9281
|
}
|
|
9252
|
-
function computeTextFont(style, fontUnit = "pt") {
|
|
9253
|
-
return `${style.italic ? "italic " : ""}${style.bold ? "bold" :
|
|
9282
|
+
function computeTextFont(style, fontUnit = "pt", fontWeight = 400) {
|
|
9283
|
+
return `${style.italic ? "italic " : ""}${style.bold ? "bold" : fontWeight} ${(fontUnit === "pt" ? computeTextFontSizeInPixels(style) : style.fontSize) ?? DEFAULT_FONT_SIZE}px ${DEFAULT_FONT}`;
|
|
9254
9284
|
}
|
|
9255
9285
|
function computeTextFontSizeInPixels(style) {
|
|
9256
9286
|
return fontSizeInPixels(style?.fontSize || DEFAULT_FONT_SIZE);
|
|
@@ -11884,7 +11914,7 @@ set(value) {
|
|
|
11884
11914
|
chartId: types.string(),
|
|
11885
11915
|
isFullScreen: types.boolean().optional()
|
|
11886
11916
|
});
|
|
11887
|
-
canvas =
|
|
11917
|
+
canvas = _odoo_owl.signal.ref(HTMLCanvasElement);
|
|
11888
11918
|
chart;
|
|
11889
11919
|
currentRuntime;
|
|
11890
11920
|
animationStore;
|
|
@@ -12607,7 +12637,7 @@ set(value) {
|
|
|
12607
12637
|
chartId: types.string(),
|
|
12608
12638
|
isFullScreen: types.boolean().optional()
|
|
12609
12639
|
});
|
|
12610
|
-
canvas =
|
|
12640
|
+
canvas = _odoo_owl.signal.ref(HTMLCanvasElement);
|
|
12611
12641
|
zoomStore;
|
|
12612
12642
|
get runtime() {
|
|
12613
12643
|
return this.env.model.getters.getChartRuntime(this.props.chartId);
|
|
@@ -13992,6 +14022,44 @@ set(value) {
|
|
|
13992
14022
|
const definition = getters.getChartDefinition(item.chartId);
|
|
13993
14023
|
return (chartSubtypeRegistry.getAll().find((c) => c.matcher?.(definition)) || chartSubtypeRegistry.get(definition.type)).displayName;
|
|
13994
14024
|
}
|
|
14025
|
+
function getCarouselLayout(figureRect, carousel, selectedItem) {
|
|
14026
|
+
const layout = CAROUSEL_LAYOUT;
|
|
14027
|
+
const title = {
|
|
14028
|
+
...DEFAULT_CAROUSEL_TITLE_STYLE,
|
|
14029
|
+
...carousel.title
|
|
14030
|
+
};
|
|
14031
|
+
const x = figureRect.x + layout.paddingX;
|
|
14032
|
+
const width = Math.max(0, figureRect.width - 2 * layout.paddingX);
|
|
14033
|
+
const titleHeight = title.text ? title.fontSize * layout.headerLineHeight : 0;
|
|
14034
|
+
const headerRect = {
|
|
14035
|
+
x,
|
|
14036
|
+
y: figureRect.y + layout.paddingY + layout.headerPaddingTop,
|
|
14037
|
+
width,
|
|
14038
|
+
height: Math.max(layout.minHeaderHeight, titleHeight)
|
|
14039
|
+
};
|
|
14040
|
+
const headerBottom = headerRect.y + headerRect.height;
|
|
14041
|
+
const separatorRect = title.text ? {
|
|
14042
|
+
x,
|
|
14043
|
+
y: headerBottom + layout.paddingY,
|
|
14044
|
+
width,
|
|
14045
|
+
height: layout.separatorWidth
|
|
14046
|
+
} : void 0;
|
|
14047
|
+
const contentPaddingTop = selectedItem?.type === "carouselDataView" ? layout.paddingY : 0;
|
|
14048
|
+
const rectAboveContent = separatorRect || headerRect;
|
|
14049
|
+
const contentY = rectAboveContent.y + rectAboveContent.height + contentPaddingTop;
|
|
14050
|
+
const contentBottom = figureRect.y + figureRect.height - layout.paddingY;
|
|
14051
|
+
return {
|
|
14052
|
+
headerRect,
|
|
14053
|
+
separatorRect,
|
|
14054
|
+
contentRect: {
|
|
14055
|
+
x,
|
|
14056
|
+
y: contentY,
|
|
14057
|
+
width,
|
|
14058
|
+
height: Math.max(0, contentBottom - contentY)
|
|
14059
|
+
},
|
|
14060
|
+
headerLineHeight: layout.headerLineHeight
|
|
14061
|
+
};
|
|
14062
|
+
}
|
|
13995
14063
|
|
|
13996
14064
|
//#endregion
|
|
13997
14065
|
//#region src/helpers/figures/charts/gauge_chart_rendering.ts
|
|
@@ -14003,7 +14071,7 @@ set(value) {
|
|
|
14003
14071
|
const GAUGE_BACKGROUND_COLOR = "#F3F2F1";
|
|
14004
14072
|
const GAUGE_INFLECTION_LABEL_BOTTOM_MARGIN = 6;
|
|
14005
14073
|
const GAUGE_TITLE_SECTION_HEIGHT = 25;
|
|
14006
|
-
function drawGaugeChart(canvas, runtime, zoom = 1, dimensions) {
|
|
14074
|
+
function drawGaugeChart(canvas, runtime, zoom = 1, dimensions, options) {
|
|
14007
14075
|
const size = dimensions ?? getCanvasSize(canvas);
|
|
14008
14076
|
const dpr = typeof globalThis.devicePixelRatio === "number" ? globalThis.devicePixelRatio : 1;
|
|
14009
14077
|
canvas.width = size.width * dpr;
|
|
@@ -14016,7 +14084,7 @@ set(value) {
|
|
|
14016
14084
|
height: size.height / zoom,
|
|
14017
14085
|
x: 0,
|
|
14018
14086
|
y: 0
|
|
14019
|
-
}, runtime, ctx);
|
|
14087
|
+
}, runtime, ctx, options);
|
|
14020
14088
|
drawBackground(ctx, config);
|
|
14021
14089
|
drawGauge(ctx, config);
|
|
14022
14090
|
drawInflectionValues(ctx, config);
|
|
@@ -14104,7 +14172,8 @@ set(value) {
|
|
|
14104
14172
|
ctx.fillText(title.label, title.textPosition.x, title.textPosition.y);
|
|
14105
14173
|
ctx.restore();
|
|
14106
14174
|
}
|
|
14107
|
-
function getGaugeRenderingConfig(boundingRect, runtime, ctx) {
|
|
14175
|
+
function getGaugeRenderingConfig(boundingRect, runtime, ctx, options) {
|
|
14176
|
+
const labelFontSize = options?.labelFontSize ?? 12;
|
|
14108
14177
|
const maxValue = runtime.maxValue;
|
|
14109
14178
|
const minValue = runtime.minValue;
|
|
14110
14179
|
const gaugeValue = getGaugeValue(runtime, "animated");
|
|
@@ -14122,14 +14191,14 @@ set(value) {
|
|
|
14122
14191
|
if (computeTextWidth(ctx, gaugeLabel, { fontSize: gaugeValueFontSize }, "px") > maxTextWidth) gaugeValueFontSize = getFontSizeMatchingWidth(maxTextWidth, gaugeValueFontSize, (fontSize) => computeTextWidth(ctx, gaugeLabel, { fontSize }, "px"));
|
|
14123
14192
|
const minLabelPosition = {
|
|
14124
14193
|
x: gaugeRect.x + gaugeArcWidth / 2,
|
|
14125
|
-
y: gaugeRect.y + gaugeRect.height +
|
|
14194
|
+
y: gaugeRect.y + gaugeRect.height + labelFontSize
|
|
14126
14195
|
};
|
|
14127
14196
|
const maxLabelPosition = {
|
|
14128
14197
|
x: gaugeRect.x + gaugeRect.width - gaugeArcWidth / 2,
|
|
14129
|
-
y: gaugeRect.y + gaugeRect.height +
|
|
14198
|
+
y: gaugeRect.y + gaugeRect.height + labelFontSize
|
|
14130
14199
|
};
|
|
14131
14200
|
const textColor = chartMutedFontColor(runtime.background);
|
|
14132
|
-
const inflectionValues = getInflectionValues(runtime, gaugeRect, textColor, ctx);
|
|
14201
|
+
const inflectionValues = getInflectionValues(runtime, gaugeRect, textColor, ctx, labelFontSize);
|
|
14133
14202
|
let x = 0, titleWidth = 0, titleHeight = 0;
|
|
14134
14203
|
if (runtime.title.text) ({width: titleWidth, height: titleHeight} = computeTextDimension(ctx, runtime.title.text, {
|
|
14135
14204
|
fontSize: 16,
|
|
@@ -14177,13 +14246,13 @@ set(value) {
|
|
|
14177
14246
|
minLabel: {
|
|
14178
14247
|
label: runtime.minValue.label,
|
|
14179
14248
|
textPosition: minLabelPosition,
|
|
14180
|
-
fontSize:
|
|
14249
|
+
fontSize: labelFontSize,
|
|
14181
14250
|
color: textColor
|
|
14182
14251
|
},
|
|
14183
14252
|
maxLabel: {
|
|
14184
14253
|
label: runtime.maxValue.label,
|
|
14185
14254
|
textPosition: maxLabelPosition,
|
|
14186
|
-
fontSize:
|
|
14255
|
+
fontSize: labelFontSize,
|
|
14187
14256
|
color: textColor
|
|
14188
14257
|
}
|
|
14189
14258
|
};
|
|
@@ -14217,27 +14286,27 @@ set(value) {
|
|
|
14217
14286
|
*
|
|
14218
14287
|
* Also compute an offset for the text so that it doesn't overlap with other text.
|
|
14219
14288
|
*/
|
|
14220
|
-
function getInflectionValues(runtime, gaugeRect, textColor, ctx) {
|
|
14289
|
+
function getInflectionValues(runtime, gaugeRect, textColor, ctx, labelFontSize = 12) {
|
|
14221
14290
|
const maxValue = runtime.maxValue;
|
|
14222
14291
|
const minValue = runtime.minValue;
|
|
14223
14292
|
const gaugeCircleCenter = {
|
|
14224
14293
|
x: gaugeRect.x + gaugeRect.width / 2,
|
|
14225
14294
|
y: gaugeRect.y + gaugeRect.height
|
|
14226
14295
|
};
|
|
14227
|
-
const textStyle = { fontSize:
|
|
14296
|
+
const textStyle = { fontSize: labelFontSize };
|
|
14228
14297
|
const inflectionValues = [];
|
|
14229
14298
|
const inflectionValuesTextRects = [];
|
|
14230
14299
|
for (const inflectionValue of runtime.inflectionValues) {
|
|
14231
14300
|
const percentage = (inflectionValue.value - minValue.value) / (maxValue.value - minValue.value);
|
|
14232
14301
|
const labelWidth = computeTextWidth(ctx, inflectionValue.label, textStyle, "px");
|
|
14233
14302
|
const angle = Math.PI - Math.PI * percentage;
|
|
14234
|
-
const textRect = getRectangleTangentToCircle(angle, gaugeRect.height + GAUGE_INFLECTION_LABEL_BOTTOM_MARGIN, gaugeCircleCenter.x, gaugeCircleCenter.y, labelWidth + 2,
|
|
14235
|
-
const offset = inflectionValuesTextRects.some((rect) => doRectanglesIntersect(rect, textRect)) ?
|
|
14303
|
+
const textRect = getRectangleTangentToCircle(angle, gaugeRect.height + GAUGE_INFLECTION_LABEL_BOTTOM_MARGIN, gaugeCircleCenter.x, gaugeCircleCenter.y, labelWidth + 2, labelFontSize);
|
|
14304
|
+
const offset = inflectionValuesTextRects.some((rect) => doRectanglesIntersect(rect, textRect)) ? labelFontSize : 0;
|
|
14236
14305
|
inflectionValuesTextRects.push(textRect);
|
|
14237
14306
|
inflectionValues.push({
|
|
14238
14307
|
rotation: angle,
|
|
14239
14308
|
label: inflectionValue.label,
|
|
14240
|
-
fontSize:
|
|
14309
|
+
fontSize: labelFontSize,
|
|
14241
14310
|
color: textColor,
|
|
14242
14311
|
offset
|
|
14243
14312
|
});
|
|
@@ -14357,6 +14426,44 @@ set(value) {
|
|
|
14357
14426
|
"mouseup"
|
|
14358
14427
|
]
|
|
14359
14428
|
};
|
|
14429
|
+
/**
|
|
14430
|
+
* Sample down a Chart.js configuration's data points to at most `maxPoints`, evenly spaced.
|
|
14431
|
+
* Used for lightweight previews (drag preview, chart suggestions) where rendering every point
|
|
14432
|
+
* of a large dataset would make Chart.js slow without any visible benefit on a small canvas.
|
|
14433
|
+
*/
|
|
14434
|
+
function limitChartConfigDataPoints(config, maxPoints = 150) {
|
|
14435
|
+
const data = config.data;
|
|
14436
|
+
if (!data) return config;
|
|
14437
|
+
return {
|
|
14438
|
+
...config,
|
|
14439
|
+
data: {
|
|
14440
|
+
...data,
|
|
14441
|
+
labels: limitArrayDataPoints(data.labels, maxPoints),
|
|
14442
|
+
datasets: data.datasets?.map((dataset) => limitDatasetDataPoints(dataset, maxPoints))
|
|
14443
|
+
}
|
|
14444
|
+
};
|
|
14445
|
+
}
|
|
14446
|
+
function limitDatasetDataPoints(dataset, maxPoints) {
|
|
14447
|
+
const values = dataset.data;
|
|
14448
|
+
if (!Array.isArray(values) || values.length <= maxPoints) return dataset;
|
|
14449
|
+
const length = values.length;
|
|
14450
|
+
const indices = sampleIndices(length, maxPoints);
|
|
14451
|
+
const limited = { ...dataset };
|
|
14452
|
+
for (const key of Object.keys(limited)) {
|
|
14453
|
+
const value = limited[key];
|
|
14454
|
+
if (Array.isArray(value) && value.length === length) limited[key] = indices.map((i) => value[i]);
|
|
14455
|
+
}
|
|
14456
|
+
return limited;
|
|
14457
|
+
}
|
|
14458
|
+
function limitArrayDataPoints(arr, maxPoints) {
|
|
14459
|
+
if (!Array.isArray(arr) || arr.length <= maxPoints) return arr;
|
|
14460
|
+
return sampleIndices(arr.length, maxPoints).map((i) => arr[i]);
|
|
14461
|
+
}
|
|
14462
|
+
/** Evenly spaced indices sampled from [0, length). */
|
|
14463
|
+
function sampleIndices(length, maxPoints) {
|
|
14464
|
+
const step = length / maxPoints;
|
|
14465
|
+
return Array.from({ length: maxPoints }, (_, i) => Math.min(length - 1, Math.floor(i * step)));
|
|
14466
|
+
}
|
|
14360
14467
|
async function chartToImageUrl(runtime, figure, type) {
|
|
14361
14468
|
try {
|
|
14362
14469
|
const canvas = createRenderingSurface(figure.width, figure.height);
|
|
@@ -14404,7 +14511,7 @@ set(value) {
|
|
|
14404
14511
|
*
|
|
14405
14512
|
* @returns a cleanup function to be called after the drawing is no longer needed (to free Chart.js resources)
|
|
14406
14513
|
*/
|
|
14407
|
-
function drawChartOnCanvas(canvas, runtime, size, type) {
|
|
14514
|
+
function drawChartOnCanvas(canvas, runtime, size, type, zoom = 1) {
|
|
14408
14515
|
if ("chartJsConfig" in runtime) {
|
|
14409
14516
|
if (!globalThis.Chart) throw new Error("Chart.js library is not loaded");
|
|
14410
14517
|
const extensionsLoaded = areChartJSExtensionsLoaded();
|
|
@@ -14420,8 +14527,8 @@ set(value) {
|
|
|
14420
14527
|
chart.destroy();
|
|
14421
14528
|
if (!extensionsLoaded) unregisterChartJsExtensions();
|
|
14422
14529
|
};
|
|
14423
|
-
} else if (type === "scorecard") drawScoreChart(getScorecardConfiguration(size, runtime), canvas);
|
|
14424
|
-
else if (type === "gauge") drawGaugeChart(canvas, runtime,
|
|
14530
|
+
} else if (type === "scorecard") drawScoreChart(getScorecardConfiguration(size, runtime), canvas, zoom);
|
|
14531
|
+
else if (type === "gauge") drawGaugeChart(canvas, runtime, zoom, size);
|
|
14425
14532
|
return () => {};
|
|
14426
14533
|
}
|
|
14427
14534
|
|
|
@@ -14959,7 +15066,7 @@ set(value) {
|
|
|
14959
15066
|
var ZoomableChartJsComponent = class extends ChartJsComponent {
|
|
14960
15067
|
static template = "o-spreadsheet-ZoomableChartJsComponent";
|
|
14961
15068
|
store;
|
|
14962
|
-
masterChartCanvas =
|
|
15069
|
+
masterChartCanvas = _odoo_owl.signal.ref(HTMLCanvasElement);
|
|
14963
15070
|
masterChart;
|
|
14964
15071
|
mode;
|
|
14965
15072
|
hasLinearScale;
|
|
@@ -15713,7 +15820,7 @@ set(value) {
|
|
|
15713
15820
|
chartId: types.string(),
|
|
15714
15821
|
isFullScreen: types.boolean().optional()
|
|
15715
15822
|
});
|
|
15716
|
-
canvas =
|
|
15823
|
+
canvas = _odoo_owl.signal.ref(HTMLCanvasElement);
|
|
15717
15824
|
animationStore;
|
|
15718
15825
|
zoomStore;
|
|
15719
15826
|
get runtime() {
|
|
@@ -15950,7 +16057,7 @@ set(value) {
|
|
|
15950
16057
|
onKeyDown: types.function().optional(),
|
|
15951
16058
|
disableKeyboardNavigation: types.boolean().optional()
|
|
15952
16059
|
});
|
|
15953
|
-
menuRef =
|
|
16060
|
+
menuRef = _odoo_owl.signal.ref();
|
|
15954
16061
|
setup() {
|
|
15955
16062
|
useLayoutEffect(() => {
|
|
15956
16063
|
const menuEl = this.menuRef();
|
|
@@ -16070,8 +16177,8 @@ set(value) {
|
|
|
16070
16177
|
zIndex: types.number().optional(),
|
|
16071
16178
|
class: types.string().optional()
|
|
16072
16179
|
});
|
|
16073
|
-
popoverRef =
|
|
16074
|
-
popoverContentRef =
|
|
16180
|
+
popoverRef = _odoo_owl.signal.ref();
|
|
16181
|
+
popoverContentRef = _odoo_owl.signal.ref();
|
|
16075
16182
|
currentPosition = void 0;
|
|
16076
16183
|
currentDisplayValue = void 0;
|
|
16077
16184
|
spreadsheetRect = useSpreadsheetRect();
|
|
@@ -16259,7 +16366,7 @@ set(value) {
|
|
|
16259
16366
|
isHoveringChild: false
|
|
16260
16367
|
});
|
|
16261
16368
|
state = (0, _odoo_owl.proxy)({ hoveredMenu: this.props.autoSelectFirstItem ? this.getNextEnabledMenuItem() : void 0 });
|
|
16262
|
-
menuRef =
|
|
16369
|
+
menuRef = _odoo_owl.signal.ref();
|
|
16263
16370
|
openingTimeOut = useTimeOut();
|
|
16264
16371
|
setup() {
|
|
16265
16372
|
const domFocusableElementStore = useStore(DOMFocusableElementStore);
|
|
@@ -16548,43 +16655,29 @@ set(value) {
|
|
|
16548
16655
|
};
|
|
16549
16656
|
|
|
16550
16657
|
//#endregion
|
|
16551
|
-
//#region src/
|
|
16552
|
-
var
|
|
16553
|
-
mutators = [
|
|
16554
|
-
|
|
16658
|
+
//#region src/stores/cell_hover_overlay_store.ts
|
|
16659
|
+
var CellHoverOverlayStore = class extends SpreadsheetStore {
|
|
16660
|
+
mutators = [
|
|
16661
|
+
"hover",
|
|
16662
|
+
"register",
|
|
16663
|
+
"unRegister"
|
|
16664
|
+
];
|
|
16665
|
+
storeGetters = ["getCellHoverOverlayColor"];
|
|
16666
|
+
providers = [];
|
|
16555
16667
|
overlayColors = new PositionMap();
|
|
16556
|
-
hover(
|
|
16557
|
-
|
|
16558
|
-
|
|
16559
|
-
this.
|
|
16668
|
+
hover(hoveredPosition) {
|
|
16669
|
+
this.overlayColors = new PositionMap();
|
|
16670
|
+
if (!hoveredPosition) return;
|
|
16671
|
+
for (const provider of this.providers) {
|
|
16672
|
+
const positions = provider.getHighlightedPositions(hoveredPosition);
|
|
16673
|
+
for (const position of positions) this.overlayColors.set(position, TABLE_HOVER_BACKGROUND_COLOR);
|
|
16674
|
+
}
|
|
16560
16675
|
}
|
|
16561
|
-
|
|
16562
|
-
this.
|
|
16676
|
+
register(highlightProvider) {
|
|
16677
|
+
this.providers.push(highlightProvider);
|
|
16563
16678
|
}
|
|
16564
|
-
|
|
16565
|
-
this.
|
|
16566
|
-
if (!this.position) return;
|
|
16567
|
-
const { sheetId, col, row } = this.position;
|
|
16568
|
-
const table = this.getters.getTable({
|
|
16569
|
-
sheetId,
|
|
16570
|
-
col,
|
|
16571
|
-
row
|
|
16572
|
-
});
|
|
16573
|
-
if (!table) return;
|
|
16574
|
-
const { left, right, top } = table.range.zone;
|
|
16575
|
-
const isTableHeader = row < top + table.config.numberOfHeaders;
|
|
16576
|
-
const doesTableRowHaveContent = range(left, right + 1).some((col) => {
|
|
16577
|
-
return !this.getters.isColHidden(sheetId, col) && this.getters.getEvaluatedCell({
|
|
16578
|
-
sheetId,
|
|
16579
|
-
col,
|
|
16580
|
-
row
|
|
16581
|
-
}).formattedValue;
|
|
16582
|
-
});
|
|
16583
|
-
if (!isTableHeader && doesTableRowHaveContent) for (let col = left; col <= right; col++) this.overlayColors.set({
|
|
16584
|
-
sheetId,
|
|
16585
|
-
col,
|
|
16586
|
-
row
|
|
16587
|
-
}, TABLE_HOVER_BACKGROUND_COLOR);
|
|
16679
|
+
unRegister(highlightProvider) {
|
|
16680
|
+
this.providers = this.providers.filter((h) => h !== highlightProvider);
|
|
16588
16681
|
}
|
|
16589
16682
|
};
|
|
16590
16683
|
|
|
@@ -16596,9 +16689,9 @@ set(value) {
|
|
|
16596
16689
|
position: types.CellPosition(),
|
|
16597
16690
|
sortDirection: types.or([types.SortDirection, types.literal("none")])
|
|
16598
16691
|
});
|
|
16599
|
-
|
|
16692
|
+
hoveredCellOverlayStore;
|
|
16600
16693
|
setup() {
|
|
16601
|
-
this.
|
|
16694
|
+
this.hoveredCellOverlayStore = useStore(CellHoverOverlayStore);
|
|
16602
16695
|
}
|
|
16603
16696
|
get style() {
|
|
16604
16697
|
const cellStyle = this.env.model.getters.getCellComputedStyle(this.props.position);
|
|
@@ -16618,7 +16711,7 @@ set(value) {
|
|
|
16618
16711
|
}
|
|
16619
16712
|
}
|
|
16620
16713
|
getBackgroundColor(cellStyle) {
|
|
16621
|
-
const overlayColor = this.
|
|
16714
|
+
const overlayColor = this.hoveredCellOverlayStore.overlayColors.get(this.props.position);
|
|
16622
16715
|
if (overlayColor) return blendColors(cellStyle.fillColor || "#FFFFFF", overlayColor);
|
|
16623
16716
|
return cellStyle.fillColor || "#FFFFFF";
|
|
16624
16717
|
}
|
|
@@ -21357,7 +21450,7 @@ set(value) {
|
|
|
21357
21450
|
//#region src/components/grid_overlay/grid_overlay.ts
|
|
21358
21451
|
function useCellHovered(env, gridRef) {
|
|
21359
21452
|
const delayedHoveredCell = useStore(DelayedHoveredCellStore);
|
|
21360
|
-
const
|
|
21453
|
+
const cellHoverOverlay = useStore(CellHoverOverlayStore);
|
|
21361
21454
|
const viewStore = useStore(ViewportsStore);
|
|
21362
21455
|
const hoveredPosition = {
|
|
21363
21456
|
col: void 0,
|
|
@@ -21401,7 +21494,9 @@ set(value) {
|
|
|
21401
21494
|
if (isChildEvent(gridRef(), zoomedMouseEvent.ev)) {
|
|
21402
21495
|
({x, y} = getOffsetRelativeToOverlay(zoomedMouseEvent));
|
|
21403
21496
|
lastMoved = Date.now();
|
|
21404
|
-
|
|
21497
|
+
const position = getPosition();
|
|
21498
|
+
if (position.col < 0 || position.row < 0) cellHoverOverlay.hover(void 0);
|
|
21499
|
+
else cellHoverOverlay.hover(getPosition());
|
|
21405
21500
|
}
|
|
21406
21501
|
}
|
|
21407
21502
|
function recompute() {
|
|
@@ -21412,7 +21507,7 @@ set(value) {
|
|
|
21412
21507
|
const zoomedMouseEvent = withZoom(env, e);
|
|
21413
21508
|
const { x, y } = getOffsetRelativeToOverlay(zoomedMouseEvent);
|
|
21414
21509
|
const gridRect = getElBoundingRect(gridRef());
|
|
21415
|
-
if (y
|
|
21510
|
+
if (y <= 0 || y >= gridRect.height || x <= 0 || x >= gridRect.width) return updateMousePosition(zoomedMouseEvent);
|
|
21416
21511
|
else return pause();
|
|
21417
21512
|
}
|
|
21418
21513
|
(0, _odoo_owl.useListener)(gridRef, "pointermove", (ev) => !env.isMobile() && updateMousePosition(withZoom(env, ev)));
|
|
@@ -21449,7 +21544,7 @@ set(value) {
|
|
|
21449
21544
|
gridOverlayDimensions: types.string(),
|
|
21450
21545
|
hasFooter: types.boolean().optional(() => true)
|
|
21451
21546
|
});
|
|
21452
|
-
gridOverlayRef =
|
|
21547
|
+
gridOverlayRef = _odoo_owl.signal.ref();
|
|
21453
21548
|
cellPopovers;
|
|
21454
21549
|
paintFormatStore;
|
|
21455
21550
|
hoveredIconStore;
|
|
@@ -21476,7 +21571,7 @@ set(value) {
|
|
|
21476
21571
|
return el;
|
|
21477
21572
|
}
|
|
21478
21573
|
get style() {
|
|
21479
|
-
return this.props.gridOverlayDimensions + cssPropertiesToCss({ cursor: this.hoveredIconStore.hoveredIcon ? "pointer" : "
|
|
21574
|
+
return this.props.gridOverlayDimensions + cssPropertiesToCss({ cursor: this.hoveredIconStore.hoveredIcon ? "pointer" : "inherit" });
|
|
21480
21575
|
}
|
|
21481
21576
|
get isPaintingFormat() {
|
|
21482
21577
|
return this.paintFormatStore.isActive;
|
|
@@ -22116,8 +22211,9 @@ set(value) {
|
|
|
22116
22211
|
const CELL_ANIMATION_DURATION = 200;
|
|
22117
22212
|
var GridRenderer = class extends DisposableStore {
|
|
22118
22213
|
renderer;
|
|
22214
|
+
storeGetters = ["drawLayer"];
|
|
22119
22215
|
fingerprints;
|
|
22120
|
-
|
|
22216
|
+
cellHoverOverlayStore = this.get(CellHoverOverlayStore);
|
|
22121
22217
|
hoveredIcon;
|
|
22122
22218
|
lastRenderSheetId = void 0;
|
|
22123
22219
|
lastRenderBoxes = /* @__PURE__ */ new Map();
|
|
@@ -22131,15 +22227,14 @@ set(value) {
|
|
|
22131
22227
|
const model = get(ModelStore);
|
|
22132
22228
|
this.getters = model.getters;
|
|
22133
22229
|
this.fingerprints = get(FormulaFingerprintStore);
|
|
22134
|
-
this.hoveredTables = get(HoveredTableStore);
|
|
22135
22230
|
this.hoveredIcon = get(HoveredIconStore);
|
|
22136
22231
|
model.on("command-dispatched", this, this.handle);
|
|
22137
22232
|
model.on("command-finalized", this, this.finalize);
|
|
22138
|
-
this.renderer.register(this);
|
|
22233
|
+
if (this.renderer) this.renderer.register(this);
|
|
22139
22234
|
this.onDispose(() => {
|
|
22140
22235
|
model.off("command-dispatched", this);
|
|
22141
22236
|
model.off("command-finalized", this);
|
|
22142
|
-
this.renderer.unRegister(this);
|
|
22237
|
+
if (this.renderer) this.renderer.unRegister(this);
|
|
22143
22238
|
});
|
|
22144
22239
|
}
|
|
22145
22240
|
handle(cmd) {
|
|
@@ -22683,7 +22778,7 @@ set(value) {
|
|
|
22683
22778
|
border: this.getters.getCellComputedBorder(position) || void 0,
|
|
22684
22779
|
style,
|
|
22685
22780
|
dataBarFill,
|
|
22686
|
-
overlayColor: this.
|
|
22781
|
+
overlayColor: this.cellHoverOverlayStore.overlayColors.get(position),
|
|
22687
22782
|
isError: cell.type === "error" && !!cell.message || this.getters.isDataValidationInvalid(position),
|
|
22688
22783
|
icons: cellIcons,
|
|
22689
22784
|
disabledAnimation: this.zonesWithPreventedAnimationsInNextFrame[sheetId]?.some((z) => isZoneInside(zone, z) || overlap(zone, z))
|
|
@@ -22859,6 +22954,7 @@ set(value) {
|
|
|
22859
22954
|
return boxes;
|
|
22860
22955
|
}
|
|
22861
22956
|
getBoxesWithAnimations(boxes, oldBoxes, timeStamp) {
|
|
22957
|
+
if (!this.renderer) return boxes;
|
|
22862
22958
|
this.updateAnimationsProgress(timeStamp);
|
|
22863
22959
|
this.addNewAnimations(boxes, oldBoxes, timeStamp);
|
|
22864
22960
|
if (this.animations.size > 0) {
|
|
@@ -23003,7 +23099,7 @@ set(value) {
|
|
|
23003
23099
|
offset: types.Pixel(),
|
|
23004
23100
|
onScroll: types.function()
|
|
23005
23101
|
});
|
|
23006
|
-
scrollbarRef =
|
|
23102
|
+
scrollbarRef = _odoo_owl.signal.ref();
|
|
23007
23103
|
scrollbar;
|
|
23008
23104
|
setup() {
|
|
23009
23105
|
this.scrollbar = new ScrollBar$1(this.scrollbarRef(), this.props.direction);
|
|
@@ -23105,6 +23201,9 @@ set(value) {
|
|
|
23105
23201
|
zoneToDisplay: this.range.zone,
|
|
23106
23202
|
getFooterSize: () => 0
|
|
23107
23203
|
});
|
|
23204
|
+
const cellHoverOverlayStore = this.get(CellHoverOverlayStore);
|
|
23205
|
+
cellHoverOverlayStore.register(this);
|
|
23206
|
+
this.onDispose(() => cellHoverOverlayStore.unRegister(this));
|
|
23108
23207
|
}
|
|
23109
23208
|
handle(cmd) {
|
|
23110
23209
|
this.cachedColDimensions = void 0;
|
|
@@ -23253,6 +23352,16 @@ set(value) {
|
|
|
23253
23352
|
}
|
|
23254
23353
|
return normalizedWeights;
|
|
23255
23354
|
}
|
|
23355
|
+
getHighlightedPositions(position) {
|
|
23356
|
+
const highlightedPositions = [];
|
|
23357
|
+
const { col, row, sheetId } = position;
|
|
23358
|
+
if (sheetId !== this.range.sheetId || !isInside(col, row, this.range.zone)) return highlightedPositions;
|
|
23359
|
+
for (let col = this.range.zone.left; col <= this.range.zone.right; col++) highlightedPositions.push({
|
|
23360
|
+
...position,
|
|
23361
|
+
col
|
|
23362
|
+
});
|
|
23363
|
+
return highlightedPositions;
|
|
23364
|
+
}
|
|
23256
23365
|
};
|
|
23257
23366
|
|
|
23258
23367
|
//#endregion
|
|
@@ -23269,10 +23378,11 @@ set(value) {
|
|
|
23269
23378
|
range: types.Range(),
|
|
23270
23379
|
canResizeColumns: types.boolean().optional(true),
|
|
23271
23380
|
onResizeColumns: types.function().optional(),
|
|
23272
|
-
columnWeights: types.array().optional()
|
|
23381
|
+
columnWeights: types.array().optional(),
|
|
23382
|
+
size: types.object()
|
|
23273
23383
|
});
|
|
23274
|
-
canvasRef =
|
|
23275
|
-
containerRef =
|
|
23384
|
+
canvasRef = _odoo_owl.signal.ref(HTMLCanvasElement);
|
|
23385
|
+
containerRef = _odoo_owl.signal.ref();
|
|
23276
23386
|
store;
|
|
23277
23387
|
dndState = (0, _odoo_owl.proxy)({ col: void 0 });
|
|
23278
23388
|
onMouseWheel;
|
|
@@ -23284,8 +23394,8 @@ set(value) {
|
|
|
23284
23394
|
useChildStoreProvider([
|
|
23285
23395
|
ViewportsStore,
|
|
23286
23396
|
HoveredIconStore,
|
|
23287
|
-
HoveredTableStore,
|
|
23288
23397
|
ClickableCellsStore,
|
|
23398
|
+
CellHoverOverlayStore,
|
|
23289
23399
|
DelayedHoveredCellStore,
|
|
23290
23400
|
CellPopoverStore
|
|
23291
23401
|
]);
|
|
@@ -23294,18 +23404,10 @@ set(value) {
|
|
|
23294
23404
|
this.zoomStore = useStore(ZoomStore);
|
|
23295
23405
|
this.cellPopoverStore = useStore(CellPopoverStore);
|
|
23296
23406
|
this.rendererStore = useLocalStore(RendererStore, ["Background", "Chart"]);
|
|
23297
|
-
const resizeObserver = new ResizeObserver(() => {
|
|
23298
|
-
this.store.setContainerSize(this.containerWidth, this.containerHeight);
|
|
23299
|
-
});
|
|
23300
|
-
(0, _odoo_owl.useEffect)(() => {
|
|
23301
|
-
const el = this.containerRef();
|
|
23302
|
-
if (el) resizeObserver.observe(el);
|
|
23303
|
-
return () => resizeObserver.disconnect();
|
|
23304
|
-
});
|
|
23305
23407
|
(0, _odoo_owl.useEffect)(() => {
|
|
23306
23408
|
this.store.setRange(this.props.range);
|
|
23307
23409
|
if (this.dndState.col === void 0) this.store.setCustomColWeights(this.props.columnWeights);
|
|
23308
|
-
this.store.setContainerSize(this.
|
|
23410
|
+
this.store.setContainerSize(this.contentWidth, this.props.size.height);
|
|
23309
23411
|
});
|
|
23310
23412
|
useGridDrawing({
|
|
23311
23413
|
canvasRef: this.canvasRef,
|
|
@@ -23324,14 +23426,11 @@ set(value) {
|
|
|
23324
23426
|
}
|
|
23325
23427
|
});
|
|
23326
23428
|
}
|
|
23327
|
-
get
|
|
23328
|
-
return
|
|
23329
|
-
}
|
|
23330
|
-
get containerHeight() {
|
|
23331
|
-
return Math.floor(getElBoundingRect(this.containerRef()).height / this.zoomStore.zoomLevel);
|
|
23429
|
+
get contentWidth() {
|
|
23430
|
+
return this.hasVerticalScrollBar ? this.props.size.width - this.zoomStore.scrollBarWidth : this.props.size.width;
|
|
23332
23431
|
}
|
|
23333
23432
|
get hasVerticalScrollBar() {
|
|
23334
|
-
return this.
|
|
23433
|
+
return this.viewStore.mainViewportCoordinates.y + this.viewStore.mainViewportRect.height > this.props.size.height;
|
|
23335
23434
|
}
|
|
23336
23435
|
get scrollBarContainerStyle() {
|
|
23337
23436
|
return cssPropertiesToCss({ width: `${this.zoomStore.scrollBarWidth}px` });
|
|
@@ -23383,7 +23482,7 @@ set(value) {
|
|
|
23383
23482
|
};
|
|
23384
23483
|
const onMouseMove = (ev) => {
|
|
23385
23484
|
deltaX = withZoom(this.env, ev).clientX - initialX;
|
|
23386
|
-
const weightDelta = deltaX / this.
|
|
23485
|
+
const weightDelta = deltaX / this.props.size.width * totalWeight;
|
|
23387
23486
|
this.store.resizeColumn(resizer.col, weightDelta, startingColWeights);
|
|
23388
23487
|
};
|
|
23389
23488
|
startDnd(onMouseMove, onMouseUp);
|
|
@@ -23413,8 +23512,8 @@ set(value) {
|
|
|
23413
23512
|
name: types.string().optional(),
|
|
23414
23513
|
title: types.string().optional()
|
|
23415
23514
|
});
|
|
23416
|
-
selectRef =
|
|
23417
|
-
dropdownRef =
|
|
23515
|
+
selectRef = _odoo_owl.signal.ref();
|
|
23516
|
+
dropdownRef = _odoo_owl.signal.ref();
|
|
23418
23517
|
state = (0, _odoo_owl.proxy)({
|
|
23419
23518
|
isPopoverOpen: false,
|
|
23420
23519
|
hoveredValue: void 0
|
|
@@ -23524,7 +23623,7 @@ set(value) {
|
|
|
23524
23623
|
setup() {
|
|
23525
23624
|
useExternalListener(window, "click", this.onExternalClick, { capture: true });
|
|
23526
23625
|
}
|
|
23527
|
-
infoRef =
|
|
23626
|
+
infoRef = _odoo_owl.signal.ref();
|
|
23528
23627
|
get popoverProps() {
|
|
23529
23628
|
return {
|
|
23530
23629
|
anchorRect: this.props.anchorRect,
|
|
@@ -23669,8 +23768,8 @@ set(value) {
|
|
|
23669
23768
|
isFullScreen: types.boolean().optional(),
|
|
23670
23769
|
openContextMenu: types.function().optional()
|
|
23671
23770
|
});
|
|
23672
|
-
carouselTabsRef =
|
|
23673
|
-
carouselTabsDropdownRef =
|
|
23771
|
+
carouselTabsRef = _odoo_owl.signal.ref();
|
|
23772
|
+
carouselTabsDropdownRef = _odoo_owl.signal.ref();
|
|
23674
23773
|
menuState = (0, _odoo_owl.proxy)({
|
|
23675
23774
|
isOpen: false,
|
|
23676
23775
|
anchorRect: null,
|
|
@@ -23738,6 +23837,19 @@ set(value) {
|
|
|
23738
23837
|
} else cssProperties["background-color"] = backgroundColor;
|
|
23739
23838
|
return cssPropertiesToCss(cssProperties);
|
|
23740
23839
|
}
|
|
23840
|
+
get contentStyle() {
|
|
23841
|
+
return cssPropertiesToCss(this.rectToCss(this.carouselLayout.contentRect));
|
|
23842
|
+
}
|
|
23843
|
+
get headerStyle() {
|
|
23844
|
+
return cssPropertiesToCss({
|
|
23845
|
+
...this.rectToCss(this.carouselLayout.headerRect),
|
|
23846
|
+
"line-height": String(this.carouselLayout.headerLineHeight)
|
|
23847
|
+
});
|
|
23848
|
+
}
|
|
23849
|
+
get separatorStyle() {
|
|
23850
|
+
const separatorRect = this.carouselLayout.separatorRect;
|
|
23851
|
+
return separatorRect ? cssPropertiesToCss(this.rectToCss(separatorRect)) : "";
|
|
23852
|
+
}
|
|
23741
23853
|
get title() {
|
|
23742
23854
|
return this.env.model.getters.dynamicTranslate(this.carousel.title?.text ?? "");
|
|
23743
23855
|
}
|
|
@@ -23747,6 +23859,14 @@ set(value) {
|
|
|
23747
23859
|
...this.carousel.title
|
|
23748
23860
|
})));
|
|
23749
23861
|
}
|
|
23862
|
+
rectToCss(rect) {
|
|
23863
|
+
return {
|
|
23864
|
+
top: `${rect.y}px`,
|
|
23865
|
+
left: `${rect.x}px`,
|
|
23866
|
+
width: `${rect.width}px`,
|
|
23867
|
+
height: `${rect.height}px`
|
|
23868
|
+
};
|
|
23869
|
+
}
|
|
23750
23870
|
updateTabsVisibility() {
|
|
23751
23871
|
const tabsContainerEl = this.carouselTabsRef();
|
|
23752
23872
|
const dropDownEl = this.carouselTabsDropdownRef();
|
|
@@ -23838,6 +23958,13 @@ set(value) {
|
|
|
23838
23958
|
get canResizeDataViewColumns() {
|
|
23839
23959
|
return !this.env.model.getters.isReadonly() && !this.env.model.getters.isSheetLocked(this.env.model.getters.getActiveSheetId());
|
|
23840
23960
|
}
|
|
23961
|
+
get carouselLayout() {
|
|
23962
|
+
return getCarouselLayout({
|
|
23963
|
+
...this.props.figureUI,
|
|
23964
|
+
x: 0,
|
|
23965
|
+
y: 0
|
|
23966
|
+
}, this.carousel, this.selectedCarouselItem);
|
|
23967
|
+
}
|
|
23841
23968
|
};
|
|
23842
23969
|
|
|
23843
23970
|
//#endregion
|
|
@@ -23878,7 +24005,7 @@ set(value) {
|
|
|
23878
24005
|
figureUI: types.FigureUI(),
|
|
23879
24006
|
openContextMenu: types.function()
|
|
23880
24007
|
});
|
|
23881
|
-
menuButtonRef =
|
|
24008
|
+
menuButtonRef = _odoo_owl.signal.ref();
|
|
23882
24009
|
showMenu(ev) {
|
|
23883
24010
|
if (!this.env.model.getters.getSelectedFigureIds().includes(this.props.figureUI.id)) this.env.model.dispatch("SELECT_FIGURE", {
|
|
23884
24011
|
figureId: this.props.figureUI.id,
|
|
@@ -23906,7 +24033,8 @@ set(value) {
|
|
|
23906
24033
|
menuBuilder: getChartMenuActions,
|
|
23907
24034
|
borderWidth: (getters) => getters.isDashboard() ? 0 : 1,
|
|
23908
24035
|
hasShadow: (getters) => getters.isDashboard(),
|
|
23909
|
-
isRounded: (getters) => getters.isDashboard()
|
|
24036
|
+
isRounded: (getters) => getters.isDashboard(),
|
|
24037
|
+
isThemeDependant: true
|
|
23910
24038
|
});
|
|
23911
24039
|
figureRegistry.add("image", {
|
|
23912
24040
|
Component: ImageFigure,
|
|
@@ -23915,14 +24043,16 @@ set(value) {
|
|
|
23915
24043
|
borderWidth: () => 0,
|
|
23916
24044
|
hasShadow: () => false,
|
|
23917
24045
|
isRounded: () => false,
|
|
23918
|
-
menuBuilder: getImageMenuActions
|
|
24046
|
+
menuBuilder: getImageMenuActions,
|
|
24047
|
+
isThemeDependant: false
|
|
23919
24048
|
});
|
|
23920
24049
|
figureRegistry.add("carousel", {
|
|
23921
24050
|
Component: CarouselFigure,
|
|
23922
24051
|
menuBuilder: getCarouselMenuActions,
|
|
23923
24052
|
borderWidth: (getters) => getters.isDashboard() ? 0 : 1,
|
|
23924
24053
|
hasShadow: (getters) => getters.isDashboard(),
|
|
23925
|
-
isRounded: (getters) => getters.isDashboard()
|
|
24054
|
+
isRounded: (getters) => getters.isDashboard(),
|
|
24055
|
+
isThemeDependant: true
|
|
23926
24056
|
});
|
|
23927
24057
|
|
|
23928
24058
|
//#endregion
|
|
@@ -23944,10 +24074,13 @@ set(value) {
|
|
|
23944
24074
|
anchorRect: null,
|
|
23945
24075
|
menuItems: []
|
|
23946
24076
|
});
|
|
23947
|
-
figureRef =
|
|
24077
|
+
figureRef = _odoo_owl.signal.ref();
|
|
23948
24078
|
get isSelected() {
|
|
23949
24079
|
return !this.env.model.getters.isDashboard() && this.env.model.getters.getSelectedFigureIds().includes(this.props.figureUI.id);
|
|
23950
24080
|
}
|
|
24081
|
+
get isThemeDependant() {
|
|
24082
|
+
return figureRegistry.get(this.props.figureUI.tag).isThemeDependant;
|
|
24083
|
+
}
|
|
23951
24084
|
get figureRegistry() {
|
|
23952
24085
|
return figureRegistry;
|
|
23953
24086
|
}
|
|
@@ -29546,6 +29679,35 @@ set(value) {
|
|
|
29546
29679
|
}));
|
|
29547
29680
|
}
|
|
29548
29681
|
|
|
29682
|
+
//#endregion
|
|
29683
|
+
//#region src/helpers/figures/charts/chart_data_source_helpers.ts
|
|
29684
|
+
function getUnboundRange(getters, zone) {
|
|
29685
|
+
return zoneToXc(getters.getUnboundedZone(getters.getActiveSheetId(), zone));
|
|
29686
|
+
}
|
|
29687
|
+
function isDatasetTitled(getters, zone) {
|
|
29688
|
+
const sheetId = getters.getActiveSheetId();
|
|
29689
|
+
const cell = getters.getEvaluatedCell({
|
|
29690
|
+
sheetId,
|
|
29691
|
+
col: zone.left,
|
|
29692
|
+
row: zone.top
|
|
29693
|
+
});
|
|
29694
|
+
return !["number", "empty"].includes(cell.type);
|
|
29695
|
+
}
|
|
29696
|
+
function dataset(zone, getters, id = "0") {
|
|
29697
|
+
return {
|
|
29698
|
+
dataRange: getUnboundRange(getters, zone),
|
|
29699
|
+
dataSetId: id
|
|
29700
|
+
};
|
|
29701
|
+
}
|
|
29702
|
+
function rangeSource(dataSets, dataSetsHaveTitle, labelRange) {
|
|
29703
|
+
return {
|
|
29704
|
+
type: "range",
|
|
29705
|
+
dataSets,
|
|
29706
|
+
dataSetsHaveTitle,
|
|
29707
|
+
labelRange
|
|
29708
|
+
};
|
|
29709
|
+
}
|
|
29710
|
+
|
|
29549
29711
|
//#endregion
|
|
29550
29712
|
//#region src/helpers/figures/charts/smart_chart_engine.ts
|
|
29551
29713
|
const DEFAULT_BAR_CHART_CONFIG = {
|
|
@@ -29576,9 +29738,6 @@ set(value) {
|
|
|
29576
29738
|
labelsAsText: false,
|
|
29577
29739
|
humanize: true
|
|
29578
29740
|
};
|
|
29579
|
-
function getUnboundRange(getters, zone) {
|
|
29580
|
-
return zoneToXc(getters.getUnboundedZone(getters.getActiveSheetId(), zone));
|
|
29581
|
-
}
|
|
29582
29741
|
function detectColumnType(cells) {
|
|
29583
29742
|
if (!cells.length) return "empty";
|
|
29584
29743
|
const counts = {
|
|
@@ -29625,14 +29784,6 @@ set(value) {
|
|
|
29625
29784
|
totalCount: values.length
|
|
29626
29785
|
};
|
|
29627
29786
|
}
|
|
29628
|
-
function isDatasetTitled(getters, column) {
|
|
29629
|
-
const titleCell = getters.getEvaluatedCell({
|
|
29630
|
-
sheetId: getters.getActiveSheetId(),
|
|
29631
|
-
col: column.zone.left,
|
|
29632
|
-
row: column.zone.top
|
|
29633
|
-
});
|
|
29634
|
-
return !["number", "empty"].includes(titleCell.type);
|
|
29635
|
-
}
|
|
29636
29787
|
/**
|
|
29637
29788
|
* Builds a chart definition for a single column selection. The logic to detect the chart type is as follows:
|
|
29638
29789
|
* - If the column contains a single cell, create a scorecard.
|
|
@@ -29644,8 +29795,7 @@ set(value) {
|
|
|
29644
29795
|
function buildSingleColumnChart(column, getters) {
|
|
29645
29796
|
const { type, zone } = column;
|
|
29646
29797
|
const sheetId = getters.getActiveSheetId();
|
|
29647
|
-
const dataSetsHaveTitle = isDatasetTitled(getters, column);
|
|
29648
|
-
const dataRange = getUnboundRange(getters, zone);
|
|
29798
|
+
const dataSetsHaveTitle = isDatasetTitled(getters, column.zone);
|
|
29649
29799
|
const titleCell = getters.getEvaluatedCell({
|
|
29650
29800
|
sheetId,
|
|
29651
29801
|
col: zone.left,
|
|
@@ -29656,14 +29806,7 @@ set(value) {
|
|
|
29656
29806
|
case "percentage": return {
|
|
29657
29807
|
type: "pie",
|
|
29658
29808
|
title: dataSetsHaveTitle ? { text: String(titleCell.value) } : {},
|
|
29659
|
-
dataSource:
|
|
29660
|
-
type: "range",
|
|
29661
|
-
dataSets: [{
|
|
29662
|
-
dataRange,
|
|
29663
|
-
dataSetId: "0"
|
|
29664
|
-
}],
|
|
29665
|
-
dataSetsHaveTitle
|
|
29666
|
-
},
|
|
29809
|
+
dataSource: rangeSource([dataset(zone, getters)], dataSetsHaveTitle),
|
|
29667
29810
|
dataSetStyles: {},
|
|
29668
29811
|
legendPosition: "none"
|
|
29669
29812
|
};
|
|
@@ -29674,45 +29817,28 @@ set(value) {
|
|
|
29674
29817
|
return {
|
|
29675
29818
|
type: "pie",
|
|
29676
29819
|
title: hasUniqueTitle ? { text: String(titleCell.value) } : {},
|
|
29677
|
-
dataSource:
|
|
29678
|
-
type: "range",
|
|
29679
|
-
dataSets: [{
|
|
29680
|
-
dataRange,
|
|
29681
|
-
dataSetId: "0"
|
|
29682
|
-
}],
|
|
29683
|
-
labelRange: dataRange,
|
|
29684
|
-
dataSetsHaveTitle: hasUniqueTitle
|
|
29685
|
-
},
|
|
29820
|
+
dataSource: rangeSource([dataset(zone, getters)], hasUniqueTitle, getUnboundRange(getters, zone)),
|
|
29686
29821
|
dataSetStyles: {},
|
|
29687
29822
|
aggregated: true,
|
|
29688
29823
|
legendPosition: "top"
|
|
29689
29824
|
};
|
|
29690
|
-
case "date":
|
|
29691
|
-
|
|
29692
|
-
|
|
29693
|
-
|
|
29694
|
-
|
|
29695
|
-
|
|
29696
|
-
|
|
29697
|
-
|
|
29698
|
-
|
|
29699
|
-
|
|
29700
|
-
|
|
29701
|
-
|
|
29702
|
-
verticalGroupBy: "month_number"
|
|
29703
|
-
};
|
|
29825
|
+
case "date": {
|
|
29826
|
+
const dataRange = getUnboundRange(getters, zone);
|
|
29827
|
+
return {
|
|
29828
|
+
type: "calendar",
|
|
29829
|
+
title: dataSetsHaveTitle ? { text: String(titleCell.value) } : {},
|
|
29830
|
+
dataSource: rangeSource([], dataSetsHaveTitle, dataRange),
|
|
29831
|
+
dataSetStyles: {},
|
|
29832
|
+
legendPosition: "left",
|
|
29833
|
+
horizontalGroupBy: "day_of_week",
|
|
29834
|
+
verticalGroupBy: "month_number"
|
|
29835
|
+
};
|
|
29836
|
+
}
|
|
29704
29837
|
}
|
|
29705
29838
|
return {
|
|
29706
29839
|
...DEFAULT_BAR_CHART_CONFIG,
|
|
29707
29840
|
title: dataSetsHaveTitle ? { text: String(titleCell.value) } : {},
|
|
29708
|
-
dataSource:
|
|
29709
|
-
type: "range",
|
|
29710
|
-
dataSets: [{
|
|
29711
|
-
dataRange,
|
|
29712
|
-
dataSetId: "0"
|
|
29713
|
-
}],
|
|
29714
|
-
dataSetsHaveTitle
|
|
29715
|
-
},
|
|
29841
|
+
dataSource: rangeSource([dataset(zone, getters)], dataSetsHaveTitle),
|
|
29716
29842
|
dataSetStyles: {}
|
|
29717
29843
|
};
|
|
29718
29844
|
}
|
|
@@ -29730,15 +29856,7 @@ set(value) {
|
|
|
29730
29856
|
if (columns[1].type === "percentage") return {
|
|
29731
29857
|
type: "pie",
|
|
29732
29858
|
title: {},
|
|
29733
|
-
dataSource:
|
|
29734
|
-
type: "range",
|
|
29735
|
-
dataSets: [{
|
|
29736
|
-
dataRange: getUnboundRange(getters, columns[1].zone),
|
|
29737
|
-
dataSetId: "0"
|
|
29738
|
-
}],
|
|
29739
|
-
labelRange: getUnboundRange(getters, columns[0].zone),
|
|
29740
|
-
dataSetsHaveTitle: isDatasetTitled(getters, columns[1])
|
|
29741
|
-
},
|
|
29859
|
+
dataSource: rangeSource([dataset(columns[1].zone, getters)], isDatasetTitled(getters, columns[1].zone), getUnboundRange(getters, columns[0].zone)),
|
|
29742
29860
|
dataSetStyles: {},
|
|
29743
29861
|
aggregated: true,
|
|
29744
29862
|
legendPosition: "none"
|
|
@@ -29746,15 +29864,7 @@ set(value) {
|
|
|
29746
29864
|
if (columns[0].type === "number" && columns[1].type === "number") return {
|
|
29747
29865
|
type: "scatter",
|
|
29748
29866
|
title: {},
|
|
29749
|
-
dataSource:
|
|
29750
|
-
type: "range",
|
|
29751
|
-
dataSets: [{
|
|
29752
|
-
dataRange: getUnboundRange(getters, columns[1].zone),
|
|
29753
|
-
dataSetId: "0"
|
|
29754
|
-
}],
|
|
29755
|
-
labelRange: getUnboundRange(getters, columns[0].zone),
|
|
29756
|
-
dataSetsHaveTitle: isDatasetTitled(getters, columns[1])
|
|
29757
|
-
},
|
|
29867
|
+
dataSource: rangeSource([dataset(columns[1].zone, getters)], isDatasetTitled(getters, columns[1].zone), getUnboundRange(getters, columns[0].zone)),
|
|
29758
29868
|
dataSetStyles: {},
|
|
29759
29869
|
labelsAsText: false,
|
|
29760
29870
|
legendPosition: "none"
|
|
@@ -29762,49 +29872,25 @@ set(value) {
|
|
|
29762
29872
|
if (columns[0].type === "date" && columns[1].type === "number") return {
|
|
29763
29873
|
...DEFAULT_LINE_CHART_CONFIG,
|
|
29764
29874
|
type: "line",
|
|
29765
|
-
dataSource:
|
|
29766
|
-
type: "range",
|
|
29767
|
-
dataSets: [{
|
|
29768
|
-
dataRange: getUnboundRange(getters, columns[1].zone),
|
|
29769
|
-
dataSetId: "0"
|
|
29770
|
-
}],
|
|
29771
|
-
labelRange: getUnboundRange(getters, columns[0].zone),
|
|
29772
|
-
dataSetsHaveTitle: isDatasetTitled(getters, columns[0])
|
|
29773
|
-
},
|
|
29875
|
+
dataSource: rangeSource([dataset(columns[1].zone, getters)], isDatasetTitled(getters, columns[0].zone), getUnboundRange(getters, columns[0].zone)),
|
|
29774
29876
|
dataSetStyles: {}
|
|
29775
29877
|
};
|
|
29776
29878
|
if (columns[0].type === "text" && columns[1].type === "number") {
|
|
29777
29879
|
const textColumn = columns[0];
|
|
29778
29880
|
const numberColumn = columns[1];
|
|
29779
29881
|
const { uniqueCount, totalCount } = getCellStats(getters, textColumn.zone);
|
|
29780
|
-
const dataSetsHaveTitle = isDatasetTitled(getters, numberColumn);
|
|
29882
|
+
const dataSetsHaveTitle = isDatasetTitled(getters, numberColumn.zone);
|
|
29781
29883
|
if (uniqueCount !== totalCount) return {
|
|
29782
29884
|
type: "treemap",
|
|
29783
29885
|
title: {},
|
|
29784
|
-
dataSource:
|
|
29785
|
-
type: "range",
|
|
29786
|
-
dataSets: [{
|
|
29787
|
-
dataRange: getUnboundRange(getters, textColumn.zone),
|
|
29788
|
-
dataSetId: "0"
|
|
29789
|
-
}],
|
|
29790
|
-
labelRange: getUnboundRange(getters, numberColumn.zone),
|
|
29791
|
-
dataSetsHaveTitle
|
|
29792
|
-
},
|
|
29886
|
+
dataSource: rangeSource([dataset(textColumn.zone, getters)], dataSetsHaveTitle, getUnboundRange(getters, numberColumn.zone)),
|
|
29793
29887
|
dataSetStyles: {},
|
|
29794
29888
|
legendPosition: "none"
|
|
29795
29889
|
};
|
|
29796
29890
|
}
|
|
29797
29891
|
return {
|
|
29798
29892
|
...DEFAULT_BAR_CHART_CONFIG,
|
|
29799
|
-
dataSource:
|
|
29800
|
-
type: "range",
|
|
29801
|
-
dataSets: [{
|
|
29802
|
-
dataRange: getUnboundRange(getters, columns[1].zone),
|
|
29803
|
-
dataSetId: "0"
|
|
29804
|
-
}],
|
|
29805
|
-
labelRange: getUnboundRange(getters, columns[0].zone),
|
|
29806
|
-
dataSetsHaveTitle: isDatasetTitled(getters, columns[1])
|
|
29807
|
-
},
|
|
29893
|
+
dataSource: rangeSource([dataset(columns[1].zone, getters)], isDatasetTitled(getters, columns[1].zone), getUnboundRange(getters, columns[0].zone)),
|
|
29808
29894
|
dataSetStyles: {}
|
|
29809
29895
|
};
|
|
29810
29896
|
}
|
|
@@ -29818,42 +29904,26 @@ set(value) {
|
|
|
29818
29904
|
*/
|
|
29819
29905
|
function buildMultiColumnChart(columns, getters) {
|
|
29820
29906
|
if (columns.length < 3) throw new Error("buildMultiColumnChart expects at least three columns");
|
|
29821
|
-
const dataSetsHaveTitle = columns.some((col) => col.type !== "text" && isDatasetTitled(getters, col));
|
|
29907
|
+
const dataSetsHaveTitle = columns.some((col) => col.type !== "text" && isDatasetTitled(getters, col.zone));
|
|
29822
29908
|
const lastColumn = columns[columns.length - 1];
|
|
29823
29909
|
const columnsExceptLast = columns.slice(0, columns.length - 1);
|
|
29824
29910
|
if ((lastColumn.type === "percentage" || lastColumn.type === "number") && columnsExceptLast.every((col) => col.type === "text")) {
|
|
29825
|
-
const dataSets = columnsExceptLast.map(({ zone }, i) => (
|
|
29826
|
-
dataRange: getUnboundRange(getters, zone),
|
|
29827
|
-
dataSetId: i.toString()
|
|
29828
|
-
}));
|
|
29911
|
+
const dataSets = columnsExceptLast.map(({ zone }, i) => dataset(zone, getters, String(i)));
|
|
29829
29912
|
return {
|
|
29830
29913
|
type: columnsExceptLast.length >= 3 ? "sunburst" : "treemap",
|
|
29831
29914
|
title: {},
|
|
29832
|
-
dataSource:
|
|
29833
|
-
type: "range",
|
|
29834
|
-
dataSets,
|
|
29835
|
-
dataSetsHaveTitle,
|
|
29836
|
-
labelRange: getUnboundRange(getters, lastColumn.zone)
|
|
29837
|
-
},
|
|
29915
|
+
dataSource: rangeSource(dataSets, dataSetsHaveTitle, getUnboundRange(getters, lastColumn.zone)),
|
|
29838
29916
|
dataSetStyles: {},
|
|
29839
29917
|
legendPosition: "none"
|
|
29840
29918
|
};
|
|
29841
29919
|
}
|
|
29842
29920
|
const firstColumn = columns[0];
|
|
29843
29921
|
const columnsExceptFirst = columns.slice(1);
|
|
29844
|
-
const rangesOfColumnsExceptFirst = columnsExceptFirst.map(({ zone }, i) => (
|
|
29845
|
-
dataRange: getUnboundRange(getters, zone),
|
|
29846
|
-
dataSetId: i.toString()
|
|
29847
|
-
}));
|
|
29922
|
+
const rangesOfColumnsExceptFirst = columnsExceptFirst.map(({ zone }, i) => dataset(zone, getters, String(i)));
|
|
29848
29923
|
if (columnsExceptFirst.every((col) => col.type === "percentage")) return {
|
|
29849
29924
|
type: "pie",
|
|
29850
29925
|
title: {},
|
|
29851
|
-
dataSource:
|
|
29852
|
-
type: "range",
|
|
29853
|
-
dataSets: rangesOfColumnsExceptFirst,
|
|
29854
|
-
labelRange: getUnboundRange(getters, firstColumn.zone),
|
|
29855
|
-
dataSetsHaveTitle
|
|
29856
|
-
},
|
|
29926
|
+
dataSource: rangeSource(rangesOfColumnsExceptFirst, dataSetsHaveTitle, getUnboundRange(getters, firstColumn.zone)),
|
|
29857
29927
|
dataSetStyles: {},
|
|
29858
29928
|
aggregated: false,
|
|
29859
29929
|
legendPosition: "top"
|
|
@@ -29861,23 +29931,13 @@ set(value) {
|
|
|
29861
29931
|
if (firstColumn.type === "date" && columnsExceptFirst.every((col) => col.type === "number")) return {
|
|
29862
29932
|
...DEFAULT_LINE_CHART_CONFIG,
|
|
29863
29933
|
type: "line",
|
|
29864
|
-
dataSource:
|
|
29865
|
-
type: "range",
|
|
29866
|
-
dataSets: rangesOfColumnsExceptFirst,
|
|
29867
|
-
labelRange: getUnboundRange(getters, firstColumn.zone),
|
|
29868
|
-
dataSetsHaveTitle
|
|
29869
|
-
},
|
|
29934
|
+
dataSource: rangeSource(rangesOfColumnsExceptFirst, dataSetsHaveTitle, getUnboundRange(getters, firstColumn.zone)),
|
|
29870
29935
|
dataSetStyles: {},
|
|
29871
29936
|
legendPosition: "top"
|
|
29872
29937
|
};
|
|
29873
29938
|
return {
|
|
29874
29939
|
...DEFAULT_BAR_CHART_CONFIG,
|
|
29875
|
-
dataSource:
|
|
29876
|
-
type: "range",
|
|
29877
|
-
dataSets: rangesOfColumnsExceptFirst,
|
|
29878
|
-
labelRange: getUnboundRange(getters, firstColumn.zone),
|
|
29879
|
-
dataSetsHaveTitle
|
|
29880
|
-
},
|
|
29940
|
+
dataSource: rangeSource(rangesOfColumnsExceptFirst, dataSetsHaveTitle, getUnboundRange(getters, firstColumn.zone)),
|
|
29881
29941
|
dataSetStyles: {},
|
|
29882
29942
|
legendPosition: "top"
|
|
29883
29943
|
};
|
|
@@ -29904,17 +29964,10 @@ set(value) {
|
|
|
29904
29964
|
function getSmartChartDefinition(zones, getters) {
|
|
29905
29965
|
const columns = categorizeColumns(zones, getters);
|
|
29906
29966
|
if (columns.length === 0 || columns.every((col) => col.type === "empty")) {
|
|
29907
|
-
const dataSets = columns.map(({ zone }, i) => (
|
|
29908
|
-
dataRange: getUnboundRange(getters, zone),
|
|
29909
|
-
dataSetId: i.toString()
|
|
29910
|
-
}));
|
|
29967
|
+
const dataSets = columns.map(({ zone }, i) => dataset(zone, getters, String(i)));
|
|
29911
29968
|
return {
|
|
29912
29969
|
...DEFAULT_BAR_CHART_CONFIG,
|
|
29913
|
-
dataSource:
|
|
29914
|
-
type: "range",
|
|
29915
|
-
dataSets,
|
|
29916
|
-
dataSetsHaveTitle: false
|
|
29917
|
-
}
|
|
29970
|
+
dataSource: rangeSource(dataSets, false)
|
|
29918
29971
|
};
|
|
29919
29972
|
}
|
|
29920
29973
|
const nonEmptyColumns = columns.filter((col) => col.type !== "empty");
|
|
@@ -31602,7 +31655,7 @@ set(value) {
|
|
|
31602
31655
|
dragAndDrop = useDragAndDropListItems();
|
|
31603
31656
|
focusedInputRef = _odoo_owl.signal.ref(HTMLInputElement);
|
|
31604
31657
|
unfocusedInputRef = _odoo_owl.signal.ref(HTMLInputElement);
|
|
31605
|
-
selectionRef =
|
|
31658
|
+
selectionRef = _odoo_owl.signal.ref();
|
|
31606
31659
|
DOMFocusableElementStore;
|
|
31607
31660
|
store;
|
|
31608
31661
|
get ranges() {
|
|
@@ -32109,7 +32162,7 @@ set(value) {
|
|
|
32109
32162
|
dropdownMaxHeight: types.Pixel().optional(),
|
|
32110
32163
|
class: types.string().optional()
|
|
32111
32164
|
});
|
|
32112
|
-
colorPickerButtonRef =
|
|
32165
|
+
colorPickerButtonRef = _odoo_owl.signal.ref();
|
|
32113
32166
|
get iconStyle() {
|
|
32114
32167
|
return this.props.currentColor ? `border-color: ${this.props.currentColor}` : "border-bottom-style: hidden";
|
|
32115
32168
|
}
|
|
@@ -32137,8 +32190,8 @@ set(value) {
|
|
|
32137
32190
|
});
|
|
32138
32191
|
dropdown = (0, _odoo_owl.proxy)({ isOpen: false });
|
|
32139
32192
|
inputRef = _odoo_owl.signal.ref(HTMLInputElement);
|
|
32140
|
-
rootEditorRef =
|
|
32141
|
-
valueListRef =
|
|
32193
|
+
rootEditorRef = _odoo_owl.signal.ref();
|
|
32194
|
+
valueListRef = _odoo_owl.signal.ref();
|
|
32142
32195
|
DOMFocusableElementStore;
|
|
32143
32196
|
setup() {
|
|
32144
32197
|
this.DOMFocusableElementStore = useStore(DOMFocusableElementStore);
|
|
@@ -32444,7 +32497,7 @@ set(value) {
|
|
|
32444
32497
|
});
|
|
32445
32498
|
categories = chartCategories;
|
|
32446
32499
|
chartTypeByCategories = {};
|
|
32447
|
-
popoverRef =
|
|
32500
|
+
popoverRef = _odoo_owl.signal.ref();
|
|
32448
32501
|
setup() {
|
|
32449
32502
|
(0, _odoo_owl.useListener)(window, "pointerdown", this.onExternalClick.bind(this), { capture: true });
|
|
32450
32503
|
for (const subtypeProperties of chartSubtypeRegistry.getAll()) {
|
|
@@ -32469,7 +32522,7 @@ set(value) {
|
|
|
32469
32522
|
static template = "o-spreadsheet-CogWheelMenu";
|
|
32470
32523
|
static components = { MenuPopover };
|
|
32471
32524
|
props = (0, _odoo_owl.useProps)({ items: types.array(types.ActionSpec()) });
|
|
32472
|
-
buttonRef =
|
|
32525
|
+
buttonRef = _odoo_owl.signal.ref();
|
|
32473
32526
|
menuState = (0, _odoo_owl.proxy)({
|
|
32474
32527
|
isOpen: false,
|
|
32475
32528
|
anchorRect: null,
|
|
@@ -32513,8 +32566,8 @@ set(value) {
|
|
|
32513
32566
|
});
|
|
32514
32567
|
DEFAULT_CAROUSEL_TITLE_STYLE = DEFAULT_CAROUSEL_TITLE_STYLE;
|
|
32515
32568
|
dragAndDrop = useDragAndDropListItems();
|
|
32516
|
-
previewListRef =
|
|
32517
|
-
addChartButton =
|
|
32569
|
+
previewListRef = _odoo_owl.signal.ref();
|
|
32570
|
+
addChartButton = _odoo_owl.signal.ref(HTMLButtonElement);
|
|
32518
32571
|
state = (0, _odoo_owl.proxy)({
|
|
32519
32572
|
popoverProps: void 0,
|
|
32520
32573
|
currentRange: void 0
|
|
@@ -32958,7 +33011,7 @@ set(value) {
|
|
|
32958
33011
|
var Collapse = class extends Component {
|
|
32959
33012
|
static template = "o-spreadsheet-Collapse";
|
|
32960
33013
|
props = (0, _odoo_owl.useProps)({ isCollapsed: types.boolean() });
|
|
32961
|
-
contentRef =
|
|
33014
|
+
contentRef = _odoo_owl.signal.ref();
|
|
32962
33015
|
setup() {
|
|
32963
33016
|
(0, _odoo_owl.onMounted)(() => {
|
|
32964
33017
|
if (this.props.isCollapsed) this.contentRef()?.classList.add("d-none");
|
|
@@ -33011,7 +33064,7 @@ set(value) {
|
|
|
33011
33064
|
TextInput
|
|
33012
33065
|
};
|
|
33013
33066
|
props = (0, _odoo_owl.useProps)(chartSidePanelPropsDefinition);
|
|
33014
|
-
editorRef =
|
|
33067
|
+
editorRef = _odoo_owl.signal.ref();
|
|
33015
33068
|
state = (0, _odoo_owl.proxy)({
|
|
33016
33069
|
annotationTextInput: this.props.definition.annotationText || "",
|
|
33017
33070
|
isOverflowing: false
|
|
@@ -33333,7 +33386,7 @@ set(value) {
|
|
|
33333
33386
|
onColorPicked: types.function(),
|
|
33334
33387
|
disableNoColor: types.boolean().optional()
|
|
33335
33388
|
});
|
|
33336
|
-
colorPickerButtonRef =
|
|
33389
|
+
colorPickerButtonRef = _odoo_owl.signal.ref();
|
|
33337
33390
|
state;
|
|
33338
33391
|
setup() {
|
|
33339
33392
|
this.state = (0, _odoo_owl.proxy)({ pickerOpened: false });
|
|
@@ -36175,6 +36228,7 @@ set(value) {
|
|
|
36175
36228
|
getDefinedAxis: () => getDefinedAxis,
|
|
36176
36229
|
getPieColors: () => getPieColors,
|
|
36177
36230
|
isTrendLineAxis: () => isTrendLineAxis,
|
|
36231
|
+
limitChartConfigDataPoints: () => limitChartConfigDataPoints,
|
|
36178
36232
|
shouldRemoveFirstLabel: () => shouldRemoveFirstLabel,
|
|
36179
36233
|
toExcelDataset: () => toExcelDataset,
|
|
36180
36234
|
toExcelLabelRange: () => toExcelLabelRange,
|
|
@@ -36945,7 +36999,7 @@ set(value) {
|
|
|
36945
36999
|
popoverProps: void 0,
|
|
36946
37000
|
popoverStyle: ""
|
|
36947
37001
|
});
|
|
36948
|
-
popoverRef =
|
|
37002
|
+
popoverRef = _odoo_owl.signal.ref(HTMLTableElement);
|
|
36949
37003
|
setup() {
|
|
36950
37004
|
useExternalListener(window, "click", this.closePopover);
|
|
36951
37005
|
}
|
|
@@ -37194,7 +37248,7 @@ set(value) {
|
|
|
37194
37248
|
onValueSelected: types.function(),
|
|
37195
37249
|
onValueHovered: types.function()
|
|
37196
37250
|
});
|
|
37197
|
-
autoCompleteListRef =
|
|
37251
|
+
autoCompleteListRef = _odoo_owl.signal.ref();
|
|
37198
37252
|
setup() {
|
|
37199
37253
|
(0, _odoo_owl.useEffect)(() => {
|
|
37200
37254
|
const selectedIndex = this.props.selectedIndex;
|
|
@@ -37485,7 +37539,7 @@ set(value) {
|
|
|
37485
37539
|
anchorRect: types.Rect()
|
|
37486
37540
|
});
|
|
37487
37541
|
spreadsheetRect = useSpreadsheetRect();
|
|
37488
|
-
bubbleRef =
|
|
37542
|
+
bubbleRef = _odoo_owl.signal.ref();
|
|
37489
37543
|
setup() {
|
|
37490
37544
|
(0, _odoo_owl.useEffect)(() => {
|
|
37491
37545
|
const el = this.bubbleRef();
|
|
@@ -37530,8 +37584,8 @@ set(value) {
|
|
|
37530
37584
|
showAssistant: types.boolean().optional(true)
|
|
37531
37585
|
});
|
|
37532
37586
|
DOMFocusableElementStore;
|
|
37533
|
-
composerRef =
|
|
37534
|
-
containerRef =
|
|
37587
|
+
composerRef = _odoo_owl.signal.ref();
|
|
37588
|
+
containerRef = _odoo_owl.signal.ref();
|
|
37535
37589
|
contentHelper = new ContentEditableHelper(this.composerRef());
|
|
37536
37590
|
composerState = (0, _odoo_owl.proxy)({
|
|
37537
37591
|
positionStart: 0,
|
|
@@ -40033,7 +40087,7 @@ set(value) {
|
|
|
40033
40087
|
chartId: types.UID(),
|
|
40034
40088
|
chartPanelStore: types.Store()
|
|
40035
40089
|
});
|
|
40036
|
-
selectRef =
|
|
40090
|
+
selectRef = _odoo_owl.signal.ref();
|
|
40037
40091
|
state = (0, _odoo_owl.proxy)({ popoverProps: void 0 });
|
|
40038
40092
|
getSupportedChartTypes() {
|
|
40039
40093
|
let supportedTypes;
|
|
@@ -40601,7 +40655,7 @@ set(value) {
|
|
|
40601
40655
|
highlightPositions: []
|
|
40602
40656
|
});
|
|
40603
40657
|
store;
|
|
40604
|
-
chartCanvasRef =
|
|
40658
|
+
chartCanvasRef = _odoo_owl.signal.ref(HTMLCanvasElement);
|
|
40605
40659
|
chart;
|
|
40606
40660
|
setup() {
|
|
40607
40661
|
this.store = useStore(ColumnStatisticsStore);
|
|
@@ -42029,7 +42083,7 @@ set(value) {
|
|
|
42029
42083
|
class: types.string()
|
|
42030
42084
|
});
|
|
42031
42085
|
icons = ICONS;
|
|
42032
|
-
cfPreviewRef =
|
|
42086
|
+
cfPreviewRef = _odoo_owl.signal.ref();
|
|
42033
42087
|
setup() {
|
|
42034
42088
|
useHighlightsOnHover(this.cfPreviewRef, this);
|
|
42035
42089
|
}
|
|
@@ -42086,7 +42140,7 @@ set(value) {
|
|
|
42086
42140
|
static components = { ConditionalFormatPreview };
|
|
42087
42141
|
props = (0, _odoo_owl.useProps)({ onCloseSidePanel: types.function() });
|
|
42088
42142
|
dragAndDrop = useDragAndDropListItems();
|
|
42089
|
-
cfListRef =
|
|
42143
|
+
cfListRef = _odoo_owl.signal.ref();
|
|
42090
42144
|
get conditionalFormats() {
|
|
42091
42145
|
return this.env.model.getters.getConditionalFormats(this.env.model.getters.getActiveSheetId());
|
|
42092
42146
|
}
|
|
@@ -42146,6 +42200,1477 @@ set(value) {
|
|
|
42146
42200
|
}
|
|
42147
42201
|
};
|
|
42148
42202
|
|
|
42203
|
+
//#endregion
|
|
42204
|
+
//#region src/stores/chart_drag_store.ts
|
|
42205
|
+
/**
|
|
42206
|
+
* Holds the id of the figure that a dragged chart suggestion is currently
|
|
42207
|
+
* hovering over, so it can be highlighted reactively instead of through direct DOM manipulation.
|
|
42208
|
+
*/
|
|
42209
|
+
var ChartDragStore = class {
|
|
42210
|
+
mutators = ["setHighlightedFigure"];
|
|
42211
|
+
highlightedFigureId = void 0;
|
|
42212
|
+
setHighlightedFigure(figureId) {
|
|
42213
|
+
this.highlightedFigureId = figureId;
|
|
42214
|
+
}
|
|
42215
|
+
};
|
|
42216
|
+
|
|
42217
|
+
//#endregion
|
|
42218
|
+
//#region src/components/helpers/chart_drag_and_drop.ts
|
|
42219
|
+
function getDefaultChartFigureSize(type) {
|
|
42220
|
+
if (type === "scorecard") return {
|
|
42221
|
+
width: 213,
|
|
42222
|
+
height: 101
|
|
42223
|
+
};
|
|
42224
|
+
return {
|
|
42225
|
+
width: 536,
|
|
42226
|
+
height: 335
|
|
42227
|
+
};
|
|
42228
|
+
}
|
|
42229
|
+
function getCarouselOverlappingChart(figureUI, otherFigures, matchTags) {
|
|
42230
|
+
if (figureUI.tag !== "chart") return;
|
|
42231
|
+
const figureCenterX = figureUI.x + figureUI.width / 2;
|
|
42232
|
+
const figureCenterY = figureUI.y + figureUI.height / 2;
|
|
42233
|
+
let bestMatch;
|
|
42234
|
+
let smallestDistance = Infinity;
|
|
42235
|
+
for (const figure of otherFigures) {
|
|
42236
|
+
if (!matchTags.includes(figure.tag)) continue;
|
|
42237
|
+
const targetCenterX = figure.x + figure.width / 2;
|
|
42238
|
+
const targetCenterY = figure.y + figure.height / 2;
|
|
42239
|
+
const distanceX = Math.abs(figureCenterX - targetCenterX);
|
|
42240
|
+
const distanceY = Math.abs(figureCenterY - targetCenterY);
|
|
42241
|
+
const squaredDistance = distanceX ** 2 + distanceY ** 2;
|
|
42242
|
+
if (distanceX <= figureUI.width / 2 && distanceY <= figureUI.height / 2 && squaredDistance < smallestDistance) {
|
|
42243
|
+
smallestDistance = squaredDistance;
|
|
42244
|
+
bestMatch = figure;
|
|
42245
|
+
}
|
|
42246
|
+
}
|
|
42247
|
+
return bestMatch;
|
|
42248
|
+
}
|
|
42249
|
+
/**
|
|
42250
|
+
* Start dragging a floating preview of the given chart definition, following the mouse.
|
|
42251
|
+
* On drop, creates the chart on the grid (or inside a carousel if dropped on one).
|
|
42252
|
+
*/
|
|
42253
|
+
function startChartDragAndDrop(env, definition, ev) {
|
|
42254
|
+
const zoom = env.getStore(ZoomStore).zoomLevel;
|
|
42255
|
+
const gridPosition = gridOverlayPosition(zoom);
|
|
42256
|
+
const spreadsheet = document.querySelector(".o-spreadsheet");
|
|
42257
|
+
if (!spreadsheet) return;
|
|
42258
|
+
const startX = ev.clientX / zoom;
|
|
42259
|
+
const startY = ev.clientY / zoom;
|
|
42260
|
+
const { width, height } = getDefaultChartFigureSize(definition.type);
|
|
42261
|
+
const figureWidth = width * zoom;
|
|
42262
|
+
const figureHeight = height * zoom;
|
|
42263
|
+
const getters = env.model.getters;
|
|
42264
|
+
const sheetId = getters.getActiveSheetId();
|
|
42265
|
+
let container = null;
|
|
42266
|
+
let destroyChart = void 0;
|
|
42267
|
+
const chartDragStore = env.getStore(ChartDragStore);
|
|
42268
|
+
const previousCursor = document.body.style.cursor;
|
|
42269
|
+
document.body.style.cursor = "grabbing";
|
|
42270
|
+
/** Grid coordinates (in sheet pixels) of a mouse position, or undefined if outside the grid. */
|
|
42271
|
+
const getGridPosition = (clientX, clientY) => {
|
|
42272
|
+
if (clientX > gridPosition.x + gridPosition.width || clientY > gridPosition.y + gridPosition.height) return;
|
|
42273
|
+
const { scrollX, scrollY } = env.getStore(ViewportsStore).activeSheetScrollInfo;
|
|
42274
|
+
return {
|
|
42275
|
+
x: Math.max(0, (clientX - gridPosition.x) / zoom + scrollX),
|
|
42276
|
+
y: Math.max(0, (clientY - gridPosition.y) / zoom + scrollY)
|
|
42277
|
+
};
|
|
42278
|
+
};
|
|
42279
|
+
/** Carousel or standalone chart the dragged chart is dropped onto, if any. */
|
|
42280
|
+
const getOverlappingFigure = (clientX, clientY) => {
|
|
42281
|
+
const position = getGridPosition(clientX, clientY);
|
|
42282
|
+
if (!position) return;
|
|
42283
|
+
const figureUI = {
|
|
42284
|
+
tag: "chart",
|
|
42285
|
+
...position,
|
|
42286
|
+
width,
|
|
42287
|
+
height
|
|
42288
|
+
};
|
|
42289
|
+
const otherFigures = env.getStore(ViewportsStore).visibleFigures;
|
|
42290
|
+
return getCarouselOverlappingChart(figureUI, otherFigures, ["carousel", "chart"]);
|
|
42291
|
+
};
|
|
42292
|
+
const halfWidth = figureWidth / 2;
|
|
42293
|
+
const halfHeight = figureHeight / 2;
|
|
42294
|
+
const onMouseMove = (e) => {
|
|
42295
|
+
if (Math.abs(e.clientX - startX) <= 5 && Math.abs(e.clientY - startY) <= 5) return;
|
|
42296
|
+
if (container === null) {
|
|
42297
|
+
container = document.createElement("div");
|
|
42298
|
+
container.className = "o-chart-drag-preview os-theme-dependant position-fixed border pe-none";
|
|
42299
|
+
container.style.width = `${width}px`;
|
|
42300
|
+
container.style.height = `${height}px`;
|
|
42301
|
+
container.style.zoom = `${zoom}`;
|
|
42302
|
+
const canvas = document.createElement("canvas");
|
|
42303
|
+
canvas.className = "w-100 h-100";
|
|
42304
|
+
container.appendChild(canvas);
|
|
42305
|
+
spreadsheet.appendChild(container);
|
|
42306
|
+
destroyChart = drawChartOnCanvas(canvas, SpreadsheetChart.fromStrDefinition(getters, sheetId, definition).getRuntime(getters, "newChart"), {
|
|
42307
|
+
width,
|
|
42308
|
+
height
|
|
42309
|
+
}, definition.type, zoom);
|
|
42310
|
+
}
|
|
42311
|
+
container.style.left = `${Math.max(gridPosition.x, (e.clientX - halfWidth) / zoom)}px`;
|
|
42312
|
+
container.style.top = `${Math.max(gridPosition.y, (e.clientY - halfHeight) / zoom)}px`;
|
|
42313
|
+
const overlappingFigure = getOverlappingFigure(e.clientX - halfWidth, e.clientY - halfHeight);
|
|
42314
|
+
container.style.opacity = overlappingFigure?.id ? "0.6" : "0.9";
|
|
42315
|
+
chartDragStore.setHighlightedFigure(overlappingFigure?.id);
|
|
42316
|
+
};
|
|
42317
|
+
const onMouseUp = (mouseEvent) => {
|
|
42318
|
+
chartDragStore.setHighlightedFigure(void 0);
|
|
42319
|
+
if (container !== null) {
|
|
42320
|
+
spreadsheet.removeChild(container);
|
|
42321
|
+
container = null;
|
|
42322
|
+
}
|
|
42323
|
+
destroyChart?.();
|
|
42324
|
+
document.body.style.cursor = previousCursor;
|
|
42325
|
+
let position = getGridPosition(mouseEvent.clientX - halfWidth, mouseEvent.clientY - halfHeight);
|
|
42326
|
+
if (Math.abs(mouseEvent.clientX / zoom - startX) <= 5 && Math.abs(mouseEvent.clientY / zoom - startY) <= 5) position = {
|
|
42327
|
+
x: 0,
|
|
42328
|
+
y: 0
|
|
42329
|
+
};
|
|
42330
|
+
else if (!position || position.x + halfWidth > gridPosition.width) return;
|
|
42331
|
+
const { col, row, offset } = env.getStore(ViewportsStore).viewports.getPositionAnchorOffset(sheetId, position);
|
|
42332
|
+
const payload = {
|
|
42333
|
+
chartId: UuidGenerator.smallUuid(),
|
|
42334
|
+
figureId: UuidGenerator.smallUuid(),
|
|
42335
|
+
sheetId,
|
|
42336
|
+
size: {
|
|
42337
|
+
width,
|
|
42338
|
+
height
|
|
42339
|
+
},
|
|
42340
|
+
definition,
|
|
42341
|
+
col,
|
|
42342
|
+
row,
|
|
42343
|
+
offset
|
|
42344
|
+
};
|
|
42345
|
+
const overlappingFigure = getOverlappingFigure(mouseEvent.clientX - halfWidth, mouseEvent.clientY - halfHeight);
|
|
42346
|
+
if (overlappingFigure?.tag === "carousel") env.model.dispatch("ADD_NEW_CHART_TO_CAROUSEL", {
|
|
42347
|
+
sheetId,
|
|
42348
|
+
figureId: overlappingFigure.id,
|
|
42349
|
+
newChartId: UuidGenerator.smallUuid(),
|
|
42350
|
+
chartDefinition: definition
|
|
42351
|
+
});
|
|
42352
|
+
else if (overlappingFigure?.tag === "chart") env.model.dispatch("CREATE_CHART_AND_MERGE_INTO_CAROUSEL", {
|
|
42353
|
+
chartId: payload.chartId,
|
|
42354
|
+
figureId: payload.figureId,
|
|
42355
|
+
sheetId: payload.sheetId,
|
|
42356
|
+
definition: payload.definition,
|
|
42357
|
+
baseFigureId: overlappingFigure.id
|
|
42358
|
+
});
|
|
42359
|
+
else env.model.dispatch("CREATE_CHART", payload);
|
|
42360
|
+
};
|
|
42361
|
+
startDnd(onMouseMove, onMouseUp);
|
|
42362
|
+
}
|
|
42363
|
+
|
|
42364
|
+
//#endregion
|
|
42365
|
+
//#region src/components/side_panel/data_analysis/chart_suggestion_preview.ts
|
|
42366
|
+
const PREVIEW_BUBBLE_RADIUS_RATIO = .3;
|
|
42367
|
+
const MIN_PREVIEW_BUBBLE_RADIUS = 1;
|
|
42368
|
+
var ChartSuggestionPreview = class extends Component {
|
|
42369
|
+
static template = "o-spreadsheet-ChartSuggestionPreview";
|
|
42370
|
+
props = (0, _odoo_owl.useProps)({
|
|
42371
|
+
definition: types.ChartDefinition(),
|
|
42372
|
+
description: types.string(),
|
|
42373
|
+
onPointerDown: types.function()
|
|
42374
|
+
});
|
|
42375
|
+
chartCanvasRef = _odoo_owl.signal.ref(HTMLCanvasElement);
|
|
42376
|
+
chart;
|
|
42377
|
+
setup() {
|
|
42378
|
+
(0, _odoo_owl.onMounted)(() => registerChartJSExtensions());
|
|
42379
|
+
(0, _odoo_owl.onWillUnmount)(() => this.destroyChart());
|
|
42380
|
+
useLayoutEffect(() => {
|
|
42381
|
+
this.updateChart();
|
|
42382
|
+
}, () => [this.props.definition]);
|
|
42383
|
+
}
|
|
42384
|
+
getChartConfiguration() {
|
|
42385
|
+
const getters = this.env.model.getters;
|
|
42386
|
+
const activeSheetId = getters.getActiveSheetId();
|
|
42387
|
+
const runtime = SpreadsheetChart.fromStrDefinition(getters, activeSheetId, this.props.definition).getRuntime(getters, "myChart");
|
|
42388
|
+
if (!("chartJsConfig" in runtime)) return null;
|
|
42389
|
+
let config = runtime.chartJsConfig;
|
|
42390
|
+
const existingScales = config.options?.scales ?? {};
|
|
42391
|
+
config = {
|
|
42392
|
+
...config,
|
|
42393
|
+
data: this.props.definition.type === "bubble" ? {
|
|
42394
|
+
...config.data,
|
|
42395
|
+
datasets: (config.data?.datasets || []).map((dataset) => ({
|
|
42396
|
+
...dataset,
|
|
42397
|
+
pointRadius: this.scaleBubblePointRadius(dataset.pointRadius),
|
|
42398
|
+
pointHoverRadius: this.scaleBubblePointRadius(dataset.pointHoverRadius)
|
|
42399
|
+
}))
|
|
42400
|
+
} : config.data,
|
|
42401
|
+
options: {
|
|
42402
|
+
...config.options,
|
|
42403
|
+
plugins: {
|
|
42404
|
+
...config.options?.plugins,
|
|
42405
|
+
legend: { display: false },
|
|
42406
|
+
title: { display: false },
|
|
42407
|
+
tooltip: { enabled: false }
|
|
42408
|
+
},
|
|
42409
|
+
events: [],
|
|
42410
|
+
animation: false,
|
|
42411
|
+
scales: this.getScalesCongif(existingScales)
|
|
42412
|
+
}
|
|
42413
|
+
};
|
|
42414
|
+
return limitChartConfigDataPoints(config);
|
|
42415
|
+
}
|
|
42416
|
+
getScalesCongif(scales) {
|
|
42417
|
+
if (scales.x) scales.x = {
|
|
42418
|
+
...scales.x,
|
|
42419
|
+
ticks: { display: false },
|
|
42420
|
+
border: { display: false }
|
|
42421
|
+
};
|
|
42422
|
+
if (scales.y) scales.y = {
|
|
42423
|
+
...scales.y,
|
|
42424
|
+
ticks: { display: false },
|
|
42425
|
+
border: { display: false }
|
|
42426
|
+
};
|
|
42427
|
+
if (scales.r) scales.r = {
|
|
42428
|
+
...scales.r,
|
|
42429
|
+
ticks: { display: false },
|
|
42430
|
+
pointLabels: { display: false }
|
|
42431
|
+
};
|
|
42432
|
+
return scales;
|
|
42433
|
+
}
|
|
42434
|
+
scaleBubblePointRadius(pointRadius) {
|
|
42435
|
+
if (Array.isArray(pointRadius)) return pointRadius.map((radius) => radius <= 0 ? radius : Math.max(MIN_PREVIEW_BUBBLE_RADIUS, radius * PREVIEW_BUBBLE_RADIUS_RATIO));
|
|
42436
|
+
if (typeof pointRadius === "number") return pointRadius <= 0 ? pointRadius : Math.max(MIN_PREVIEW_BUBBLE_RADIUS, pointRadius * PREVIEW_BUBBLE_RADIUS_RATIO);
|
|
42437
|
+
return pointRadius ?? MIN_PREVIEW_BUBBLE_RADIUS;
|
|
42438
|
+
}
|
|
42439
|
+
updateChart() {
|
|
42440
|
+
this.destroyChart();
|
|
42441
|
+
const config = this.getChartConfiguration();
|
|
42442
|
+
if (!config) {
|
|
42443
|
+
this.drawNativeChart();
|
|
42444
|
+
return;
|
|
42445
|
+
}
|
|
42446
|
+
const ctx = this.chartCanvasRef()?.getContext("2d");
|
|
42447
|
+
if (!ctx || !globalThis.Chart) return;
|
|
42448
|
+
this.chart = new globalThis.Chart(ctx, config);
|
|
42449
|
+
}
|
|
42450
|
+
drawNativeChart() {
|
|
42451
|
+
const canvas = this.chartCanvasRef();
|
|
42452
|
+
if (!canvas) return;
|
|
42453
|
+
const getters = this.env.model.getters;
|
|
42454
|
+
let runtime = SpreadsheetChart.fromStrDefinition(getters, getters.getActiveSheetId(), this.props.definition).getRuntime(getters, "myChart");
|
|
42455
|
+
const { type } = this.props.definition;
|
|
42456
|
+
if (type === "scorecard") {
|
|
42457
|
+
const rect = canvas.getBoundingClientRect();
|
|
42458
|
+
runtime = {
|
|
42459
|
+
...runtime,
|
|
42460
|
+
keyValueStyle: {
|
|
42461
|
+
...runtime.keyValueStyle,
|
|
42462
|
+
fontSize: 16
|
|
42463
|
+
}
|
|
42464
|
+
};
|
|
42465
|
+
drawScoreChart(getScorecardConfiguration({
|
|
42466
|
+
width: rect.width || 130,
|
|
42467
|
+
height: rect.height || 120
|
|
42468
|
+
}, runtime), canvas);
|
|
42469
|
+
} else if (type === "gauge") {
|
|
42470
|
+
let gaugeRuntime = runtime;
|
|
42471
|
+
if (gaugeRuntime.title) gaugeRuntime = {
|
|
42472
|
+
...gaugeRuntime,
|
|
42473
|
+
title: { text: "" }
|
|
42474
|
+
};
|
|
42475
|
+
drawGaugeChart(canvas, gaugeRuntime, 1, void 0, { labelFontSize: 8 });
|
|
42476
|
+
}
|
|
42477
|
+
}
|
|
42478
|
+
destroyChart() {
|
|
42479
|
+
this.chart?.destroy();
|
|
42480
|
+
this.chart = void 0;
|
|
42481
|
+
}
|
|
42482
|
+
};
|
|
42483
|
+
|
|
42484
|
+
//#endregion
|
|
42485
|
+
//#region src/helpers/data_analysis.ts
|
|
42486
|
+
function analyzeColumns(zones, getters) {
|
|
42487
|
+
return getZonesByColumns(zones).map((zone) => analyzeColumn(zone, getters));
|
|
42488
|
+
}
|
|
42489
|
+
function analyzeColumn(zone, getters) {
|
|
42490
|
+
const sheetId = getters.getActiveSheetId();
|
|
42491
|
+
const cells = getters.getEvaluatedCellsInZone(sheetId, zone).filter((c) => c.type !== "empty");
|
|
42492
|
+
if (!cells.length) return {
|
|
42493
|
+
zone,
|
|
42494
|
+
type: "empty",
|
|
42495
|
+
hasHeader: false,
|
|
42496
|
+
rowCount: 0,
|
|
42497
|
+
uniqueCount: 0,
|
|
42498
|
+
uniqueRatio: 0,
|
|
42499
|
+
nonEmpty: []
|
|
42500
|
+
};
|
|
42501
|
+
const firstCell = cells[0];
|
|
42502
|
+
const rest = cells.slice(1);
|
|
42503
|
+
const hasHeader = firstCell.type === "text" && rest.some((c) => c.type !== "text");
|
|
42504
|
+
const dataCells = hasHeader ? rest : cells;
|
|
42505
|
+
const numericValues = dataCells.filter((c) => c.type === "number").map((c) => c.value);
|
|
42506
|
+
const allVals = dataCells.map((c) => String(c.value ?? ""));
|
|
42507
|
+
const uniqueCount = new Set(allVals).size;
|
|
42508
|
+
return {
|
|
42509
|
+
zone,
|
|
42510
|
+
type: computeColumnType(dataCells),
|
|
42511
|
+
header: hasHeader ? firstCell.value : void 0,
|
|
42512
|
+
hasHeader,
|
|
42513
|
+
rowCount: dataCells.length,
|
|
42514
|
+
uniqueCount,
|
|
42515
|
+
uniqueRatio: allVals.length > 0 ? uniqueCount / allVals.length : 0,
|
|
42516
|
+
maxValue: numericValues.length ? numericValues.reduce((max, v) => v > max ? v : max, numericValues[0]) : void 0,
|
|
42517
|
+
nonEmpty: dataCells
|
|
42518
|
+
};
|
|
42519
|
+
}
|
|
42520
|
+
function computeColumnType(cells) {
|
|
42521
|
+
if (cells.length === 0) return "empty";
|
|
42522
|
+
if (cells.every((c) => c.type === "error")) return "error";
|
|
42523
|
+
cells = cells.filter((c) => c.type !== "error");
|
|
42524
|
+
if (cells.every((c) => c.type === "boolean")) return "boolean";
|
|
42525
|
+
if (cells.every((c) => c.type === "number" && !!c.format && isDateTimeFormat(c.format))) return "date";
|
|
42526
|
+
else if (cells.every((c) => c.type === "number")) {
|
|
42527
|
+
if (cells.every((c) => c.format?.includes("%"))) return "percentage";
|
|
42528
|
+
return "number";
|
|
42529
|
+
} else {
|
|
42530
|
+
const textVals = cells.filter((c) => c.type === "text").map((c) => c.value);
|
|
42531
|
+
if (textVals.length > 0) {
|
|
42532
|
+
const unique = new Set(textVals).size;
|
|
42533
|
+
return unique / textVals.length < .75 && unique <= 20 ? "categorical" : "label";
|
|
42534
|
+
}
|
|
42535
|
+
}
|
|
42536
|
+
return "empty";
|
|
42537
|
+
}
|
|
42538
|
+
|
|
42539
|
+
//#endregion
|
|
42540
|
+
//#region src/helpers/figures/charts/charts_suggestions_constants.ts
|
|
42541
|
+
/** Above this many rows, shape-based charts (radar, scatter, pyramid) become unreadable. */
|
|
42542
|
+
const MAX_ROWS_FOR_SHAPE_CHART = 10;
|
|
42543
|
+
function barChart(titleText, source, opts = {}) {
|
|
42544
|
+
return {
|
|
42545
|
+
...opts,
|
|
42546
|
+
type: "bar",
|
|
42547
|
+
title: { text: titleText },
|
|
42548
|
+
dataSource: source,
|
|
42549
|
+
dataSetStyles: {},
|
|
42550
|
+
legendPosition: opts.legendPosition ?? "none",
|
|
42551
|
+
stacked: opts.stacked ?? false,
|
|
42552
|
+
humanize: true,
|
|
42553
|
+
aggregated: opts.aggregated ?? false
|
|
42554
|
+
};
|
|
42555
|
+
}
|
|
42556
|
+
function lineChart(titleText, source, opts = {}) {
|
|
42557
|
+
return {
|
|
42558
|
+
...opts,
|
|
42559
|
+
type: "line",
|
|
42560
|
+
title: { text: titleText },
|
|
42561
|
+
dataSource: source,
|
|
42562
|
+
dataSetStyles: {},
|
|
42563
|
+
legendPosition: opts.legendPosition ?? "none",
|
|
42564
|
+
stacked: opts.stacked ?? false,
|
|
42565
|
+
cumulative: opts.cumulative ?? false,
|
|
42566
|
+
labelsAsText: false,
|
|
42567
|
+
humanize: true
|
|
42568
|
+
};
|
|
42569
|
+
}
|
|
42570
|
+
function pieChart(titleText, source, opts = {}) {
|
|
42571
|
+
return {
|
|
42572
|
+
...opts,
|
|
42573
|
+
type: "pie",
|
|
42574
|
+
title: { text: titleText },
|
|
42575
|
+
dataSource: source,
|
|
42576
|
+
dataSetStyles: {},
|
|
42577
|
+
legendPosition: opts.legendPosition ?? "top",
|
|
42578
|
+
humanize: true
|
|
42579
|
+
};
|
|
42580
|
+
}
|
|
42581
|
+
function radarChart(titleText, source, opts = {}) {
|
|
42582
|
+
return {
|
|
42583
|
+
...opts,
|
|
42584
|
+
type: "radar",
|
|
42585
|
+
title: { text: titleText },
|
|
42586
|
+
dataSource: source,
|
|
42587
|
+
dataSetStyles: {},
|
|
42588
|
+
legendPosition: opts.legendPosition ?? "none",
|
|
42589
|
+
stacked: false,
|
|
42590
|
+
humanize: true
|
|
42591
|
+
};
|
|
42592
|
+
}
|
|
42593
|
+
function sunburstChart(titleText, source) {
|
|
42594
|
+
return {
|
|
42595
|
+
type: "sunburst",
|
|
42596
|
+
title: { text: titleText },
|
|
42597
|
+
dataSource: source,
|
|
42598
|
+
dataSetStyles: {},
|
|
42599
|
+
legendPosition: "none"
|
|
42600
|
+
};
|
|
42601
|
+
}
|
|
42602
|
+
function treemapChart(titleText, source) {
|
|
42603
|
+
return {
|
|
42604
|
+
type: "treemap",
|
|
42605
|
+
title: { text: titleText },
|
|
42606
|
+
dataSource: source,
|
|
42607
|
+
dataSetStyles: {},
|
|
42608
|
+
legendPosition: "none"
|
|
42609
|
+
};
|
|
42610
|
+
}
|
|
42611
|
+
function calendarChart(titleText, source) {
|
|
42612
|
+
return {
|
|
42613
|
+
type: "calendar",
|
|
42614
|
+
title: { text: titleText },
|
|
42615
|
+
dataSource: source,
|
|
42616
|
+
dataSetStyles: {},
|
|
42617
|
+
legendPosition: "none",
|
|
42618
|
+
horizontalGroupBy: "month_number",
|
|
42619
|
+
verticalGroupBy: "day_of_week",
|
|
42620
|
+
humanize: true
|
|
42621
|
+
};
|
|
42622
|
+
}
|
|
42623
|
+
function scatterChart(titleText, source) {
|
|
42624
|
+
return {
|
|
42625
|
+
type: "scatter",
|
|
42626
|
+
title: { text: titleText },
|
|
42627
|
+
dataSource: source,
|
|
42628
|
+
dataSetStyles: {},
|
|
42629
|
+
legendPosition: "none",
|
|
42630
|
+
labelsAsText: false,
|
|
42631
|
+
humanize: true
|
|
42632
|
+
};
|
|
42633
|
+
}
|
|
42634
|
+
function pyramidChart(titleText, source, opts = {}) {
|
|
42635
|
+
return {
|
|
42636
|
+
type: "pyramid",
|
|
42637
|
+
title: { text: titleText },
|
|
42638
|
+
dataSource: source,
|
|
42639
|
+
dataSetStyles: {},
|
|
42640
|
+
legendPosition: opts.legendPosition ?? "top",
|
|
42641
|
+
stacked: false,
|
|
42642
|
+
horizontal: true,
|
|
42643
|
+
humanize: true
|
|
42644
|
+
};
|
|
42645
|
+
}
|
|
42646
|
+
function comboChart(titleText, source, opts = {}) {
|
|
42647
|
+
return {
|
|
42648
|
+
...opts,
|
|
42649
|
+
type: "combo",
|
|
42650
|
+
title: { text: titleText },
|
|
42651
|
+
dataSource: source,
|
|
42652
|
+
dataSetStyles: {},
|
|
42653
|
+
legendPosition: "top",
|
|
42654
|
+
humanize: true
|
|
42655
|
+
};
|
|
42656
|
+
}
|
|
42657
|
+
function bubbleChart(titleText, xRange, yRanges, sizeRange, labelRange, dataSetsHaveTitle) {
|
|
42658
|
+
return {
|
|
42659
|
+
type: "bubble",
|
|
42660
|
+
title: { text: titleText },
|
|
42661
|
+
humanize: true,
|
|
42662
|
+
dataSetsHaveTitle,
|
|
42663
|
+
yRanges,
|
|
42664
|
+
xRange,
|
|
42665
|
+
sizeRange,
|
|
42666
|
+
labelRange,
|
|
42667
|
+
labelsAsText: false,
|
|
42668
|
+
legendPosition: "none",
|
|
42669
|
+
bubbleColor: { color: "multiple" },
|
|
42670
|
+
verticalAxisPosition: "left"
|
|
42671
|
+
};
|
|
42672
|
+
}
|
|
42673
|
+
function scorecardChart(titleText, keyValue, opts = {}) {
|
|
42674
|
+
return {
|
|
42675
|
+
...opts,
|
|
42676
|
+
type: "scorecard",
|
|
42677
|
+
title: { text: titleText },
|
|
42678
|
+
keyValue,
|
|
42679
|
+
baselineMode: opts.baselineMode ?? "difference",
|
|
42680
|
+
baselineColorUp: DEFAULT_SCORECARD_BASELINE_COLOR_UP,
|
|
42681
|
+
baselineColorDown: DEFAULT_SCORECARD_BASELINE_COLOR_DOWN,
|
|
42682
|
+
humanize: opts.humanize ?? true
|
|
42683
|
+
};
|
|
42684
|
+
}
|
|
42685
|
+
function gaugeChart(titleText, dataRange, rangeMin, rangeMax) {
|
|
42686
|
+
return {
|
|
42687
|
+
type: "gauge",
|
|
42688
|
+
title: { text: titleText },
|
|
42689
|
+
dataRange,
|
|
42690
|
+
sectionRule: {
|
|
42691
|
+
colors: {
|
|
42692
|
+
lowerColor: DEFAULT_GAUGE_LOWER_COLOR,
|
|
42693
|
+
middleColor: DEFAULT_GAUGE_MIDDLE_COLOR,
|
|
42694
|
+
upperColor: DEFAULT_GAUGE_UPPER_COLOR
|
|
42695
|
+
},
|
|
42696
|
+
rangeMin,
|
|
42697
|
+
rangeMax,
|
|
42698
|
+
lowerInflectionPoint: {
|
|
42699
|
+
type: "percentage",
|
|
42700
|
+
value: "33",
|
|
42701
|
+
operator: "<="
|
|
42702
|
+
},
|
|
42703
|
+
upperInflectionPoint: {
|
|
42704
|
+
type: "percentage",
|
|
42705
|
+
value: "66",
|
|
42706
|
+
operator: "<="
|
|
42707
|
+
}
|
|
42708
|
+
},
|
|
42709
|
+
humanize: true
|
|
42710
|
+
};
|
|
42711
|
+
}
|
|
42712
|
+
/** Pattern A — Single numeric column */
|
|
42713
|
+
const SINGLE_NUMBER_COLUMN_SUGGESTIONS = [
|
|
42714
|
+
{
|
|
42715
|
+
description: _t("Highlights the most recent value compared to the previous one."),
|
|
42716
|
+
isApplicable: ({ rowCount }) => rowCount < 3,
|
|
42717
|
+
build: (ctx) => scorecardChart(ctx.title, ctx.lastCellXC, {
|
|
42718
|
+
baseline: ctx.prevCellXC,
|
|
42719
|
+
baselineMode: "difference"
|
|
42720
|
+
})
|
|
42721
|
+
},
|
|
42722
|
+
{
|
|
42723
|
+
description: _t("Shows the position of the last value within the data's min-max range."),
|
|
42724
|
+
isApplicable: ({ rowCount }) => rowCount === 3,
|
|
42725
|
+
build: (ctx) => gaugeChart(ctx.title, ctx.lastCellXC, `=${ctx.firstCellXC}`, `=${ctx.prevCellXC}`)
|
|
42726
|
+
},
|
|
42727
|
+
{
|
|
42728
|
+
description: _t("Compares individual values side-by-side."),
|
|
42729
|
+
isApplicable: ({ rowCount }) => rowCount > 1,
|
|
42730
|
+
build: (ctx) => barChart(ctx.title, ctx.source)
|
|
42731
|
+
},
|
|
42732
|
+
{
|
|
42733
|
+
description: _t("Shows the evolution of all values over the range."),
|
|
42734
|
+
isApplicable: ({ rowCount }) => rowCount > 2,
|
|
42735
|
+
build: (ctx) => lineChart(ctx.title, ctx.source)
|
|
42736
|
+
},
|
|
42737
|
+
{
|
|
42738
|
+
description: _t("Emphasizes total accumulation over the range."),
|
|
42739
|
+
isApplicable: ({ rowCount }) => rowCount > 2,
|
|
42740
|
+
build: (ctx) => lineChart(ctx.title, ctx.source, {
|
|
42741
|
+
fillArea: true,
|
|
42742
|
+
cumulative: true
|
|
42743
|
+
})
|
|
42744
|
+
}
|
|
42745
|
+
];
|
|
42746
|
+
/** Pattern B — Single percentage column */
|
|
42747
|
+
const SINGLE_PERCENTAGE_COLUMN_SUGGESTIONS = [
|
|
42748
|
+
{
|
|
42749
|
+
description: _t("Shows the last percentage value with its baseline."),
|
|
42750
|
+
isApplicable: ({ rowCount }) => rowCount < 3,
|
|
42751
|
+
build: (ctx) => scorecardChart(ctx.title, ctx.lastCellXC, {
|
|
42752
|
+
baseline: ctx.prevCellXC,
|
|
42753
|
+
baselineMode: "percentage"
|
|
42754
|
+
})
|
|
42755
|
+
},
|
|
42756
|
+
{
|
|
42757
|
+
description: _t("Natural fit for a 0–100% range."),
|
|
42758
|
+
isApplicable: ({ rowCount }) => rowCount === 1,
|
|
42759
|
+
build: (ctx) => gaugeChart(ctx.title, ctx.lastCellXC, "0", ctx.isAboveOne ? "100" : "1")
|
|
42760
|
+
},
|
|
42761
|
+
{
|
|
42762
|
+
description: _t("Natural fit for a 0–100% range."),
|
|
42763
|
+
isApplicable: ({ rowCount }) => rowCount === 3,
|
|
42764
|
+
build: (ctx) => gaugeChart(ctx.title, ctx.lastCellXC, `=${ctx.firstCellXC}`, `=${ctx.prevCellXC}`)
|
|
42765
|
+
},
|
|
42766
|
+
{
|
|
42767
|
+
description: _t("Shows completion against total."),
|
|
42768
|
+
isApplicable: ({ rowCount }) => rowCount > 1,
|
|
42769
|
+
build: (ctx) => pieChart(ctx.title, ctx.source, { isDoughnut: true })
|
|
42770
|
+
},
|
|
42771
|
+
{
|
|
42772
|
+
description: _t("Compares all percentage values side-by-side."),
|
|
42773
|
+
isApplicable: ({ rowCount }) => rowCount > 1,
|
|
42774
|
+
build: (ctx) => barChart(ctx.title, ctx.source)
|
|
42775
|
+
}
|
|
42776
|
+
];
|
|
42777
|
+
/** Pattern C — Single date column */
|
|
42778
|
+
const SINGLE_DATE_COLUMN_SUGGESTIONS = [{
|
|
42779
|
+
description: _t("Shows the last date value."),
|
|
42780
|
+
isApplicable: ({ rowCount }) => rowCount === 1,
|
|
42781
|
+
build: (ctx) => scorecardChart(ctx.title, ctx.lastCellXC)
|
|
42782
|
+
}];
|
|
42783
|
+
/** Pattern D — Single categorical column */
|
|
42784
|
+
const SINGLE_CATEGORICAL_COLUMN_SUGGESTIONS = [
|
|
42785
|
+
{
|
|
42786
|
+
description: _t("Shows the share of each category."),
|
|
42787
|
+
build: (ctx) => pieChart(ctx.title, ctx.source, {
|
|
42788
|
+
aggregated: true,
|
|
42789
|
+
legendPosition: "top"
|
|
42790
|
+
})
|
|
42791
|
+
},
|
|
42792
|
+
{
|
|
42793
|
+
description: _t("Same as pie, cleaner proportional look."),
|
|
42794
|
+
build: (ctx) => pieChart(ctx.title, ctx.source, {
|
|
42795
|
+
aggregated: true,
|
|
42796
|
+
isDoughnut: true,
|
|
42797
|
+
legendPosition: "top"
|
|
42798
|
+
})
|
|
42799
|
+
},
|
|
42800
|
+
{
|
|
42801
|
+
description: _t("Absolute count per category."),
|
|
42802
|
+
build: (ctx) => barChart(ctx.title, {
|
|
42803
|
+
...ctx.source,
|
|
42804
|
+
labelRange: ctx.range
|
|
42805
|
+
}, {
|
|
42806
|
+
legendPosition: "none",
|
|
42807
|
+
aggregated: true
|
|
42808
|
+
})
|
|
42809
|
+
}
|
|
42810
|
+
];
|
|
42811
|
+
/** Pattern E — Single label column */
|
|
42812
|
+
const SINGLE_LABEL_COLUMN_SUGGESTIONS = [{
|
|
42813
|
+
description: _t("Displays a key performance indicator."),
|
|
42814
|
+
isApplicable: ({ rowCount }) => rowCount === 1,
|
|
42815
|
+
build: (ctx) => scorecardChart(ctx.title, ctx.lastCellXC, {
|
|
42816
|
+
baselineMode: "text",
|
|
42817
|
+
humanize: false
|
|
42818
|
+
})
|
|
42819
|
+
}];
|
|
42820
|
+
/** Pattern F — Categorical + Number */
|
|
42821
|
+
const CATEGORICAL_VS_NUMBER_SUGGESTIONS = [
|
|
42822
|
+
{
|
|
42823
|
+
description: _t("Classic category-vs-value comparison."),
|
|
42824
|
+
build: (ctx) => barChart(ctx.title, ctx.source, { aggregated: true })
|
|
42825
|
+
},
|
|
42826
|
+
{
|
|
42827
|
+
description: _t("Better when category labels are long."),
|
|
42828
|
+
build: (ctx) => barChart(ctx.title, ctx.source, {
|
|
42829
|
+
horizontal: true,
|
|
42830
|
+
aggregated: true
|
|
42831
|
+
})
|
|
42832
|
+
},
|
|
42833
|
+
{
|
|
42834
|
+
description: _t("Share of total per category."),
|
|
42835
|
+
build: (ctx) => pieChart(ctx.title, ctx.source, {
|
|
42836
|
+
legendPosition: "top",
|
|
42837
|
+
aggregated: true
|
|
42838
|
+
})
|
|
42839
|
+
},
|
|
42840
|
+
{
|
|
42841
|
+
description: _t("Proportional area — good for many categories."),
|
|
42842
|
+
build: (ctx) => treemapChart(ctx.title, ctx.treemapSource)
|
|
42843
|
+
}
|
|
42844
|
+
];
|
|
42845
|
+
/** Patterns G & J — Date + Number or Date + Percentage */
|
|
42846
|
+
const DATE_VS_SERIES_SUGGESTIONS = [
|
|
42847
|
+
{
|
|
42848
|
+
description: _t("Best for visualizing time-series trends."),
|
|
42849
|
+
build: (ctx) => lineChart(ctx.title, ctx.source)
|
|
42850
|
+
},
|
|
42851
|
+
{
|
|
42852
|
+
description: _t("Emphasizes total volume over time."),
|
|
42853
|
+
build: (ctx) => lineChart(ctx.title, ctx.source, {
|
|
42854
|
+
fillArea: true,
|
|
42855
|
+
cumulative: true
|
|
42856
|
+
})
|
|
42857
|
+
},
|
|
42858
|
+
{
|
|
42859
|
+
description: _t("Period-by-period comparison."),
|
|
42860
|
+
build: (ctx) => barChart(ctx.title, ctx.source)
|
|
42861
|
+
},
|
|
42862
|
+
{
|
|
42863
|
+
description: _t("Shows intensity variation across days of the year."),
|
|
42864
|
+
isApplicable: ({ isPercentage }) => !isPercentage,
|
|
42865
|
+
build: (ctx) => calendarChart(ctx.title, ctx.source)
|
|
42866
|
+
}
|
|
42867
|
+
];
|
|
42868
|
+
/** Pattern H — Number + Number */
|
|
42869
|
+
const NUMBER_VS_NUMBER_SUGGESTIONS = [
|
|
42870
|
+
{
|
|
42871
|
+
description: _t("Highlights the second metric compared to the first one."),
|
|
42872
|
+
isApplicable: ({ rowCount1, rowCount2 }) => rowCount1 === 1 && rowCount2 === 1,
|
|
42873
|
+
build: (ctx) => scorecardChart(ctx.title, ctx.lastCellXC, {
|
|
42874
|
+
baseline: ctx.prevCellXC,
|
|
42875
|
+
baselineMode: "difference"
|
|
42876
|
+
})
|
|
42877
|
+
},
|
|
42878
|
+
{
|
|
42879
|
+
description: _t("Side-by-side comparison of two numeric series."),
|
|
42880
|
+
build: (ctx) => barChart(ctx.title, ctx.sourceBoth, { legendPosition: "top" })
|
|
42881
|
+
},
|
|
42882
|
+
{
|
|
42883
|
+
description: _t("Reveals correlation between two numeric variables."),
|
|
42884
|
+
isApplicable: ({ rowCount1 }) => rowCount1 > 2,
|
|
42885
|
+
build: (ctx) => scatterChart(ctx.title, ctx.source2)
|
|
42886
|
+
},
|
|
42887
|
+
{
|
|
42888
|
+
description: _t("Bar for the first series, line for the second — good for mixed scales."),
|
|
42889
|
+
isApplicable: ({ rowCount1 }) => rowCount1 > 2,
|
|
42890
|
+
build: (ctx) => comboChart(ctx.title, ctx.sourceBoth)
|
|
42891
|
+
},
|
|
42892
|
+
{
|
|
42893
|
+
description: _t("When both metrics contribute to a total."),
|
|
42894
|
+
isApplicable: ({ rowCount1 }) => rowCount1 > 2,
|
|
42895
|
+
build: (ctx) => lineChart(ctx.title, ctx.sourceBoth, {
|
|
42896
|
+
stacked: true,
|
|
42897
|
+
fillArea: true,
|
|
42898
|
+
legendPosition: "top"
|
|
42899
|
+
})
|
|
42900
|
+
},
|
|
42901
|
+
{
|
|
42902
|
+
description: _t("Shape-based comparison when rows represent named entities."),
|
|
42903
|
+
isApplicable: ({ rowCount1 }) => rowCount1 > 2 && rowCount1 <= MAX_ROWS_FOR_SHAPE_CHART,
|
|
42904
|
+
build: (ctx) => radarChart(ctx.title, ctx.sourceBoth, { legendPosition: "top" })
|
|
42905
|
+
}
|
|
42906
|
+
];
|
|
42907
|
+
/** Pattern I — Categorical + Percentage */
|
|
42908
|
+
const CATEGORICAL_VS_PERCENTAGE_SUGGESTIONS = [
|
|
42909
|
+
{
|
|
42910
|
+
description: _t("Vertical comparison of rates per category."),
|
|
42911
|
+
build: (ctx) => barChart(ctx.title, ctx.source)
|
|
42912
|
+
},
|
|
42913
|
+
{
|
|
42914
|
+
description: _t("Progress-bar style per category."),
|
|
42915
|
+
build: (ctx) => barChart(ctx.title, ctx.source, { horizontal: true })
|
|
42916
|
+
},
|
|
42917
|
+
{
|
|
42918
|
+
description: _t("Share of total percentage across categories."),
|
|
42919
|
+
build: (ctx) => pieChart(ctx.title, ctx.source, { legendPosition: "top" })
|
|
42920
|
+
},
|
|
42921
|
+
{
|
|
42922
|
+
description: _t("Comparison of completion rates across categories."),
|
|
42923
|
+
isApplicable: ({ rowCount }) => rowCount > 2 && rowCount <= MAX_ROWS_FOR_SHAPE_CHART,
|
|
42924
|
+
build: (ctx) => radarChart(ctx.title, ctx.source)
|
|
42925
|
+
}
|
|
42926
|
+
];
|
|
42927
|
+
/** Pattern K — Label + Number */
|
|
42928
|
+
const LABEL_VS_NUMBER_SUGGESTIONS = [
|
|
42929
|
+
{
|
|
42930
|
+
description: _t("Highlights the most recent value for the named entity."),
|
|
42931
|
+
isApplicable: ({ rowCount }) => rowCount === 1,
|
|
42932
|
+
build: (ctx) => scorecardChart("", ctx.lastCellXC, {
|
|
42933
|
+
humanize: false,
|
|
42934
|
+
baselineMode: "text",
|
|
42935
|
+
baseline: ctx.prevCellXC
|
|
42936
|
+
})
|
|
42937
|
+
},
|
|
42938
|
+
{
|
|
42939
|
+
description: _t("Vertical comparison across named items."),
|
|
42940
|
+
isApplicable: ({ rowCount }) => rowCount > 1,
|
|
42941
|
+
build: (ctx) => barChart(ctx.title, ctx.source)
|
|
42942
|
+
},
|
|
42943
|
+
{
|
|
42944
|
+
description: _t("Works well for named entities with long labels."),
|
|
42945
|
+
isApplicable: ({ rowCount }) => rowCount > 1,
|
|
42946
|
+
build: (ctx) => barChart(ctx.title, ctx.source, { horizontal: true })
|
|
42947
|
+
},
|
|
42948
|
+
{
|
|
42949
|
+
description: _t("Share of total per category."),
|
|
42950
|
+
isApplicable: ({ rowCount }) => rowCount > 1 && rowCount <= MAX_ROWS_FOR_SHAPE_CHART,
|
|
42951
|
+
build: (ctx) => pieChart(ctx.title, ctx.source, {
|
|
42952
|
+
legendPosition: "top",
|
|
42953
|
+
aggregated: true
|
|
42954
|
+
})
|
|
42955
|
+
},
|
|
42956
|
+
{
|
|
42957
|
+
description: _t("Shape-based comparison across labeled items."),
|
|
42958
|
+
isApplicable: ({ rowCount }) => rowCount > 2 && rowCount <= MAX_ROWS_FOR_SHAPE_CHART,
|
|
42959
|
+
build: (ctx) => radarChart(ctx.title, ctx.source)
|
|
42960
|
+
}
|
|
42961
|
+
];
|
|
42962
|
+
/** Pattern M — Categorical + Multiple Numbers */
|
|
42963
|
+
const CATEGORICAL_VS_MULTIPLE_NUMBERS_SUGGESTIONS = [
|
|
42964
|
+
{
|
|
42965
|
+
description: _t("Side-by-side comparison across categories for each series."),
|
|
42966
|
+
build: (ctx) => barChart(ctx.title, ctx.source, { legendPosition: "top" })
|
|
42967
|
+
},
|
|
42968
|
+
{
|
|
42969
|
+
description: _t("Shows composition and total per category."),
|
|
42970
|
+
build: (ctx) => barChart(ctx.title, ctx.source, {
|
|
42971
|
+
stacked: true,
|
|
42972
|
+
legendPosition: "top"
|
|
42973
|
+
})
|
|
42974
|
+
},
|
|
42975
|
+
{
|
|
42976
|
+
description: _t("Trend per series across categories."),
|
|
42977
|
+
build: (ctx) => lineChart(ctx.title, ctx.source, { legendPosition: "top" })
|
|
42978
|
+
},
|
|
42979
|
+
{
|
|
42980
|
+
description: _t("Volume and composition across categories."),
|
|
42981
|
+
build: (ctx) => lineChart(ctx.title, ctx.source, {
|
|
42982
|
+
stacked: true,
|
|
42983
|
+
fillArea: true,
|
|
42984
|
+
legendPosition: "top"
|
|
42985
|
+
})
|
|
42986
|
+
},
|
|
42987
|
+
{
|
|
42988
|
+
description: _t("Shape comparison across metrics (best for ≤ 10 rows)."),
|
|
42989
|
+
isApplicable: ({ rowCount }) => rowCount > 2 && rowCount <= MAX_ROWS_FOR_SHAPE_CHART,
|
|
42990
|
+
build: (ctx) => radarChart(ctx.title, ctx.source, { legendPosition: "top" })
|
|
42991
|
+
}
|
|
42992
|
+
];
|
|
42993
|
+
/** Pattern N — Date + Multiple Numbers */
|
|
42994
|
+
const DATE_VS_MULTIPLE_NUMBERS_SUGGESTIONS = [
|
|
42995
|
+
{
|
|
42996
|
+
description: _t("Trend comparison across multiple metrics over time."),
|
|
42997
|
+
build: (ctx) => lineChart(ctx.title, ctx.source, { legendPosition: "top" })
|
|
42998
|
+
},
|
|
42999
|
+
{
|
|
43000
|
+
description: _t("Volume composition over time."),
|
|
43001
|
+
build: (ctx) => lineChart(ctx.title, ctx.source, {
|
|
43002
|
+
stacked: true,
|
|
43003
|
+
fillArea: true,
|
|
43004
|
+
legendPosition: "top"
|
|
43005
|
+
})
|
|
43006
|
+
},
|
|
43007
|
+
{
|
|
43008
|
+
description: _t("Bar for the primary metric, line for the others."),
|
|
43009
|
+
build: (ctx) => comboChart(ctx.title, ctx.source)
|
|
43010
|
+
},
|
|
43011
|
+
{
|
|
43012
|
+
description: _t("Period-by-period grouped comparison."),
|
|
43013
|
+
build: (ctx) => barChart(ctx.title, ctx.source, { legendPosition: "top" })
|
|
43014
|
+
}
|
|
43015
|
+
];
|
|
43016
|
+
/** Pattern O — Categorical + Categorical + Number */
|
|
43017
|
+
const MULTIPLE_CATEGORICALS_VS_NUMBER_SUGGESTIONS = [
|
|
43018
|
+
{
|
|
43019
|
+
description: _t("Two-level hierarchy weighted by value."),
|
|
43020
|
+
build: (ctx) => sunburstChart(ctx.title, ctx.hierarchySource)
|
|
43021
|
+
},
|
|
43022
|
+
{
|
|
43023
|
+
description: _t("Proportional nested area weighted by value."),
|
|
43024
|
+
build: (ctx) => treemapChart(ctx.title, ctx.hierarchySource)
|
|
43025
|
+
},
|
|
43026
|
+
{
|
|
43027
|
+
description: _t("One series per inner category, grouped by outer category."),
|
|
43028
|
+
build: (ctx) => barChart(ctx.title, ctx.barSource, { legendPosition: "top" })
|
|
43029
|
+
},
|
|
43030
|
+
{
|
|
43031
|
+
description: _t("Contribution of inner categories per outer category."),
|
|
43032
|
+
build: (ctx) => barChart(ctx.title, ctx.barSource, {
|
|
43033
|
+
stacked: true,
|
|
43034
|
+
legendPosition: "top"
|
|
43035
|
+
})
|
|
43036
|
+
}
|
|
43037
|
+
];
|
|
43038
|
+
/** Pattern P — Categorical + Date + Number */
|
|
43039
|
+
const CATEGORICAL_DATE_NUMBER_SUGGESTIONS = [
|
|
43040
|
+
{
|
|
43041
|
+
description: _t("Trend of values over time."),
|
|
43042
|
+
build: (ctx) => lineChart(ctx.title, ctx.sourceByDate, { legendPosition: "top" })
|
|
43043
|
+
},
|
|
43044
|
+
{
|
|
43045
|
+
description: _t("Volume contribution over time."),
|
|
43046
|
+
build: (ctx) => lineChart(ctx.title, ctx.sourceByDate, {
|
|
43047
|
+
stacked: true,
|
|
43048
|
+
fillArea: true,
|
|
43049
|
+
legendPosition: "top"
|
|
43050
|
+
})
|
|
43051
|
+
},
|
|
43052
|
+
{
|
|
43053
|
+
description: _t("Period × category side-by-side comparison."),
|
|
43054
|
+
build: (ctx) => barChart(ctx.title, ctx.sourceByCat, { legendPosition: "top" })
|
|
43055
|
+
},
|
|
43056
|
+
{
|
|
43057
|
+
description: _t("Composition per period over time."),
|
|
43058
|
+
build: (ctx) => barChart(ctx.title, ctx.sourceByDate, {
|
|
43059
|
+
stacked: true,
|
|
43060
|
+
legendPosition: "top"
|
|
43061
|
+
})
|
|
43062
|
+
}
|
|
43063
|
+
];
|
|
43064
|
+
/** Pattern Q — Label + Multiple Numbers */
|
|
43065
|
+
const LABEL_VS_MULTIPLE_NUMBERS_SUGGESTIONS = [
|
|
43066
|
+
{
|
|
43067
|
+
description: _t("Side-by-side per entity — works well for long labels."),
|
|
43068
|
+
build: (ctx) => barChart(ctx.title, ctx.source, {
|
|
43069
|
+
horizontal: true,
|
|
43070
|
+
legendPosition: "top"
|
|
43071
|
+
})
|
|
43072
|
+
},
|
|
43073
|
+
{
|
|
43074
|
+
description: _t("Grouped bars per entity for direct metric comparison."),
|
|
43075
|
+
build: (ctx) => barChart(ctx.title, ctx.source, { legendPosition: "top" })
|
|
43076
|
+
},
|
|
43077
|
+
{
|
|
43078
|
+
description: _t("Shape/profile comparison across metrics for each entity."),
|
|
43079
|
+
isApplicable: ({ rowCount }) => rowCount > 2 && rowCount <= MAX_ROWS_FOR_SHAPE_CHART,
|
|
43080
|
+
build: (ctx) => radarChart(ctx.title, ctx.source, { legendPosition: "top" })
|
|
43081
|
+
},
|
|
43082
|
+
{
|
|
43083
|
+
description: _t("Correlation between the two numeric metrics across entities."),
|
|
43084
|
+
isApplicable: ({ rowCount, numColsCount }) => rowCount > 2 && numColsCount === 2,
|
|
43085
|
+
build: (ctx) => scatterChart(ctx.title, ctx.scatterSource)
|
|
43086
|
+
},
|
|
43087
|
+
{
|
|
43088
|
+
description: _t("Three metrics in one view: X position, Y position and bubble size."),
|
|
43089
|
+
isApplicable: ({ numColsCount }) => numColsCount === 3,
|
|
43090
|
+
build: (ctx) => {
|
|
43091
|
+
const bubble = ctx.bubble;
|
|
43092
|
+
return bubbleChart(ctx.title, bubble.xRange, bubble.yRanges, bubble.sizeRange, bubble.labelRange, bubble.hasTitle);
|
|
43093
|
+
}
|
|
43094
|
+
}
|
|
43095
|
+
];
|
|
43096
|
+
/** Pattern R — Categorical + Two Numbers (Population Pyramid or Grouped) */
|
|
43097
|
+
const CATEGORICAL_TWO_NUMBERS_SUGGESTIONS = [
|
|
43098
|
+
{
|
|
43099
|
+
description: _t("Natural fit for symmetric or opposing values per category."),
|
|
43100
|
+
isApplicable: ({ isPyramid }) => isPyramid,
|
|
43101
|
+
build: (ctx) => pyramidChart(ctx.title, ctx.sourceBoth)
|
|
43102
|
+
},
|
|
43103
|
+
{
|
|
43104
|
+
description: _t("Side-by-side comparison of both metrics per category."),
|
|
43105
|
+
build: (ctx) => barChart(ctx.title, ctx.sourceBoth, {
|
|
43106
|
+
legendPosition: "top",
|
|
43107
|
+
aggregated: true
|
|
43108
|
+
})
|
|
43109
|
+
},
|
|
43110
|
+
{
|
|
43111
|
+
description: _t("Bar for the first series, line for the second — good for mixed scales."),
|
|
43112
|
+
build: (ctx) => comboChart(ctx.title, ctx.sourceBoth, { aggregated: true })
|
|
43113
|
+
},
|
|
43114
|
+
{
|
|
43115
|
+
description: _t("Shows total and composition per category."),
|
|
43116
|
+
isApplicable: ({ isPyramid }) => !isPyramid,
|
|
43117
|
+
build: (ctx) => barChart(ctx.title, ctx.sourceBoth, {
|
|
43118
|
+
stacked: true,
|
|
43119
|
+
legendPosition: "top",
|
|
43120
|
+
aggregated: true
|
|
43121
|
+
})
|
|
43122
|
+
}
|
|
43123
|
+
];
|
|
43124
|
+
/** Pattern S — Many Numbers (3+ numeric columns, no categorical/date) */
|
|
43125
|
+
const MANY_NUMBERS_SUGGESTIONS = [
|
|
43126
|
+
{
|
|
43127
|
+
description: _t("Shows the position of the last value within the data's min-max range."),
|
|
43128
|
+
isApplicable: ({ colsLength, rowCount }) => colsLength === 3 && rowCount === 1,
|
|
43129
|
+
build: (ctx) => gaugeChart(ctx.title, ctx.lastCellXC, `=${ctx.firstCellXC}`, `=${ctx.secondCellXC}`)
|
|
43130
|
+
},
|
|
43131
|
+
{
|
|
43132
|
+
description: _t("Trend comparison across all metrics."),
|
|
43133
|
+
isApplicable: ({ rowCount }) => rowCount > 2,
|
|
43134
|
+
build: (ctx) => lineChart(ctx.title, ctx.source, { legendPosition: "top" })
|
|
43135
|
+
},
|
|
43136
|
+
{
|
|
43137
|
+
description: _t("Overall shape/profile across all metrics."),
|
|
43138
|
+
isApplicable: ({ rowCount }) => rowCount > 2 && rowCount <= MAX_ROWS_FOR_SHAPE_CHART,
|
|
43139
|
+
build: (ctx) => radarChart(ctx.title, ctx.source, { legendPosition: "top" })
|
|
43140
|
+
},
|
|
43141
|
+
{
|
|
43142
|
+
description: _t("Side-by-side comparison of all numeric metrics."),
|
|
43143
|
+
build: (ctx) => barChart(ctx.title, ctx.source, { legendPosition: "top" })
|
|
43144
|
+
},
|
|
43145
|
+
{
|
|
43146
|
+
description: _t("Side-by-side comparison of all numeric metrics."),
|
|
43147
|
+
build: (ctx) => barChart(ctx.title, ctx.source, {
|
|
43148
|
+
legendPosition: "top",
|
|
43149
|
+
stacked: true
|
|
43150
|
+
})
|
|
43151
|
+
}
|
|
43152
|
+
];
|
|
43153
|
+
|
|
43154
|
+
//#endregion
|
|
43155
|
+
//#region src/helpers/figures/charts/chart_suggestion_engine.ts
|
|
43156
|
+
const PYRAMID_HEADER_KEYWORD_PATTERNS = [
|
|
43157
|
+
"male",
|
|
43158
|
+
"female",
|
|
43159
|
+
"man",
|
|
43160
|
+
"woman",
|
|
43161
|
+
"boy",
|
|
43162
|
+
"girl",
|
|
43163
|
+
"before",
|
|
43164
|
+
"after",
|
|
43165
|
+
"previous",
|
|
43166
|
+
"current",
|
|
43167
|
+
"positive",
|
|
43168
|
+
"negative",
|
|
43169
|
+
"gain",
|
|
43170
|
+
"loss",
|
|
43171
|
+
"income",
|
|
43172
|
+
"expense",
|
|
43173
|
+
"revenue",
|
|
43174
|
+
"cost"
|
|
43175
|
+
].map((keyword) => new RegExp(`\\b${keyword}\\b`, "i"));
|
|
43176
|
+
function matchesColumnType(spec, type) {
|
|
43177
|
+
return Array.isArray(spec) ? spec.includes(type) : spec === type;
|
|
43178
|
+
}
|
|
43179
|
+
function matchesShape(pattern, shape) {
|
|
43180
|
+
const { leadingTypes, extension } = pattern;
|
|
43181
|
+
const expectedLength = leadingTypes.length + (extension?.minSize ?? 0);
|
|
43182
|
+
if (extension ? shape.length < expectedLength : shape.length !== expectedLength) return false;
|
|
43183
|
+
return shape.every((type, i) => {
|
|
43184
|
+
const spec = i < leadingTypes.length ? leadingTypes[i] : extension?.type;
|
|
43185
|
+
return spec !== void 0 && matchesColumnType(spec, type);
|
|
43186
|
+
});
|
|
43187
|
+
}
|
|
43188
|
+
function isPyramidLike(numCol1, numCol2) {
|
|
43189
|
+
const h1 = numCol1.header ?? "";
|
|
43190
|
+
const h2 = numCol2.header ?? "";
|
|
43191
|
+
return PYRAMID_HEADER_KEYWORD_PATTERNS.some((re) => re.test(h1) || re.test(h2));
|
|
43192
|
+
}
|
|
43193
|
+
/** Returns the first and last cell XC coordinates, and the second-to-last cell XC coordinate if it exists. */
|
|
43194
|
+
function interestingCellsXc(col) {
|
|
43195
|
+
if (!col) return {
|
|
43196
|
+
firstCellXC: "",
|
|
43197
|
+
lastCellXC: "",
|
|
43198
|
+
prevCellXC: void 0
|
|
43199
|
+
};
|
|
43200
|
+
const firstCellPosition = col.nonEmpty.at(0)?.position;
|
|
43201
|
+
const firstCellXC = firstCellPosition ? toXC(firstCellPosition?.col, firstCellPosition?.row) : "";
|
|
43202
|
+
const lastCellPosition = col.nonEmpty.at(-1)?.position;
|
|
43203
|
+
const lastCellXC = lastCellPosition ? toXC(lastCellPosition?.col, lastCellPosition?.row) : "";
|
|
43204
|
+
const prevCellPosition = col.nonEmpty.at(-2)?.position;
|
|
43205
|
+
return {
|
|
43206
|
+
firstCellXC,
|
|
43207
|
+
lastCellXC,
|
|
43208
|
+
prevCellXC: prevCellPosition ? toXC(prevCellPosition?.col, prevCellPosition?.row) : void 0
|
|
43209
|
+
};
|
|
43210
|
+
}
|
|
43211
|
+
/** Pattern A — Single numeric column */
|
|
43212
|
+
function buildSingleNumberContext([col], getters) {
|
|
43213
|
+
const title = col.header ?? _t("Value");
|
|
43214
|
+
const { firstCellXC, lastCellXC, prevCellXC } = interestingCellsXc(col);
|
|
43215
|
+
return {
|
|
43216
|
+
title,
|
|
43217
|
+
source: rangeSource([dataset(col.zone, getters)], col.hasHeader),
|
|
43218
|
+
rowCount: col.rowCount,
|
|
43219
|
+
firstCellXC,
|
|
43220
|
+
lastCellXC,
|
|
43221
|
+
prevCellXC
|
|
43222
|
+
};
|
|
43223
|
+
}
|
|
43224
|
+
/** Pattern B — Single percentage column */
|
|
43225
|
+
function buildSinglePercentageContext([col], getters) {
|
|
43226
|
+
const hasTitle = col.hasHeader;
|
|
43227
|
+
const title = col.header ?? _t("Rate");
|
|
43228
|
+
const { firstCellXC, lastCellXC, prevCellXC } = interestingCellsXc(col);
|
|
43229
|
+
const source = rangeSource([dataset(col.zone, getters)], hasTitle);
|
|
43230
|
+
const isAboveOne = (col.maxValue ?? 0) > 1;
|
|
43231
|
+
return {
|
|
43232
|
+
title,
|
|
43233
|
+
source,
|
|
43234
|
+
rowCount: col.rowCount,
|
|
43235
|
+
firstCellXC,
|
|
43236
|
+
lastCellXC,
|
|
43237
|
+
prevCellXC,
|
|
43238
|
+
isAboveOne
|
|
43239
|
+
};
|
|
43240
|
+
}
|
|
43241
|
+
/** Pattern C — Single date column */
|
|
43242
|
+
function buildSingleDateContext([col]) {
|
|
43243
|
+
const { lastCellXC } = interestingCellsXc(col);
|
|
43244
|
+
return {
|
|
43245
|
+
title: col.header ?? _t("Date"),
|
|
43246
|
+
lastCellXC,
|
|
43247
|
+
rowCount: col.rowCount
|
|
43248
|
+
};
|
|
43249
|
+
}
|
|
43250
|
+
/** Pattern D — Single categorical column */
|
|
43251
|
+
function buildSingleCategoricalContext([col], getters) {
|
|
43252
|
+
const hasTitle = col.hasHeader;
|
|
43253
|
+
const title = col.header ?? _t("Category");
|
|
43254
|
+
const range = getUnboundRange(getters, col.zone);
|
|
43255
|
+
return {
|
|
43256
|
+
title,
|
|
43257
|
+
source: rangeSource([dataset(col.zone, getters)], hasTitle, range),
|
|
43258
|
+
range
|
|
43259
|
+
};
|
|
43260
|
+
}
|
|
43261
|
+
/** Pattern E — Single label column */
|
|
43262
|
+
function buildSingleLabelContext([col]) {
|
|
43263
|
+
const title = col.header ?? _t("Label");
|
|
43264
|
+
const { lastCellXC } = interestingCellsXc(col);
|
|
43265
|
+
return {
|
|
43266
|
+
title,
|
|
43267
|
+
lastCellXC,
|
|
43268
|
+
rowCount: col.rowCount
|
|
43269
|
+
};
|
|
43270
|
+
}
|
|
43271
|
+
/** Pattern F — Categorical + Number */
|
|
43272
|
+
function buildCategoricalVsNumberContext([catCol, numCol], getters) {
|
|
43273
|
+
const labelRange = getUnboundRange(getters, catCol.zone);
|
|
43274
|
+
const hasTitle = numCol.hasHeader;
|
|
43275
|
+
return {
|
|
43276
|
+
title: numCol.header ? _t("%(numberHeader)s by %(categoryHeader)s", {
|
|
43277
|
+
numberHeader: numCol.header,
|
|
43278
|
+
categoryHeader: catCol.header ?? _t("Category")
|
|
43279
|
+
}) : _t("By Category"),
|
|
43280
|
+
source: rangeSource([dataset(numCol.zone, getters)], hasTitle, labelRange),
|
|
43281
|
+
treemapSource: rangeSource([dataset(catCol.zone, getters)], hasTitle, getUnboundRange(getters, numCol.zone))
|
|
43282
|
+
};
|
|
43283
|
+
}
|
|
43284
|
+
/** Patterns G & J — Date + Number or Date + Percentage */
|
|
43285
|
+
function buildDateVsSeriesContext([dateCol, seriesCol], getters) {
|
|
43286
|
+
const isPercentage = seriesCol.type === "percentage";
|
|
43287
|
+
const labelRange = getUnboundRange(getters, dateCol.zone);
|
|
43288
|
+
const hasTitle = isDatasetTitled(getters, dateCol.zone);
|
|
43289
|
+
return {
|
|
43290
|
+
title: seriesCol.header ? _t("%(seriesHeader)s over time", { seriesHeader: seriesCol.header }) : isPercentage ? _t("Rate over Time") : _t("Over Time"),
|
|
43291
|
+
source: rangeSource([dataset(seriesCol.zone, getters)], hasTitle, labelRange),
|
|
43292
|
+
isPercentage
|
|
43293
|
+
};
|
|
43294
|
+
}
|
|
43295
|
+
/** Pattern H — Number + Number */
|
|
43296
|
+
function buildNumberVsNumberContext([col1, col2], getters) {
|
|
43297
|
+
const title = col1.header && col2.header ? _t("%(col2Header)s vs %(col1Header)s", {
|
|
43298
|
+
col2Header: col2.header,
|
|
43299
|
+
col1Header: col1.header
|
|
43300
|
+
}) : _t("Correlation");
|
|
43301
|
+
const hasTitle = col1.hasHeader || col2.hasHeader;
|
|
43302
|
+
const source2 = rangeSource([dataset(col2.zone, getters)], col2.hasHeader, getUnboundRange(getters, col1.zone));
|
|
43303
|
+
const sourceBoth = rangeSource([dataset(col1.zone, getters, "0"), dataset(col2.zone, getters, "1")], hasTitle);
|
|
43304
|
+
const { lastCellXC } = interestingCellsXc(col2);
|
|
43305
|
+
const { lastCellXC: prevCellXC } = interestingCellsXc(col1);
|
|
43306
|
+
return {
|
|
43307
|
+
title,
|
|
43308
|
+
source2,
|
|
43309
|
+
sourceBoth,
|
|
43310
|
+
rowCount1: col1.rowCount,
|
|
43311
|
+
rowCount2: col2.rowCount,
|
|
43312
|
+
lastCellXC,
|
|
43313
|
+
prevCellXC
|
|
43314
|
+
};
|
|
43315
|
+
}
|
|
43316
|
+
/** Pattern I — Categorical + Percentage */
|
|
43317
|
+
function buildCategoricalVsPercentageContext([catCol, pctCol], getters) {
|
|
43318
|
+
const labelRange = getUnboundRange(getters, catCol.zone);
|
|
43319
|
+
const hasTitle = pctCol.hasHeader;
|
|
43320
|
+
return {
|
|
43321
|
+
title: pctCol.header ? _t("%(percentageHeader)s by %(categoryHeader)s", {
|
|
43322
|
+
percentageHeader: pctCol.header,
|
|
43323
|
+
categoryHeader: catCol.header ?? _t("Category")
|
|
43324
|
+
}) : _t("Rates by Category"),
|
|
43325
|
+
source: rangeSource([dataset(pctCol.zone, getters)], hasTitle, labelRange),
|
|
43326
|
+
rowCount: catCol.rowCount
|
|
43327
|
+
};
|
|
43328
|
+
}
|
|
43329
|
+
/** Pattern K — Label + Number */
|
|
43330
|
+
function buildLabelVsNumberContext([labelCol, numCol], getters) {
|
|
43331
|
+
const labelRange = getUnboundRange(getters, labelCol.zone);
|
|
43332
|
+
const hasTitle = numCol.hasHeader;
|
|
43333
|
+
const title = numCol.header ? _t("%(numberHeader)s by %(labelHeader)s", {
|
|
43334
|
+
numberHeader: numCol.header,
|
|
43335
|
+
labelHeader: labelCol.header ?? _t("Name")
|
|
43336
|
+
}) : _t("By Name");
|
|
43337
|
+
const source = rangeSource([dataset(numCol.zone, getters)], hasTitle, labelRange);
|
|
43338
|
+
const { lastCellXC } = interestingCellsXc(numCol);
|
|
43339
|
+
const { lastCellXC: prevCellXC } = interestingCellsXc(labelCol);
|
|
43340
|
+
return {
|
|
43341
|
+
title,
|
|
43342
|
+
source,
|
|
43343
|
+
rowCount: labelCol.rowCount,
|
|
43344
|
+
lastCellXC,
|
|
43345
|
+
prevCellXC
|
|
43346
|
+
};
|
|
43347
|
+
}
|
|
43348
|
+
/** Pattern M — Categorical + Multiple Numbers */
|
|
43349
|
+
function buildCategoricalVsMultipleNumbersContext([catCol, ...numCols], getters) {
|
|
43350
|
+
const labelRange = getUnboundRange(getters, catCol.zone);
|
|
43351
|
+
const hasTitle = numCols.some((c) => c.hasHeader);
|
|
43352
|
+
return {
|
|
43353
|
+
title: catCol.header ? _t("By %(header)s", { header: catCol.header }) : _t("Multi-series"),
|
|
43354
|
+
source: rangeSource(numCols.map((c, i) => dataset(c.zone, getters, String(i))), hasTitle, labelRange),
|
|
43355
|
+
rowCount: catCol.rowCount
|
|
43356
|
+
};
|
|
43357
|
+
}
|
|
43358
|
+
/** Pattern N — Date + Multiple Numbers */
|
|
43359
|
+
function buildDateVsMultipleNumbersContext([dateCol, ...numCols], getters) {
|
|
43360
|
+
const labelRange = getUnboundRange(getters, dateCol.zone);
|
|
43361
|
+
const hasTitle = numCols.some((c) => c.hasHeader) || isDatasetTitled(getters, dateCol.zone);
|
|
43362
|
+
return {
|
|
43363
|
+
title: numCols.length === 1 && numCols[0].header ? _t("%(header)s over time", { header: numCols[0].header }) : _t("Multi-series over Time"),
|
|
43364
|
+
source: rangeSource(numCols.map((c, i) => dataset(c.zone, getters, String(i))), hasTitle, labelRange)
|
|
43365
|
+
};
|
|
43366
|
+
}
|
|
43367
|
+
/** Pattern O — Categorical + Categorical + Number */
|
|
43368
|
+
function buildMultipleCategoricalsVsNumberContext([cat1, cat2, numCol], getters) {
|
|
43369
|
+
const title = numCol.header ? _t("%(numHeader)s by %(cat1Header)s and %(cat2Header)s", {
|
|
43370
|
+
numHeader: numCol.header,
|
|
43371
|
+
cat1Header: cat1.header ?? _t("Level 1"),
|
|
43372
|
+
cat2Header: cat2.header ?? _t("Level 2")
|
|
43373
|
+
}) : _t("Two-level hierarchy");
|
|
43374
|
+
const hasTitle = numCol.hasHeader;
|
|
43375
|
+
return {
|
|
43376
|
+
title,
|
|
43377
|
+
hierarchySource: rangeSource([dataset(cat1.zone, getters, "0"), dataset(cat2.zone, getters, "1")], hasTitle, getUnboundRange(getters, numCol.zone)),
|
|
43378
|
+
barSource: rangeSource([dataset(numCol.zone, getters)], hasTitle, getUnboundRange(getters, cat1.zone))
|
|
43379
|
+
};
|
|
43380
|
+
}
|
|
43381
|
+
/** Pattern P — Categorical + Date + Number */
|
|
43382
|
+
function buildCategoricalDateNumberContext([catCol, dateCol, numCol], getters) {
|
|
43383
|
+
const dateRange = getUnboundRange(getters, dateCol.zone);
|
|
43384
|
+
const catRange = getUnboundRange(getters, catCol.zone);
|
|
43385
|
+
const hasTitle = numCol.hasHeader;
|
|
43386
|
+
return {
|
|
43387
|
+
title: numCol.header ? _t("%(numHeader)s by %(catHeader)s over %(dateHeader)s", {
|
|
43388
|
+
numHeader: numCol.header,
|
|
43389
|
+
catHeader: catCol.header ?? _t("Category"),
|
|
43390
|
+
dateHeader: dateCol.header ?? _t("Time")
|
|
43391
|
+
}) : _t("Multi-series over Time"),
|
|
43392
|
+
sourceByDate: rangeSource([dataset(numCol.zone, getters)], hasTitle, dateRange),
|
|
43393
|
+
sourceByCat: rangeSource([dataset(numCol.zone, getters)], hasTitle, catRange)
|
|
43394
|
+
};
|
|
43395
|
+
}
|
|
43396
|
+
/** Pattern Q — Label + Multiple Numbers */
|
|
43397
|
+
function buildLabelVsMultipleNumbersContext([labelCol, ...numCols], getters) {
|
|
43398
|
+
const labelRange = getUnboundRange(getters, labelCol.zone);
|
|
43399
|
+
const hasTitle = numCols.some((c) => c.hasHeader);
|
|
43400
|
+
const title = labelCol.header ? _t("By %(header)s", { header: labelCol.header }) : _t("Profile Comparison");
|
|
43401
|
+
const source = rangeSource(numCols.map((c, i) => dataset(c.zone, getters, String(i))), hasTitle, labelRange);
|
|
43402
|
+
const scatterSource = rangeSource([dataset(numCols[1].zone, getters)], numCols[1].hasHeader, getUnboundRange(getters, numCols[0].zone));
|
|
43403
|
+
const bubble = numCols.length === 3 ? {
|
|
43404
|
+
xRange: getUnboundRange(getters, numCols[0].zone),
|
|
43405
|
+
yRanges: [getUnboundRange(getters, numCols[1].zone)],
|
|
43406
|
+
sizeRange: getUnboundRange(getters, numCols[2].zone),
|
|
43407
|
+
labelRange,
|
|
43408
|
+
hasTitle
|
|
43409
|
+
} : void 0;
|
|
43410
|
+
return {
|
|
43411
|
+
title,
|
|
43412
|
+
source,
|
|
43413
|
+
rowCount: labelCol.rowCount,
|
|
43414
|
+
numColsCount: numCols.length,
|
|
43415
|
+
scatterSource,
|
|
43416
|
+
bubble
|
|
43417
|
+
};
|
|
43418
|
+
}
|
|
43419
|
+
/** Pattern R — Categorical + Two Numbers (Population Pyramid or Grouped) */
|
|
43420
|
+
function buildCategoricalTwoNumbersContext([catCol, numCol1, numCol2], getters) {
|
|
43421
|
+
const catRange = getUnboundRange(getters, catCol.zone);
|
|
43422
|
+
const hasTitle = numCol1.hasHeader || numCol2.hasHeader;
|
|
43423
|
+
const title = numCol1.header && numCol2.header ? _t("%(num1Header)s vs %(num2Header)s by %(catHeader)s", {
|
|
43424
|
+
num1Header: numCol1.header,
|
|
43425
|
+
num2Header: numCol2.header,
|
|
43426
|
+
catHeader: catCol.header ?? _t("Category")
|
|
43427
|
+
}) : catCol.header ? _t("By %(header)s", { header: catCol.header }) : _t("Category Comparison");
|
|
43428
|
+
const isPyramid = isPyramidLike(numCol1, numCol2);
|
|
43429
|
+
return {
|
|
43430
|
+
title,
|
|
43431
|
+
sourceBoth: rangeSource([dataset(numCol1.zone, getters, "0"), dataset(numCol2.zone, getters, "1")], hasTitle, catRange),
|
|
43432
|
+
isPyramid
|
|
43433
|
+
};
|
|
43434
|
+
}
|
|
43435
|
+
/** Pattern S — Many Numbers (3+ numeric columns, no categorical/date) */
|
|
43436
|
+
function buildManyNumbersContext(cols, getters) {
|
|
43437
|
+
const title = cols.every((c) => c.hasHeader) ? cols.map((c) => c.header).join(" / ") : _t("KPI Overview");
|
|
43438
|
+
const hasTitle = cols.some((c) => c.hasHeader);
|
|
43439
|
+
const source = rangeSource(cols.map((c, i) => dataset(c.zone, getters, String(i))), hasTitle);
|
|
43440
|
+
const { lastCellXC } = interestingCellsXc(cols[2]);
|
|
43441
|
+
const { lastCellXC: firstCellXC } = interestingCellsXc(cols[0]);
|
|
43442
|
+
const { lastCellXC: secondCellXC } = interestingCellsXc(cols[1]);
|
|
43443
|
+
return {
|
|
43444
|
+
title,
|
|
43445
|
+
source,
|
|
43446
|
+
colsLength: cols.length,
|
|
43447
|
+
rowCount: cols[0].rowCount,
|
|
43448
|
+
lastCellXC,
|
|
43449
|
+
firstCellXC,
|
|
43450
|
+
secondCellXC
|
|
43451
|
+
};
|
|
43452
|
+
}
|
|
43453
|
+
const NUMBER_OR_PERCENTAGE = ["number", "percentage"];
|
|
43454
|
+
const EXACT_PATTERNS = [
|
|
43455
|
+
{
|
|
43456
|
+
pattern: { leadingTypes: ["number"] },
|
|
43457
|
+
buildContext: buildSingleNumberContext,
|
|
43458
|
+
suggestions: SINGLE_NUMBER_COLUMN_SUGGESTIONS
|
|
43459
|
+
},
|
|
43460
|
+
{
|
|
43461
|
+
pattern: { leadingTypes: ["percentage"] },
|
|
43462
|
+
buildContext: buildSinglePercentageContext,
|
|
43463
|
+
suggestions: SINGLE_PERCENTAGE_COLUMN_SUGGESTIONS
|
|
43464
|
+
},
|
|
43465
|
+
{
|
|
43466
|
+
pattern: { leadingTypes: ["date"] },
|
|
43467
|
+
buildContext: buildSingleDateContext,
|
|
43468
|
+
suggestions: SINGLE_DATE_COLUMN_SUGGESTIONS
|
|
43469
|
+
},
|
|
43470
|
+
{
|
|
43471
|
+
pattern: { leadingTypes: ["categorical"] },
|
|
43472
|
+
buildContext: buildSingleCategoricalContext,
|
|
43473
|
+
suggestions: SINGLE_CATEGORICAL_COLUMN_SUGGESTIONS
|
|
43474
|
+
},
|
|
43475
|
+
{
|
|
43476
|
+
pattern: { leadingTypes: ["label"] },
|
|
43477
|
+
buildContext: buildSingleLabelContext,
|
|
43478
|
+
suggestions: SINGLE_LABEL_COLUMN_SUGGESTIONS
|
|
43479
|
+
},
|
|
43480
|
+
{
|
|
43481
|
+
pattern: { leadingTypes: ["categorical", "number"] },
|
|
43482
|
+
buildContext: buildCategoricalVsNumberContext,
|
|
43483
|
+
suggestions: CATEGORICAL_VS_NUMBER_SUGGESTIONS
|
|
43484
|
+
},
|
|
43485
|
+
{
|
|
43486
|
+
pattern: { leadingTypes: ["date", NUMBER_OR_PERCENTAGE] },
|
|
43487
|
+
buildContext: buildDateVsSeriesContext,
|
|
43488
|
+
suggestions: DATE_VS_SERIES_SUGGESTIONS
|
|
43489
|
+
},
|
|
43490
|
+
{
|
|
43491
|
+
pattern: { leadingTypes: ["number", "number"] },
|
|
43492
|
+
buildContext: buildNumberVsNumberContext,
|
|
43493
|
+
suggestions: NUMBER_VS_NUMBER_SUGGESTIONS
|
|
43494
|
+
},
|
|
43495
|
+
{
|
|
43496
|
+
pattern: { leadingTypes: ["categorical", "percentage"] },
|
|
43497
|
+
buildContext: buildCategoricalVsPercentageContext,
|
|
43498
|
+
suggestions: CATEGORICAL_VS_PERCENTAGE_SUGGESTIONS
|
|
43499
|
+
},
|
|
43500
|
+
{
|
|
43501
|
+
pattern: { leadingTypes: ["label", "number"] },
|
|
43502
|
+
buildContext: buildLabelVsNumberContext,
|
|
43503
|
+
suggestions: LABEL_VS_NUMBER_SUGGESTIONS
|
|
43504
|
+
},
|
|
43505
|
+
{
|
|
43506
|
+
pattern: { leadingTypes: ["label", "percentage"] },
|
|
43507
|
+
buildContext: buildCategoricalVsPercentageContext,
|
|
43508
|
+
suggestions: CATEGORICAL_VS_PERCENTAGE_SUGGESTIONS
|
|
43509
|
+
},
|
|
43510
|
+
{
|
|
43511
|
+
pattern: { leadingTypes: [
|
|
43512
|
+
"categorical",
|
|
43513
|
+
"categorical",
|
|
43514
|
+
"number"
|
|
43515
|
+
] },
|
|
43516
|
+
buildContext: buildMultipleCategoricalsVsNumberContext,
|
|
43517
|
+
suggestions: MULTIPLE_CATEGORICALS_VS_NUMBER_SUGGESTIONS
|
|
43518
|
+
},
|
|
43519
|
+
{
|
|
43520
|
+
pattern: { leadingTypes: [
|
|
43521
|
+
"categorical",
|
|
43522
|
+
"label",
|
|
43523
|
+
"number"
|
|
43524
|
+
] },
|
|
43525
|
+
buildContext: buildMultipleCategoricalsVsNumberContext,
|
|
43526
|
+
suggestions: MULTIPLE_CATEGORICALS_VS_NUMBER_SUGGESTIONS
|
|
43527
|
+
},
|
|
43528
|
+
{
|
|
43529
|
+
pattern: { leadingTypes: [
|
|
43530
|
+
"categorical",
|
|
43531
|
+
"date",
|
|
43532
|
+
NUMBER_OR_PERCENTAGE
|
|
43533
|
+
] },
|
|
43534
|
+
buildContext: buildCategoricalDateNumberContext,
|
|
43535
|
+
suggestions: CATEGORICAL_DATE_NUMBER_SUGGESTIONS
|
|
43536
|
+
},
|
|
43537
|
+
{
|
|
43538
|
+
pattern: { leadingTypes: [
|
|
43539
|
+
"categorical",
|
|
43540
|
+
NUMBER_OR_PERCENTAGE,
|
|
43541
|
+
NUMBER_OR_PERCENTAGE
|
|
43542
|
+
] },
|
|
43543
|
+
buildContext: buildCategoricalTwoNumbersContext,
|
|
43544
|
+
suggestions: CATEGORICAL_TWO_NUMBERS_SUGGESTIONS
|
|
43545
|
+
}
|
|
43546
|
+
];
|
|
43547
|
+
const EXTENDABLE_PATTERNS = [
|
|
43548
|
+
{
|
|
43549
|
+
pattern: {
|
|
43550
|
+
leadingTypes: [],
|
|
43551
|
+
extension: {
|
|
43552
|
+
type: NUMBER_OR_PERCENTAGE,
|
|
43553
|
+
minSize: 3
|
|
43554
|
+
}
|
|
43555
|
+
},
|
|
43556
|
+
buildContext: buildManyNumbersContext,
|
|
43557
|
+
suggestions: MANY_NUMBERS_SUGGESTIONS
|
|
43558
|
+
},
|
|
43559
|
+
{
|
|
43560
|
+
pattern: {
|
|
43561
|
+
leadingTypes: ["label"],
|
|
43562
|
+
extension: {
|
|
43563
|
+
type: NUMBER_OR_PERCENTAGE,
|
|
43564
|
+
minSize: 2
|
|
43565
|
+
}
|
|
43566
|
+
},
|
|
43567
|
+
buildContext: buildLabelVsMultipleNumbersContext,
|
|
43568
|
+
suggestions: LABEL_VS_MULTIPLE_NUMBERS_SUGGESTIONS
|
|
43569
|
+
},
|
|
43570
|
+
{
|
|
43571
|
+
pattern: {
|
|
43572
|
+
leadingTypes: ["categorical"],
|
|
43573
|
+
extension: {
|
|
43574
|
+
type: NUMBER_OR_PERCENTAGE,
|
|
43575
|
+
minSize: 2
|
|
43576
|
+
}
|
|
43577
|
+
},
|
|
43578
|
+
buildContext: buildCategoricalVsMultipleNumbersContext,
|
|
43579
|
+
suggestions: CATEGORICAL_VS_MULTIPLE_NUMBERS_SUGGESTIONS
|
|
43580
|
+
},
|
|
43581
|
+
{
|
|
43582
|
+
pattern: {
|
|
43583
|
+
leadingTypes: ["date"],
|
|
43584
|
+
extension: {
|
|
43585
|
+
type: NUMBER_OR_PERCENTAGE,
|
|
43586
|
+
minSize: 2
|
|
43587
|
+
}
|
|
43588
|
+
},
|
|
43589
|
+
buildContext: buildDateVsMultipleNumbersContext,
|
|
43590
|
+
suggestions: DATE_VS_MULTIPLE_NUMBERS_SUGGESTIONS
|
|
43591
|
+
}
|
|
43592
|
+
];
|
|
43593
|
+
function getChartSuggestions(zones, getters) {
|
|
43594
|
+
const cols = analyzeColumns(zones, getters);
|
|
43595
|
+
if (cols.some((c) => c.type === "error")) return [];
|
|
43596
|
+
const nonEmpty = cols.filter((c) => c.type !== "empty");
|
|
43597
|
+
if (!nonEmpty.length) return [];
|
|
43598
|
+
const shape = nonEmpty.map((c) => c.type);
|
|
43599
|
+
const rule = EXACT_PATTERNS.find((rule) => matchesShape(rule.pattern, shape)) ?? EXTENDABLE_PATTERNS.find((rule) => matchesShape(rule.pattern, shape));
|
|
43600
|
+
if (!rule) return [];
|
|
43601
|
+
const ctx = rule.buildContext(nonEmpty, getters);
|
|
43602
|
+
return rule.suggestions.filter((suggestion) => suggestion.isApplicable?.(ctx) ?? true).map((suggestion) => ({
|
|
43603
|
+
description: suggestion.description.toString(),
|
|
43604
|
+
definition: suggestion.build(ctx)
|
|
43605
|
+
}));
|
|
43606
|
+
}
|
|
43607
|
+
|
|
43608
|
+
//#endregion
|
|
43609
|
+
//#region src/components/side_panel/data_analysis/data_analysis_store.ts
|
|
43610
|
+
var DataAnalysisStore = class extends SpreadsheetStore {
|
|
43611
|
+
mutators = [];
|
|
43612
|
+
hasData = false;
|
|
43613
|
+
chartSuggestions = [];
|
|
43614
|
+
isDirty = false;
|
|
43615
|
+
ranges;
|
|
43616
|
+
constructor(get) {
|
|
43617
|
+
super(get);
|
|
43618
|
+
this.model.selection.observe(this, { handleEvent: () => this.refresh() });
|
|
43619
|
+
this.onDispose(() => {
|
|
43620
|
+
this.model.selection.unobserve(this);
|
|
43621
|
+
});
|
|
43622
|
+
this.refresh();
|
|
43623
|
+
}
|
|
43624
|
+
handle(cmd) {
|
|
43625
|
+
if (invalidateEvaluationCommands.has(cmd.type) || cmd.type === "UPDATE_CELL" && ("content" in cmd || "format" in cmd)) this.isDirty = true;
|
|
43626
|
+
switch (cmd.type) {
|
|
43627
|
+
case "HIDE_COLUMNS_ROWS":
|
|
43628
|
+
case "UNHIDE_COLUMNS_ROWS":
|
|
43629
|
+
case "GROUP_HEADERS":
|
|
43630
|
+
case "UNGROUP_HEADERS":
|
|
43631
|
+
case "ACTIVATE_SHEET":
|
|
43632
|
+
case "ACTIVATE_NEXT_SHEET":
|
|
43633
|
+
case "ACTIVATE_PREVIOUS_SHEET":
|
|
43634
|
+
case "EVALUATE_CELLS":
|
|
43635
|
+
case "SET_FORMATTING":
|
|
43636
|
+
case "CLEAR_FORMATTING":
|
|
43637
|
+
this.isDirty = true;
|
|
43638
|
+
break;
|
|
43639
|
+
}
|
|
43640
|
+
}
|
|
43641
|
+
finalize() {
|
|
43642
|
+
if (this.isDirty) {
|
|
43643
|
+
this.refresh();
|
|
43644
|
+
this.isDirty = false;
|
|
43645
|
+
}
|
|
43646
|
+
}
|
|
43647
|
+
refresh() {
|
|
43648
|
+
const sheetId = this.getters.getActiveSheetId();
|
|
43649
|
+
const zones = this.getters.getSelectedZones();
|
|
43650
|
+
this.ranges = zones.map(zoneToXc);
|
|
43651
|
+
this.hasData = zones.some((zone) => this.getters.getEvaluatedCellsInZone(sheetId, zone).some((cell) => cell.type !== "empty"));
|
|
43652
|
+
const suggestions = this.hasData ? getChartSuggestions(zones, this.getters) : [];
|
|
43653
|
+
this.chartSuggestions = suggestions;
|
|
43654
|
+
}
|
|
43655
|
+
};
|
|
43656
|
+
|
|
43657
|
+
//#endregion
|
|
43658
|
+
//#region src/components/side_panel/data_analysis/data_analysis_panel.ts
|
|
43659
|
+
var DataAnalysisPanel = class extends Component {
|
|
43660
|
+
static template = "o-spreadsheet-DataAnalysisPanel";
|
|
43661
|
+
static components = {
|
|
43662
|
+
Section,
|
|
43663
|
+
ChartSuggestionPreview
|
|
43664
|
+
};
|
|
43665
|
+
store;
|
|
43666
|
+
setup() {
|
|
43667
|
+
this.store = useLocalStore(DataAnalysisStore);
|
|
43668
|
+
}
|
|
43669
|
+
onStartChartSuggestionDrag(definition, ev) {
|
|
43670
|
+
startChartDragAndDrop(this.env, definition, ev);
|
|
43671
|
+
}
|
|
43672
|
+
};
|
|
43673
|
+
|
|
42149
43674
|
//#endregion
|
|
42150
43675
|
//#region src/registries/data_source_registry.ts
|
|
42151
43676
|
const unusedDataSourceRegistry = new Registry();
|
|
@@ -42222,7 +43747,7 @@ set(value) {
|
|
|
42222
43747
|
var DataValidationPreview = class extends Component {
|
|
42223
43748
|
static template = "o-spreadsheet-DataValidationPreview";
|
|
42224
43749
|
props = (0, _odoo_owl.useProps)({ rule: types.DataValidationRule() });
|
|
42225
|
-
dvPreviewRef =
|
|
43750
|
+
dvPreviewRef = _odoo_owl.signal.ref();
|
|
42226
43751
|
setup() {
|
|
42227
43752
|
useHighlightsOnHover(this.dvPreviewRef, this);
|
|
42228
43753
|
}
|
|
@@ -43721,7 +45246,7 @@ set(value) {
|
|
|
43721
45246
|
};
|
|
43722
45247
|
props = (0, _odoo_owl.useProps)({ namedRange: types.NamedRange() });
|
|
43723
45248
|
state = (0, _odoo_owl.proxy)({});
|
|
43724
|
-
namedRangePreviewRef =
|
|
45249
|
+
namedRangePreviewRef = _odoo_owl.signal.ref();
|
|
43725
45250
|
setup() {
|
|
43726
45251
|
useHighlightsOnHover(this.namedRangePreviewRef, this);
|
|
43727
45252
|
}
|
|
@@ -44212,7 +45737,7 @@ set(value) {
|
|
|
44212
45737
|
onClick: types.function(),
|
|
44213
45738
|
scrolledTo: types.or([types.literal("top"), types.literal("bottom")]).optional()
|
|
44214
45739
|
});
|
|
44215
|
-
itemRef =
|
|
45740
|
+
itemRef = _odoo_owl.signal.ref();
|
|
44216
45741
|
setup() {
|
|
44217
45742
|
(0, _odoo_owl.onWillPatch)(() => {
|
|
44218
45743
|
if (this.props.scrolledTo) this.scrollListToSelectedValue();
|
|
@@ -44452,7 +45977,7 @@ set(value) {
|
|
|
44452
45977
|
onFiltersUpdated: types.function()
|
|
44453
45978
|
});
|
|
44454
45979
|
state;
|
|
44455
|
-
buttonFilter =
|
|
45980
|
+
buttonFilter = _odoo_owl.signal.ref();
|
|
44456
45981
|
popover;
|
|
44457
45982
|
setup() {
|
|
44458
45983
|
useExternalListener(window, "click", (ev) => {
|
|
@@ -44581,11 +46106,11 @@ set(value) {
|
|
|
44581
46106
|
onFieldPicked: types.function(),
|
|
44582
46107
|
fields: types.array()
|
|
44583
46108
|
});
|
|
44584
|
-
buttonRef =
|
|
46109
|
+
buttonRef = _odoo_owl.signal.ref(HTMLButtonElement);
|
|
44585
46110
|
popover = (0, _odoo_owl.proxy)({ isOpen: false });
|
|
44586
46111
|
search = (0, _odoo_owl.proxy)({ input: "" });
|
|
44587
46112
|
autoComplete;
|
|
44588
|
-
autofocusRef =
|
|
46113
|
+
autofocusRef = _odoo_owl.signal.ref(HTMLInputElement);
|
|
44589
46114
|
setup() {
|
|
44590
46115
|
this.autoComplete = useLocalStore(AutoCompleteStore);
|
|
44591
46116
|
this.autoComplete.useProvider(this.getProvider());
|
|
@@ -44994,7 +46519,7 @@ set(value) {
|
|
|
44994
46519
|
getScrollableContainerEl: types.function().optional(),
|
|
44995
46520
|
pivotId: types.UID()
|
|
44996
46521
|
});
|
|
44997
|
-
dimensionsRef =
|
|
46522
|
+
dimensionsRef = _odoo_owl.signal.ref();
|
|
44998
46523
|
dragAndDrop = useDragAndDropListItems();
|
|
44999
46524
|
AGGREGATORS = AGGREGATORS;
|
|
45000
46525
|
isDateOrDatetimeField = isDateOrDatetimeField;
|
|
@@ -45065,16 +46590,7 @@ set(value) {
|
|
|
45065
46590
|
});
|
|
45066
46591
|
}
|
|
45067
46592
|
getDimensionElementsRects() {
|
|
45068
|
-
return Array.from(this.dimensionsRef().children).map((el) =>
|
|
45069
|
-
const style = getComputedStyle(el);
|
|
45070
|
-
const rect = el.getBoundingClientRect();
|
|
45071
|
-
return {
|
|
45072
|
-
x: rect.x,
|
|
45073
|
-
y: rect.y,
|
|
45074
|
-
width: rect.width + parseInt(style.marginLeft || "0") + parseInt(style.marginRight || "0"),
|
|
45075
|
-
height: rect.height + parseInt(style.marginTop || "0") + parseInt(style.marginBottom || "0")
|
|
45076
|
-
};
|
|
45077
|
-
});
|
|
46593
|
+
return Array.from(this.dimensionsRef().children).map((el) => getBoundingRectWithMargins(el));
|
|
45078
46594
|
}
|
|
45079
46595
|
removeDimension(dimension) {
|
|
45080
46596
|
const { columns, rows } = this.props.definition;
|
|
@@ -45639,7 +47155,7 @@ set(value) {
|
|
|
45639
47155
|
});
|
|
45640
47156
|
store;
|
|
45641
47157
|
state;
|
|
45642
|
-
pivotSidePanelRef =
|
|
47158
|
+
pivotSidePanelRef = _odoo_owl.signal.ref();
|
|
45643
47159
|
setup() {
|
|
45644
47160
|
this.store = useLocalStore(PivotSidePanelStore, this.props.pivotId);
|
|
45645
47161
|
this.state = (0, _odoo_owl.proxy)({
|
|
@@ -46879,7 +48395,7 @@ set(value) {
|
|
|
46879
48395
|
selected: types.boolean().optional(),
|
|
46880
48396
|
onClick: types.function().optional()
|
|
46881
48397
|
});
|
|
46882
|
-
canvasRef =
|
|
48398
|
+
canvasRef = _odoo_owl.signal.ref(HTMLCanvasElement);
|
|
46883
48399
|
menu = (0, _odoo_owl.proxy)({
|
|
46884
48400
|
isOpen: false,
|
|
46885
48401
|
anchorRect: null,
|
|
@@ -46973,7 +48489,7 @@ set(value) {
|
|
|
46973
48489
|
tableStyles: types.RecordOf(),
|
|
46974
48490
|
type: types.or([types.literal("table"), types.literal("pivot")])
|
|
46975
48491
|
});
|
|
46976
|
-
tableStyleListRef =
|
|
48492
|
+
tableStyleListRef = _odoo_owl.signal.ref();
|
|
46977
48493
|
state = (0, _odoo_owl.proxy)({ selectedCategory: this.initialSelectedCategory });
|
|
46978
48494
|
setup() {
|
|
46979
48495
|
useExternalListener(window, "click", this.onExternalClick, { capture: true });
|
|
@@ -48344,6 +49860,10 @@ set(value) {
|
|
|
48344
49860
|
title: _t("Column statistics"),
|
|
48345
49861
|
Body: ColumnStatsPanel
|
|
48346
49862
|
});
|
|
49863
|
+
sidePanelRegistry.add("DataAnalysisPanel", {
|
|
49864
|
+
title: _t("Data analysis"),
|
|
49865
|
+
Body: DataAnalysisPanel
|
|
49866
|
+
});
|
|
48347
49867
|
sidePanelRegistry.add("TableSidePanel", {
|
|
48348
49868
|
title: _t("Edit table"),
|
|
48349
49869
|
Body: TablePanel,
|
|
@@ -48451,6 +49971,7 @@ set(value) {
|
|
|
48451
49971
|
"changePanelSize",
|
|
48452
49972
|
"resetPanelSize",
|
|
48453
49973
|
"togglePinPanel",
|
|
49974
|
+
"togglePinnedSidePanel",
|
|
48454
49975
|
"closeMainPanel",
|
|
48455
49976
|
"changeSpreadsheetWidth",
|
|
48456
49977
|
"toggleCollapsePanel"
|
|
@@ -48602,6 +50123,19 @@ set(value) {
|
|
|
48602
50123
|
this.secondaryPanel = void 0;
|
|
48603
50124
|
}
|
|
48604
50125
|
}
|
|
50126
|
+
togglePinnedSidePanel(componentTag, panelProps = {}) {
|
|
50127
|
+
if (this.mainPanel?.componentTag === componentTag && this.isMainPanelOpen) {
|
|
50128
|
+
this.closeMainPanel();
|
|
50129
|
+
return;
|
|
50130
|
+
}
|
|
50131
|
+
if (this.secondaryPanel?.componentTag === componentTag && this.isSecondaryPanelOpen) {
|
|
50132
|
+
this.secondaryPanel?.currentPanelProps.onCloseSidePanel?.();
|
|
50133
|
+
this.secondaryPanel = void 0;
|
|
50134
|
+
return;
|
|
50135
|
+
}
|
|
50136
|
+
this.open(componentTag, panelProps);
|
|
50137
|
+
if (!this.mainPanel?.isPinned) this.togglePinPanel();
|
|
50138
|
+
}
|
|
48605
50139
|
toggleCollapsePanel(panel) {
|
|
48606
50140
|
const panelInfo = this[panel];
|
|
48607
50141
|
if (!panelInfo) return;
|
|
@@ -48980,6 +50514,219 @@ set(value) {
|
|
|
48980
50514
|
}
|
|
48981
50515
|
};
|
|
48982
50516
|
|
|
50517
|
+
//#endregion
|
|
50518
|
+
//#region src/helpers/sort_interactive.ts
|
|
50519
|
+
function interactiveSortSelection(env, sheetId, anchor, zone, sortDirection) {
|
|
50520
|
+
let multiColumns = zone.right > zone.left;
|
|
50521
|
+
if (env.model.getters.doesIntersectMerge(sheetId, zone)) {
|
|
50522
|
+
multiColumns = false;
|
|
50523
|
+
let table;
|
|
50524
|
+
for (let row = zone.top; row <= zone.bottom; row++) {
|
|
50525
|
+
table = [];
|
|
50526
|
+
for (let col = zone.left; col <= zone.right; col++) {
|
|
50527
|
+
const merge = env.model.getters.getMerge({
|
|
50528
|
+
sheetId,
|
|
50529
|
+
col,
|
|
50530
|
+
row
|
|
50531
|
+
});
|
|
50532
|
+
if (merge && !table.includes(merge.id.toString())) table.push(merge.id.toString());
|
|
50533
|
+
}
|
|
50534
|
+
if (table.length >= 2) {
|
|
50535
|
+
multiColumns = true;
|
|
50536
|
+
break;
|
|
50537
|
+
}
|
|
50538
|
+
}
|
|
50539
|
+
}
|
|
50540
|
+
if (multiColumns) {
|
|
50541
|
+
interactiveSort(env, sheetId, anchor, zone, sortDirection);
|
|
50542
|
+
return;
|
|
50543
|
+
}
|
|
50544
|
+
const contiguousZone = env.model.getters.getContiguousZone(sheetId, zone);
|
|
50545
|
+
if (isEqual(contiguousZone, zone)) interactiveSort(env, sheetId, anchor, zone, sortDirection);
|
|
50546
|
+
else env.askConfirmation(_t("We found data next to your selection. Since this data was not selected, it will not be sorted. Do you want to extend your selection?"), () => interactiveSort(env, sheetId, anchor, contiguousZone, sortDirection), () => interactiveSort(env, sheetId, anchor, zone, sortDirection));
|
|
50547
|
+
}
|
|
50548
|
+
function interactiveSort(env, sheetId, anchor, zone, sortDirection, sortOptions) {
|
|
50549
|
+
const result = env.model.dispatch("SORT_CELLS", {
|
|
50550
|
+
sheetId,
|
|
50551
|
+
col: anchor.col,
|
|
50552
|
+
row: anchor.row,
|
|
50553
|
+
zone,
|
|
50554
|
+
sortDirection,
|
|
50555
|
+
sortOptions
|
|
50556
|
+
});
|
|
50557
|
+
if (result.isCancelledBecause("InvalidSortZone")) {
|
|
50558
|
+
const { col, row } = anchor;
|
|
50559
|
+
env.model.selection.selectZone({
|
|
50560
|
+
cell: {
|
|
50561
|
+
col,
|
|
50562
|
+
row
|
|
50563
|
+
},
|
|
50564
|
+
zone
|
|
50565
|
+
});
|
|
50566
|
+
env.raiseError(_t("Cannot sort. To sort, select only cells or only merges that have the same size."));
|
|
50567
|
+
}
|
|
50568
|
+
if (result.isCancelledBecause("SortZoneWithArrayFormulas")) {
|
|
50569
|
+
const { col, row } = anchor;
|
|
50570
|
+
env.model.selection.selectZone({
|
|
50571
|
+
cell: {
|
|
50572
|
+
col,
|
|
50573
|
+
row
|
|
50574
|
+
},
|
|
50575
|
+
zone
|
|
50576
|
+
});
|
|
50577
|
+
env.raiseError(_t("Cannot sort a zone with array formulas."));
|
|
50578
|
+
}
|
|
50579
|
+
}
|
|
50580
|
+
|
|
50581
|
+
//#endregion
|
|
50582
|
+
//#region src/stores/trim_whitespace_store.ts
|
|
50583
|
+
var TrimWhitespaceStore = class extends SpreadsheetStore {
|
|
50584
|
+
mutators = ["trimWhitespace"];
|
|
50585
|
+
notificationStore = this.get(NotificationStore);
|
|
50586
|
+
trimWhitespace() {
|
|
50587
|
+
this.model.dispatch("TRIM_WHITESPACE");
|
|
50588
|
+
}
|
|
50589
|
+
handle(cmd) {
|
|
50590
|
+
switch (cmd.type) {
|
|
50591
|
+
case "TRIM_WHITESPACE":
|
|
50592
|
+
this._trimWhitespace();
|
|
50593
|
+
break;
|
|
50594
|
+
}
|
|
50595
|
+
}
|
|
50596
|
+
_trimWhitespace() {
|
|
50597
|
+
const zones = recomputeZones(this.getters.getSelectedZones());
|
|
50598
|
+
const sheetId = this.getters.getActiveSheetId();
|
|
50599
|
+
let count = 0;
|
|
50600
|
+
for (const { col, row } of zones.map(positions).flat()) {
|
|
50601
|
+
const cell = this.getters.getCell({
|
|
50602
|
+
col,
|
|
50603
|
+
row,
|
|
50604
|
+
sheetId
|
|
50605
|
+
});
|
|
50606
|
+
if (!cell) continue;
|
|
50607
|
+
const currentContent = !cell.isFormula ? cell.content : cell.compiledFormula.toFormulaString(this.getters);
|
|
50608
|
+
const trimmedContent = trimContent(currentContent);
|
|
50609
|
+
if (trimmedContent !== currentContent) {
|
|
50610
|
+
count += 1;
|
|
50611
|
+
this.model.dispatch("UPDATE_CELL", {
|
|
50612
|
+
sheetId,
|
|
50613
|
+
col,
|
|
50614
|
+
row,
|
|
50615
|
+
content: trimmedContent
|
|
50616
|
+
});
|
|
50617
|
+
}
|
|
50618
|
+
}
|
|
50619
|
+
const text = count ? _t("Trimmed whitespace from %s cells.", count) : _t("No selected cells had whitespace trimmed.");
|
|
50620
|
+
this.notificationStore.notifyUser({
|
|
50621
|
+
type: "info",
|
|
50622
|
+
text,
|
|
50623
|
+
sticky: false
|
|
50624
|
+
});
|
|
50625
|
+
}
|
|
50626
|
+
};
|
|
50627
|
+
|
|
50628
|
+
//#endregion
|
|
50629
|
+
//#region src/actions/data_actions.ts
|
|
50630
|
+
const sortRange = {
|
|
50631
|
+
name: _t("Sort range"),
|
|
50632
|
+
isVisible: IS_ONLY_ONE_RANGE,
|
|
50633
|
+
icon: "o-spreadsheet-Icon.SORT_RANGE"
|
|
50634
|
+
};
|
|
50635
|
+
const sortAscending = {
|
|
50636
|
+
name: _t("Ascending (A ⟶ Z)"),
|
|
50637
|
+
execute: (env) => {
|
|
50638
|
+
const { anchor, zones } = env.model.getters.getSelection();
|
|
50639
|
+
interactiveSortSelection(env, env.model.getters.getActiveSheetId(), anchor.cell, zones[0], "asc");
|
|
50640
|
+
},
|
|
50641
|
+
icon: "o-spreadsheet-Icon.SORT_ASCENDING"
|
|
50642
|
+
};
|
|
50643
|
+
const dataCleanup = {
|
|
50644
|
+
name: _t("Data cleanup"),
|
|
50645
|
+
icon: "o-spreadsheet-Icon.DATA_CLEANUP"
|
|
50646
|
+
};
|
|
50647
|
+
const removeDuplicates = {
|
|
50648
|
+
name: _t("Remove duplicates"),
|
|
50649
|
+
execute: (env) => {
|
|
50650
|
+
if (getZoneArea(env.model.getters.getSelectedZone()) === 1) env.model.selection.selectTableAroundSelection();
|
|
50651
|
+
env.openSidePanel("RemoveDuplicates", {});
|
|
50652
|
+
},
|
|
50653
|
+
isEnabled: (env) => !env.isSmall
|
|
50654
|
+
};
|
|
50655
|
+
const trimWhitespace = {
|
|
50656
|
+
name: _t("Trim whitespace"),
|
|
50657
|
+
execute: (env) => {
|
|
50658
|
+
env.getStore(TrimWhitespaceStore).trimWhitespace();
|
|
50659
|
+
}
|
|
50660
|
+
};
|
|
50661
|
+
const cleanupDataSources = {
|
|
50662
|
+
name: _t("Remove unused data sources"),
|
|
50663
|
+
execute: (env) => env.openSidePanel("DataSourceCleanup", {}),
|
|
50664
|
+
isEnabled: (env) => !env.isSmall
|
|
50665
|
+
};
|
|
50666
|
+
const sortDescending = {
|
|
50667
|
+
name: _t("Descending (Z ⟶ A)"),
|
|
50668
|
+
execute: (env) => {
|
|
50669
|
+
const { anchor, zones } = env.model.getters.getSelection();
|
|
50670
|
+
interactiveSortSelection(env, env.model.getters.getActiveSheetId(), anchor.cell, zones[0], "desc");
|
|
50671
|
+
},
|
|
50672
|
+
icon: "o-spreadsheet-Icon.SORT_DESCENDING"
|
|
50673
|
+
};
|
|
50674
|
+
const createRemoveFilter = { ...CREATE_OR_REMOVE_FILTER_ACTION };
|
|
50675
|
+
const createRemoveFilterTool = {
|
|
50676
|
+
...CREATE_OR_REMOVE_FILTER_ACTION,
|
|
50677
|
+
isActive: (env) => SELECTED_TABLE_HAS_FILTERS(env)
|
|
50678
|
+
};
|
|
50679
|
+
const splitToColumns = {
|
|
50680
|
+
name: _t("Split text to columns"),
|
|
50681
|
+
sequence: 1,
|
|
50682
|
+
execute: (env) => env.openSidePanel("SplitToColumns", {}),
|
|
50683
|
+
isEnabled: (env) => !env.isSmall && env.model.getters.isSingleColSelected(),
|
|
50684
|
+
icon: "o-spreadsheet-Icon.SPLIT_TEXT"
|
|
50685
|
+
};
|
|
50686
|
+
const dataAnalysis = {
|
|
50687
|
+
name: _t("Data analysis"),
|
|
50688
|
+
execute: (env) => {
|
|
50689
|
+
env.getStore(SidePanelStore).togglePinnedSidePanel("DataAnalysisPanel");
|
|
50690
|
+
},
|
|
50691
|
+
icon: "o-spreadsheet-Icon.COLUMN_STATS",
|
|
50692
|
+
isEnabled: (env) => !env.isSmall
|
|
50693
|
+
};
|
|
50694
|
+
const columnStatistics = {
|
|
50695
|
+
name: _t("Column statistics"),
|
|
50696
|
+
execute: (env) => env.openSidePanel("ColumnStats", {}),
|
|
50697
|
+
icon: "o-spreadsheet-Icon.COLUMN_STATS"
|
|
50698
|
+
};
|
|
50699
|
+
const reinsertDynamicPivotMenu = {
|
|
50700
|
+
id: "reinsert_dynamic_pivot",
|
|
50701
|
+
name: _t("Re-insert dynamic pivot"),
|
|
50702
|
+
sequence: 60,
|
|
50703
|
+
icon: "o-spreadsheet-Icon.INSERT_PIVOT",
|
|
50704
|
+
children: [REINSERT_DYNAMIC_PIVOT_CHILDREN],
|
|
50705
|
+
isVisible: (env) => env.model.getters.getPivotIds().some((id) => env.model.getters.getPivot(id).isValid())
|
|
50706
|
+
};
|
|
50707
|
+
const reinsertStaticPivotMenu = {
|
|
50708
|
+
id: "reinsert_static_pivot",
|
|
50709
|
+
name: _t("Re-insert static pivot"),
|
|
50710
|
+
sequence: 70,
|
|
50711
|
+
icon: "o-spreadsheet-Icon.INSERT_PIVOT",
|
|
50712
|
+
children: [REINSERT_STATIC_PIVOT_CHILDREN],
|
|
50713
|
+
isVisible: (env) => env.model.getters.getPivotIds().some((id) => env.model.getters.getPivot(id).isValid())
|
|
50714
|
+
};
|
|
50715
|
+
const calculationMode = {
|
|
50716
|
+
name: _t("Calculation"),
|
|
50717
|
+
icon: "o-spreadsheet-Icon.CALCULATOR"
|
|
50718
|
+
};
|
|
50719
|
+
const automaticCalculation = {
|
|
50720
|
+
name: _t("Automatic"),
|
|
50721
|
+
execute: (env) => env.model.dispatch("SET_AUTOMATIC_EVALUATION", { enabled: true }),
|
|
50722
|
+
isActive: (env) => env.model.getters.isAutomaticEvaluationEnabled()
|
|
50723
|
+
};
|
|
50724
|
+
const manualCalculation = {
|
|
50725
|
+
name: _t("Manual"),
|
|
50726
|
+
execute: (env) => env.model.dispatch("SET_AUTOMATIC_EVALUATION", { enabled: false }),
|
|
50727
|
+
isActive: (env) => !env.model.getters.isAutomaticEvaluationEnabled()
|
|
50728
|
+
};
|
|
50729
|
+
|
|
48983
50730
|
//#endregion
|
|
48984
50731
|
//#region src/helpers/ui/merge_interactive.ts
|
|
48985
50732
|
const AddMergeInteractiveContent = {
|
|
@@ -49194,6 +50941,10 @@ set(value) {
|
|
|
49194
50941
|
}).addChild("paste_format_only", ["paste_special"], {
|
|
49195
50942
|
...pasteSpecialFormat,
|
|
49196
50943
|
sequence: 20
|
|
50944
|
+
}).add("data_analysis", {
|
|
50945
|
+
...dataAnalysis,
|
|
50946
|
+
sequence: 50,
|
|
50947
|
+
separator: true
|
|
49197
50948
|
}).add("add_row_before", {
|
|
49198
50949
|
...cellInsertRowsBefore,
|
|
49199
50950
|
sequence: 70
|
|
@@ -49301,211 +51052,6 @@ set(value) {
|
|
|
49301
51052
|
sequence: 30
|
|
49302
51053
|
});
|
|
49303
51054
|
|
|
49304
|
-
//#endregion
|
|
49305
|
-
//#region src/helpers/sort_interactive.ts
|
|
49306
|
-
function interactiveSortSelection(env, sheetId, anchor, zone, sortDirection) {
|
|
49307
|
-
let multiColumns = zone.right > zone.left;
|
|
49308
|
-
if (env.model.getters.doesIntersectMerge(sheetId, zone)) {
|
|
49309
|
-
multiColumns = false;
|
|
49310
|
-
let table;
|
|
49311
|
-
for (let row = zone.top; row <= zone.bottom; row++) {
|
|
49312
|
-
table = [];
|
|
49313
|
-
for (let col = zone.left; col <= zone.right; col++) {
|
|
49314
|
-
const merge = env.model.getters.getMerge({
|
|
49315
|
-
sheetId,
|
|
49316
|
-
col,
|
|
49317
|
-
row
|
|
49318
|
-
});
|
|
49319
|
-
if (merge && !table.includes(merge.id.toString())) table.push(merge.id.toString());
|
|
49320
|
-
}
|
|
49321
|
-
if (table.length >= 2) {
|
|
49322
|
-
multiColumns = true;
|
|
49323
|
-
break;
|
|
49324
|
-
}
|
|
49325
|
-
}
|
|
49326
|
-
}
|
|
49327
|
-
if (multiColumns) {
|
|
49328
|
-
interactiveSort(env, sheetId, anchor, zone, sortDirection);
|
|
49329
|
-
return;
|
|
49330
|
-
}
|
|
49331
|
-
const contiguousZone = env.model.getters.getContiguousZone(sheetId, zone);
|
|
49332
|
-
if (isEqual(contiguousZone, zone)) interactiveSort(env, sheetId, anchor, zone, sortDirection);
|
|
49333
|
-
else env.askConfirmation(_t("We found data next to your selection. Since this data was not selected, it will not be sorted. Do you want to extend your selection?"), () => interactiveSort(env, sheetId, anchor, contiguousZone, sortDirection), () => interactiveSort(env, sheetId, anchor, zone, sortDirection));
|
|
49334
|
-
}
|
|
49335
|
-
function interactiveSort(env, sheetId, anchor, zone, sortDirection, sortOptions) {
|
|
49336
|
-
const result = env.model.dispatch("SORT_CELLS", {
|
|
49337
|
-
sheetId,
|
|
49338
|
-
col: anchor.col,
|
|
49339
|
-
row: anchor.row,
|
|
49340
|
-
zone,
|
|
49341
|
-
sortDirection,
|
|
49342
|
-
sortOptions
|
|
49343
|
-
});
|
|
49344
|
-
if (result.isCancelledBecause("InvalidSortZone")) {
|
|
49345
|
-
const { col, row } = anchor;
|
|
49346
|
-
env.model.selection.selectZone({
|
|
49347
|
-
cell: {
|
|
49348
|
-
col,
|
|
49349
|
-
row
|
|
49350
|
-
},
|
|
49351
|
-
zone
|
|
49352
|
-
});
|
|
49353
|
-
env.raiseError(_t("Cannot sort. To sort, select only cells or only merges that have the same size."));
|
|
49354
|
-
}
|
|
49355
|
-
if (result.isCancelledBecause("SortZoneWithArrayFormulas")) {
|
|
49356
|
-
const { col, row } = anchor;
|
|
49357
|
-
env.model.selection.selectZone({
|
|
49358
|
-
cell: {
|
|
49359
|
-
col,
|
|
49360
|
-
row
|
|
49361
|
-
},
|
|
49362
|
-
zone
|
|
49363
|
-
});
|
|
49364
|
-
env.raiseError(_t("Cannot sort a zone with array formulas."));
|
|
49365
|
-
}
|
|
49366
|
-
}
|
|
49367
|
-
|
|
49368
|
-
//#endregion
|
|
49369
|
-
//#region src/stores/trim_whitespace_store.ts
|
|
49370
|
-
var TrimWhitespaceStore = class extends SpreadsheetStore {
|
|
49371
|
-
mutators = ["trimWhitespace"];
|
|
49372
|
-
notificationStore = this.get(NotificationStore);
|
|
49373
|
-
trimWhitespace() {
|
|
49374
|
-
this.model.dispatch("TRIM_WHITESPACE");
|
|
49375
|
-
}
|
|
49376
|
-
handle(cmd) {
|
|
49377
|
-
switch (cmd.type) {
|
|
49378
|
-
case "TRIM_WHITESPACE":
|
|
49379
|
-
this._trimWhitespace();
|
|
49380
|
-
break;
|
|
49381
|
-
}
|
|
49382
|
-
}
|
|
49383
|
-
_trimWhitespace() {
|
|
49384
|
-
const zones = recomputeZones(this.getters.getSelectedZones());
|
|
49385
|
-
const sheetId = this.getters.getActiveSheetId();
|
|
49386
|
-
let count = 0;
|
|
49387
|
-
for (const { col, row } of zones.map(positions).flat()) {
|
|
49388
|
-
const cell = this.getters.getCell({
|
|
49389
|
-
col,
|
|
49390
|
-
row,
|
|
49391
|
-
sheetId
|
|
49392
|
-
});
|
|
49393
|
-
if (!cell) continue;
|
|
49394
|
-
const currentContent = !cell.isFormula ? cell.content : cell.compiledFormula.toFormulaString(this.getters);
|
|
49395
|
-
const trimmedContent = trimContent(currentContent);
|
|
49396
|
-
if (trimmedContent !== currentContent) {
|
|
49397
|
-
count += 1;
|
|
49398
|
-
this.model.dispatch("UPDATE_CELL", {
|
|
49399
|
-
sheetId,
|
|
49400
|
-
col,
|
|
49401
|
-
row,
|
|
49402
|
-
content: trimmedContent
|
|
49403
|
-
});
|
|
49404
|
-
}
|
|
49405
|
-
}
|
|
49406
|
-
const text = count ? _t("Trimmed whitespace from %s cells.", count) : _t("No selected cells had whitespace trimmed.");
|
|
49407
|
-
this.notificationStore.notifyUser({
|
|
49408
|
-
type: "info",
|
|
49409
|
-
text,
|
|
49410
|
-
sticky: false
|
|
49411
|
-
});
|
|
49412
|
-
}
|
|
49413
|
-
};
|
|
49414
|
-
|
|
49415
|
-
//#endregion
|
|
49416
|
-
//#region src/actions/data_actions.ts
|
|
49417
|
-
const sortRange = {
|
|
49418
|
-
name: _t("Sort range"),
|
|
49419
|
-
isVisible: IS_ONLY_ONE_RANGE,
|
|
49420
|
-
icon: "o-spreadsheet-Icon.SORT_RANGE"
|
|
49421
|
-
};
|
|
49422
|
-
const sortAscending = {
|
|
49423
|
-
name: _t("Ascending (A ⟶ Z)"),
|
|
49424
|
-
execute: (env) => {
|
|
49425
|
-
const { anchor, zones } = env.model.getters.getSelection();
|
|
49426
|
-
interactiveSortSelection(env, env.model.getters.getActiveSheetId(), anchor.cell, zones[0], "asc");
|
|
49427
|
-
},
|
|
49428
|
-
icon: "o-spreadsheet-Icon.SORT_ASCENDING"
|
|
49429
|
-
};
|
|
49430
|
-
const dataCleanup = {
|
|
49431
|
-
name: _t("Data cleanup"),
|
|
49432
|
-
icon: "o-spreadsheet-Icon.DATA_CLEANUP"
|
|
49433
|
-
};
|
|
49434
|
-
const removeDuplicates = {
|
|
49435
|
-
name: _t("Remove duplicates"),
|
|
49436
|
-
execute: (env) => {
|
|
49437
|
-
if (getZoneArea(env.model.getters.getSelectedZone()) === 1) env.model.selection.selectTableAroundSelection();
|
|
49438
|
-
env.openSidePanel("RemoveDuplicates", {});
|
|
49439
|
-
},
|
|
49440
|
-
isEnabled: (env) => !env.isSmall
|
|
49441
|
-
};
|
|
49442
|
-
const trimWhitespace = {
|
|
49443
|
-
name: _t("Trim whitespace"),
|
|
49444
|
-
execute: (env) => {
|
|
49445
|
-
env.getStore(TrimWhitespaceStore).trimWhitespace();
|
|
49446
|
-
}
|
|
49447
|
-
};
|
|
49448
|
-
const cleanupDataSources = {
|
|
49449
|
-
name: _t("Remove unused data sources"),
|
|
49450
|
-
execute: (env) => env.openSidePanel("DataSourceCleanup", {}),
|
|
49451
|
-
isEnabled: (env) => !env.isSmall
|
|
49452
|
-
};
|
|
49453
|
-
const sortDescending = {
|
|
49454
|
-
name: _t("Descending (Z ⟶ A)"),
|
|
49455
|
-
execute: (env) => {
|
|
49456
|
-
const { anchor, zones } = env.model.getters.getSelection();
|
|
49457
|
-
interactiveSortSelection(env, env.model.getters.getActiveSheetId(), anchor.cell, zones[0], "desc");
|
|
49458
|
-
},
|
|
49459
|
-
icon: "o-spreadsheet-Icon.SORT_DESCENDING"
|
|
49460
|
-
};
|
|
49461
|
-
const createRemoveFilter = { ...CREATE_OR_REMOVE_FILTER_ACTION };
|
|
49462
|
-
const createRemoveFilterTool = {
|
|
49463
|
-
...CREATE_OR_REMOVE_FILTER_ACTION,
|
|
49464
|
-
isActive: (env) => SELECTED_TABLE_HAS_FILTERS(env)
|
|
49465
|
-
};
|
|
49466
|
-
const splitToColumns = {
|
|
49467
|
-
name: _t("Split text to columns"),
|
|
49468
|
-
sequence: 1,
|
|
49469
|
-
execute: (env) => env.openSidePanel("SplitToColumns", {}),
|
|
49470
|
-
isEnabled: (env) => !env.isSmall && env.model.getters.isSingleColSelected(),
|
|
49471
|
-
icon: "o-spreadsheet-Icon.SPLIT_TEXT"
|
|
49472
|
-
};
|
|
49473
|
-
const columnStatistics = {
|
|
49474
|
-
name: _t("Column statistics"),
|
|
49475
|
-
execute: (env) => env.openSidePanel("ColumnStats", {}),
|
|
49476
|
-
icon: "o-spreadsheet-Icon.COLUMN_STATS"
|
|
49477
|
-
};
|
|
49478
|
-
const reinsertDynamicPivotMenu = {
|
|
49479
|
-
id: "reinsert_dynamic_pivot",
|
|
49480
|
-
name: _t("Re-insert dynamic pivot"),
|
|
49481
|
-
sequence: 60,
|
|
49482
|
-
icon: "o-spreadsheet-Icon.INSERT_PIVOT",
|
|
49483
|
-
children: [REINSERT_DYNAMIC_PIVOT_CHILDREN],
|
|
49484
|
-
isVisible: (env) => env.model.getters.getPivotIds().some((id) => env.model.getters.getPivot(id).isValid())
|
|
49485
|
-
};
|
|
49486
|
-
const reinsertStaticPivotMenu = {
|
|
49487
|
-
id: "reinsert_static_pivot",
|
|
49488
|
-
name: _t("Re-insert static pivot"),
|
|
49489
|
-
sequence: 70,
|
|
49490
|
-
icon: "o-spreadsheet-Icon.INSERT_PIVOT",
|
|
49491
|
-
children: [REINSERT_STATIC_PIVOT_CHILDREN],
|
|
49492
|
-
isVisible: (env) => env.model.getters.getPivotIds().some((id) => env.model.getters.getPivot(id).isValid())
|
|
49493
|
-
};
|
|
49494
|
-
const calculationMode = {
|
|
49495
|
-
name: _t("Calculation"),
|
|
49496
|
-
icon: "o-spreadsheet-Icon.CALCULATOR"
|
|
49497
|
-
};
|
|
49498
|
-
const automaticCalculation = {
|
|
49499
|
-
name: _t("Automatic"),
|
|
49500
|
-
execute: (env) => env.model.dispatch("SET_AUTOMATIC_EVALUATION", { enabled: true }),
|
|
49501
|
-
isActive: (env) => env.model.getters.isAutomaticEvaluationEnabled()
|
|
49502
|
-
};
|
|
49503
|
-
const manualCalculation = {
|
|
49504
|
-
name: _t("Manual"),
|
|
49505
|
-
execute: (env) => env.model.dispatch("SET_AUTOMATIC_EVALUATION", { enabled: false }),
|
|
49506
|
-
isActive: (env) => !env.model.getters.isAutomaticEvaluationEnabled()
|
|
49507
|
-
};
|
|
49508
|
-
|
|
49509
51055
|
//#endregion
|
|
49510
51056
|
//#region src/registries/menus/col_menu_registry.ts
|
|
49511
51057
|
const colMenuRegistry = new MenuItemRegistry();
|
|
@@ -49539,6 +51085,9 @@ set(value) {
|
|
|
49539
51085
|
}).addChild("sort_descending", ["sort_columns"], {
|
|
49540
51086
|
...sortDescending,
|
|
49541
51087
|
sequence: 20
|
|
51088
|
+
}).add("data_analysis", {
|
|
51089
|
+
...dataAnalysis,
|
|
51090
|
+
sequence: 55
|
|
49542
51091
|
}).add("column_statistics", {
|
|
49543
51092
|
...columnStatistics,
|
|
49544
51093
|
sequence: 60,
|
|
@@ -52354,14 +53903,15 @@ set(value) {
|
|
|
52354
53903
|
selectedRect: void 0,
|
|
52355
53904
|
horizontalSnap: void 0,
|
|
52356
53905
|
verticalSnap: void 0,
|
|
52357
|
-
cancelDnd: void 0
|
|
52358
|
-
overlappingChartOrCarousel: void 0
|
|
53906
|
+
cancelDnd: void 0
|
|
52359
53907
|
});
|
|
52360
53908
|
viewStore;
|
|
52361
53909
|
zoomStore;
|
|
53910
|
+
chartDragStore;
|
|
52362
53911
|
setup() {
|
|
52363
53912
|
this.viewStore = useStore(ViewportsStore);
|
|
52364
53913
|
this.zoomStore = useStore(ZoomStore);
|
|
53914
|
+
this.chartDragStore = useStore(ChartDragStore);
|
|
52365
53915
|
(0, _odoo_owl.onMounted)(() => {
|
|
52366
53916
|
this.render();
|
|
52367
53917
|
});
|
|
@@ -52375,7 +53925,7 @@ set(value) {
|
|
|
52375
53925
|
this.dnd.selectedRect = void 0;
|
|
52376
53926
|
this.dnd.horizontalSnap = void 0;
|
|
52377
53927
|
this.dnd.verticalSnap = void 0;
|
|
52378
|
-
this.
|
|
53928
|
+
this.chartDragStore.setHighlightedFigure(void 0);
|
|
52379
53929
|
this.dnd.cancelDnd = void 0;
|
|
52380
53930
|
}
|
|
52381
53931
|
});
|
|
@@ -52501,6 +54051,7 @@ set(value) {
|
|
|
52501
54051
|
maxY: this.env.model.getters.getRowDimensions(sheetId, this.env.model.getters.getNumberRows(sheetId) - 1).end
|
|
52502
54052
|
};
|
|
52503
54053
|
let hasStartedDnd = false;
|
|
54054
|
+
let overlappingChartOrCarousel = void 0;
|
|
52504
54055
|
const onMouseMove = (ev) => {
|
|
52505
54056
|
const currentMousePosition = {
|
|
52506
54057
|
x: ev.clientX / zoom,
|
|
@@ -52512,10 +54063,10 @@ set(value) {
|
|
|
52512
54063
|
hasStartedDnd = true;
|
|
52513
54064
|
const selectedFigures = dragFigureForMove(currentMousePosition, initialMousePosition, initialFigures, maxDimensions, initialScrollPosition, this.viewStore.activeSheetScrollInfo);
|
|
52514
54065
|
const draggedFigure = selectedFigures.find((f) => f.id === draggedFigureId);
|
|
52515
|
-
|
|
54066
|
+
overlappingChartOrCarousel = void 0;
|
|
52516
54067
|
const otherFigures = this.getOtherFigures(selectedFigures.map((f) => f.id));
|
|
52517
|
-
if (draggedFigure && !selectedFigures.find((f) => f.tag !== "chart")) overlappingChartOrCarousel =
|
|
52518
|
-
this.
|
|
54068
|
+
if (draggedFigure && !selectedFigures.find((f) => f.tag !== "chart")) overlappingChartOrCarousel = getCarouselOverlappingChart(draggedFigure, otherFigures, ["carousel", "chart"]);
|
|
54069
|
+
this.chartDragStore.setHighlightedFigure(overlappingChartOrCarousel?.id);
|
|
52519
54070
|
if (!overlappingChartOrCarousel) {
|
|
52520
54071
|
const snapReturn = snapForMove(this.env, selectedFigures, otherFigures);
|
|
52521
54072
|
this.dnd.selectedFigures = snapReturn.snappedFigures;
|
|
@@ -52537,7 +54088,7 @@ set(value) {
|
|
|
52537
54088
|
else this.env.model.dispatch("SELECT_FIGURE", { figureId: figureUI.id });
|
|
52538
54089
|
return;
|
|
52539
54090
|
}
|
|
52540
|
-
if (!
|
|
54091
|
+
if (!overlappingChartOrCarousel) {
|
|
52541
54092
|
const payloads = this.dnd.selectedFigures?.map((f) => {
|
|
52542
54093
|
return {
|
|
52543
54094
|
sheetId,
|
|
@@ -52547,14 +54098,14 @@ set(value) {
|
|
|
52547
54098
|
}) || [];
|
|
52548
54099
|
this.env.model.dispatch("MOVE_FIGURES", { figures: payloads });
|
|
52549
54100
|
} else {
|
|
52550
|
-
const overlappingFigureId =
|
|
54101
|
+
const overlappingFigureId = overlappingChartOrCarousel.id;
|
|
52551
54102
|
const chartFigureIds = this.dnd.selectedFigures?.map((f) => f.id) || [];
|
|
52552
|
-
if (
|
|
54103
|
+
if (overlappingChartOrCarousel.tag === "carousel") this.env.model.dispatch("ADD_FIGURES_CHART_TO_CAROUSEL", {
|
|
52553
54104
|
sheetId,
|
|
52554
54105
|
carouselFigureId: overlappingFigureId,
|
|
52555
54106
|
chartFigureIds
|
|
52556
54107
|
});
|
|
52557
|
-
else if (
|
|
54108
|
+
else if (overlappingChartOrCarousel.tag === "chart") this.env.model.dispatch("MERGE_CHART_FIGURES_INTO_CAROUSEL", {
|
|
52558
54109
|
sheetId,
|
|
52559
54110
|
baseFigureId: overlappingFigureId,
|
|
52560
54111
|
chartFigureIds: [overlappingFigureId, ...chartFigureIds]
|
|
@@ -52565,7 +54116,7 @@ set(value) {
|
|
|
52565
54116
|
this.dnd.selectedRect = void 0;
|
|
52566
54117
|
this.dnd.horizontalSnap = void 0;
|
|
52567
54118
|
this.dnd.verticalSnap = void 0;
|
|
52568
|
-
this.
|
|
54119
|
+
this.chartDragStore.setHighlightedFigure(void 0);
|
|
52569
54120
|
};
|
|
52570
54121
|
this.dnd.cancelDnd = startDnd(onMouseMove, onMouseUp);
|
|
52571
54122
|
}
|
|
@@ -52621,7 +54172,6 @@ set(value) {
|
|
|
52621
54172
|
this.dnd.selectedRect = void 0;
|
|
52622
54173
|
this.dnd.horizontalSnap = void 0;
|
|
52623
54174
|
this.dnd.verticalSnap = void 0;
|
|
52624
|
-
this.dnd.overlappingChartOrCarousel = void 0;
|
|
52625
54175
|
};
|
|
52626
54176
|
this.dnd.cancelDnd = startDnd(onMouseMove, onMouseUp);
|
|
52627
54177
|
}
|
|
@@ -52631,12 +54181,12 @@ set(value) {
|
|
|
52631
54181
|
getFigureStyle(figureUI) {
|
|
52632
54182
|
if (figureUI.id !== this.dnd.draggedFigure?.id) return "";
|
|
52633
54183
|
return cssPropertiesToCss({
|
|
52634
|
-
opacity: this.
|
|
54184
|
+
opacity: this.chartDragStore.highlightedFigureId ? "0.6" : "0.9",
|
|
52635
54185
|
cursor: "grabbing"
|
|
52636
54186
|
});
|
|
52637
54187
|
}
|
|
52638
54188
|
getFigureClass(figureUI) {
|
|
52639
|
-
if (figureUI.id !== this.
|
|
54189
|
+
if (figureUI.id !== this.chartDragStore.highlightedFigureId) return "";
|
|
52640
54190
|
return "o-add-to-carousel";
|
|
52641
54191
|
}
|
|
52642
54192
|
getSnap(snapLine) {
|
|
@@ -52679,26 +54229,6 @@ set(value) {
|
|
|
52679
54229
|
height: `100%`
|
|
52680
54230
|
});
|
|
52681
54231
|
}
|
|
52682
|
-
getOverlappingFigure(figureUI, otherFigures) {
|
|
52683
|
-
if (figureUI.tag !== "chart") return;
|
|
52684
|
-
const figureCenterX = figureUI.x + figureUI.width / 2;
|
|
52685
|
-
const figureCenterY = figureUI.y + figureUI.height / 2;
|
|
52686
|
-
let bestMatch;
|
|
52687
|
-
let smallestDistance = Infinity;
|
|
52688
|
-
for (const figure of otherFigures) {
|
|
52689
|
-
if (figure.tag !== "chart" && figure.tag !== "carousel") continue;
|
|
52690
|
-
const targetCenterX = figure.x + figure.width / 2;
|
|
52691
|
-
const targetCenterY = figure.y + figure.height / 2;
|
|
52692
|
-
const distanceX = Math.abs(figureCenterX - targetCenterX);
|
|
52693
|
-
const distanceY = Math.abs(figureCenterY - targetCenterY);
|
|
52694
|
-
const squaredDistance = distanceX ** 2 + distanceY ** 2;
|
|
52695
|
-
if (distanceX <= figureUI.width / 2 && distanceY <= figureUI.height / 2 && squaredDistance < smallestDistance) {
|
|
52696
|
-
smallestDistance = squaredDistance;
|
|
52697
|
-
bestMatch = figure;
|
|
52698
|
-
}
|
|
52699
|
-
}
|
|
52700
|
-
return bestMatch;
|
|
52701
|
-
}
|
|
52702
54232
|
};
|
|
52703
54233
|
|
|
52704
54234
|
//#endregion
|
|
@@ -52998,7 +54528,7 @@ set(value) {
|
|
|
52998
54528
|
var ColResizer = class extends AbstractResizer {
|
|
52999
54529
|
static template = "o-spreadsheet-ColResizer";
|
|
53000
54530
|
static components = { UnhideColumnHeaders };
|
|
53001
|
-
colResizerRef =
|
|
54531
|
+
colResizerRef = _odoo_owl.signal.ref();
|
|
53002
54532
|
setup() {
|
|
53003
54533
|
super.setup();
|
|
53004
54534
|
this.PADDING = 15;
|
|
@@ -53132,7 +54662,7 @@ set(value) {
|
|
|
53132
54662
|
var RowResizer = class extends AbstractResizer {
|
|
53133
54663
|
static template = "o-spreadsheet-RowResizer";
|
|
53134
54664
|
static components = { UnhideRowHeaders };
|
|
53135
|
-
rowResizerRef =
|
|
54665
|
+
rowResizerRef = _odoo_owl.signal.ref();
|
|
53136
54666
|
setup() {
|
|
53137
54667
|
super.setup();
|
|
53138
54668
|
this.PADDING = 5;
|
|
@@ -53741,9 +55271,8 @@ set(value) {
|
|
|
53741
55271
|
useStore(TableResizeStore);
|
|
53742
55272
|
}
|
|
53743
55273
|
get containerStyle() {
|
|
53744
|
-
const tableZone = this.props.table.range.zone;
|
|
53745
55274
|
const sheetId = this.props.table.range.sheetId;
|
|
53746
|
-
|
|
55275
|
+
const tableZone = this.props.table.range.zone;
|
|
53747
55276
|
const bottomRight = {
|
|
53748
55277
|
...tableZone,
|
|
53749
55278
|
left: tableZone.right,
|
|
@@ -53830,8 +55359,8 @@ set(value) {
|
|
|
53830
55359
|
HEADER_HEIGHT = 26;
|
|
53831
55360
|
HEADER_WIDTH = 48;
|
|
53832
55361
|
menuState;
|
|
53833
|
-
gridRef =
|
|
53834
|
-
canvasRef =
|
|
55362
|
+
gridRef = _odoo_owl.signal.ref();
|
|
55363
|
+
canvasRef = _odoo_owl.signal.ref(HTMLCanvasElement);
|
|
53835
55364
|
highlightStore;
|
|
53836
55365
|
viewStore;
|
|
53837
55366
|
zoomStore;
|
|
@@ -54462,6 +55991,9 @@ set(value) {
|
|
|
54462
55991
|
const sheetId = this.env.model.getters.getActiveSheetId();
|
|
54463
55992
|
return this.env.model.getters.getClientsToDisplay(sheetId);
|
|
54464
55993
|
}
|
|
55994
|
+
get displayTableResizer() {
|
|
55995
|
+
return this.env.model.getters.isGridSelectionActive() && !this.env.model.getters.isReadonly() && !this.env.model.getters.isSheetLocked(this.env.model.getters.getActiveSheetId());
|
|
55996
|
+
}
|
|
54465
55997
|
};
|
|
54466
55998
|
|
|
54467
55999
|
//#endregion
|
|
@@ -54480,7 +56012,7 @@ set(value) {
|
|
|
54480
56012
|
static template = "o-spreadsheet-FullScreenFigure";
|
|
54481
56013
|
static components = { ChartFigure };
|
|
54482
56014
|
fullScreenFigureStore;
|
|
54483
|
-
fullScreenFigureRef =
|
|
56015
|
+
fullScreenFigureRef = _odoo_owl.signal.ref();
|
|
54484
56016
|
spreadsheetRect = useSpreadsheetRect();
|
|
54485
56017
|
figureRegistry = figureRegistry;
|
|
54486
56018
|
setup() {
|
|
@@ -54728,6 +56260,41 @@ set(value) {
|
|
|
54728
56260
|
}
|
|
54729
56261
|
};
|
|
54730
56262
|
|
|
56263
|
+
//#endregion
|
|
56264
|
+
//#region src/components/tables/hovered_table_store.ts
|
|
56265
|
+
var HoveredTableStore = class extends SpreadsheetStore {
|
|
56266
|
+
mutators = ["hover"];
|
|
56267
|
+
storeGetters = ["getCellHoverOverlayColor"];
|
|
56268
|
+
position;
|
|
56269
|
+
constructor(get) {
|
|
56270
|
+
super(get);
|
|
56271
|
+
const cellHoverOverlayStore = this.get(CellHoverOverlayStore);
|
|
56272
|
+
cellHoverOverlayStore.register(this);
|
|
56273
|
+
this.onDispose(() => cellHoverOverlayStore.unRegister(this));
|
|
56274
|
+
}
|
|
56275
|
+
getHighlightedPositions(hoveredPosition) {
|
|
56276
|
+
const highlightedPositions = [];
|
|
56277
|
+
const { sheetId, row } = hoveredPosition;
|
|
56278
|
+
const table = this.getters.getTable(hoveredPosition);
|
|
56279
|
+
if (!table) return highlightedPositions;
|
|
56280
|
+
const { left, right, top } = table.range.zone;
|
|
56281
|
+
const isTableHeader = row < top + table.config.numberOfHeaders;
|
|
56282
|
+
const doesTableRowHaveContent = range(left, right + 1).some((col) => {
|
|
56283
|
+
return !this.getters.isColHidden(sheetId, col) && this.getters.getEvaluatedCell({
|
|
56284
|
+
sheetId,
|
|
56285
|
+
col,
|
|
56286
|
+
row
|
|
56287
|
+
}).formattedValue;
|
|
56288
|
+
});
|
|
56289
|
+
if (!isTableHeader && doesTableRowHaveContent) for (let col = left; col <= right; col++) highlightedPositions.push({
|
|
56290
|
+
sheetId,
|
|
56291
|
+
col,
|
|
56292
|
+
row
|
|
56293
|
+
});
|
|
56294
|
+
return highlightedPositions;
|
|
56295
|
+
}
|
|
56296
|
+
};
|
|
56297
|
+
|
|
54731
56298
|
//#endregion
|
|
54732
56299
|
//#region src/helpers/figures/charts/runtime/helpers_index.ts
|
|
54733
56300
|
var helpers_index_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -67830,6 +69397,12 @@ set(value) {
|
|
|
67830
69397
|
const figures = cmd.chartFigureIds.map((id) => this.getters.getFigure(cmd.sheetId, id));
|
|
67831
69398
|
const baseFigureId = this.getters.getFigure(cmd.sheetId, cmd.baseFigureId);
|
|
67832
69399
|
if (figures.some((f) => f === void 0 || f.tag !== "chart") || !baseFigureId || baseFigureId.tag !== "chart") return "FigureDoesNotExist";
|
|
69400
|
+
break;
|
|
69401
|
+
case "CREATE_CHART_AND_MERGE_INTO_CAROUSEL":
|
|
69402
|
+
const baseFigure = this.getters.getFigure(cmd.sheetId, cmd.baseFigureId);
|
|
69403
|
+
if (this.getters.getFigure(cmd.sheetId, cmd.figureId) || !baseFigure) return "InvalidFigureId";
|
|
69404
|
+
if (baseFigure.tag !== "chart") return "FigureDoesNotExist";
|
|
69405
|
+
break;
|
|
67833
69406
|
}
|
|
67834
69407
|
return "Success";
|
|
67835
69408
|
}
|
|
@@ -67865,6 +69438,29 @@ set(value) {
|
|
|
67865
69438
|
carouselFigureId,
|
|
67866
69439
|
chartFigureIds: cmd.chartFigureIds
|
|
67867
69440
|
});
|
|
69441
|
+
break;
|
|
69442
|
+
case "CREATE_CHART_AND_MERGE_INTO_CAROUSEL":
|
|
69443
|
+
const baseFigureToMerge = this.getters.getFigure(cmd.sheetId, cmd.baseFigureId);
|
|
69444
|
+
if (!baseFigureToMerge) throw new Error(`Figure ${cmd.baseFigureId} does not exists.`);
|
|
69445
|
+
this.dispatch("CREATE_CHART", {
|
|
69446
|
+
chartId: cmd.chartId,
|
|
69447
|
+
figureId: cmd.figureId,
|
|
69448
|
+
sheetId: cmd.sheetId,
|
|
69449
|
+
definition: cmd.definition,
|
|
69450
|
+
col: baseFigureToMerge.col,
|
|
69451
|
+
row: baseFigureToMerge.row,
|
|
69452
|
+
offset: baseFigureToMerge.offset,
|
|
69453
|
+
size: {
|
|
69454
|
+
width: baseFigureToMerge.width,
|
|
69455
|
+
height: baseFigureToMerge.height
|
|
69456
|
+
}
|
|
69457
|
+
});
|
|
69458
|
+
this.dispatch("MERGE_CHART_FIGURES_INTO_CAROUSEL", {
|
|
69459
|
+
sheetId: cmd.sheetId,
|
|
69460
|
+
baseFigureId: cmd.baseFigureId,
|
|
69461
|
+
chartFigureIds: [cmd.baseFigureId, cmd.figureId]
|
|
69462
|
+
});
|
|
69463
|
+
break;
|
|
67868
69464
|
}
|
|
67869
69465
|
}
|
|
67870
69466
|
getFigureUI(sheetId, figure) {
|
|
@@ -69554,6 +71150,9 @@ set(value) {
|
|
|
69554
71150
|
}).addChild("split_to_columns", ["data"], {
|
|
69555
71151
|
...splitToColumns,
|
|
69556
71152
|
sequence: 20
|
|
71153
|
+
}).addChild("data_analysis", ["data"], {
|
|
71154
|
+
...dataAnalysis,
|
|
71155
|
+
sequence: 24
|
|
69557
71156
|
}).addChild("column_statistics", ["data"], {
|
|
69558
71157
|
...columnStatistics,
|
|
69559
71158
|
sequence: 25
|
|
@@ -69955,7 +71554,7 @@ set(value) {
|
|
|
69955
71554
|
onAnimationEnd: types.function(),
|
|
69956
71555
|
style: types.string()
|
|
69957
71556
|
});
|
|
69958
|
-
rippleRef =
|
|
71557
|
+
rippleRef = _odoo_owl.signal.ref();
|
|
69959
71558
|
setup() {
|
|
69960
71559
|
let animation = void 0;
|
|
69961
71560
|
(0, _odoo_owl.onMounted)(() => {
|
|
@@ -70009,7 +71608,7 @@ set(value) {
|
|
|
70009
71608
|
onAnimationEnd: types.function().optional(() => () => {}),
|
|
70010
71609
|
class: types.string().optional("")
|
|
70011
71610
|
});
|
|
70012
|
-
childContainerRef =
|
|
71611
|
+
childContainerRef = _odoo_owl.signal.ref();
|
|
70013
71612
|
state = (0, _odoo_owl.proxy)({ ripples: [] });
|
|
70014
71613
|
currentId = 1;
|
|
70015
71614
|
onClick(ev) {
|
|
@@ -70280,9 +71879,9 @@ set(value) {
|
|
|
70280
71879
|
isEditing: false,
|
|
70281
71880
|
openedPicker: void 0
|
|
70282
71881
|
});
|
|
70283
|
-
sheetDivRef =
|
|
70284
|
-
iconRef =
|
|
70285
|
-
sheetNameRef =
|
|
71882
|
+
sheetDivRef = _odoo_owl.signal.ref();
|
|
71883
|
+
iconRef = _odoo_owl.signal.ref();
|
|
71884
|
+
sheetNameRef = _odoo_owl.signal.ref();
|
|
70286
71885
|
editionState = "initializing";
|
|
70287
71886
|
DOMFocusableElementStore;
|
|
70288
71887
|
setup() {
|
|
@@ -70566,8 +72165,10 @@ set(value) {
|
|
|
70566
72165
|
});
|
|
70567
72166
|
state = (0, _odoo_owl.proxy)({ selectedStatisticFn: "" });
|
|
70568
72167
|
store;
|
|
72168
|
+
sidePanelStore;
|
|
70569
72169
|
setup() {
|
|
70570
72170
|
this.store = useStore(AggregateStatisticsStore);
|
|
72171
|
+
this.sidePanelStore = useStore(SidePanelStore);
|
|
70571
72172
|
(0, _odoo_owl.onWillUpdateProps)(() => {
|
|
70572
72173
|
if (Object.values(this.store.statisticFnResults).every((result) => result?.value === void 0)) this.props.closeContextMenu();
|
|
70573
72174
|
});
|
|
@@ -70607,6 +72208,12 @@ set(value) {
|
|
|
70607
72208
|
format: fnValue.format()
|
|
70608
72209
|
});
|
|
70609
72210
|
}
|
|
72211
|
+
get isDataAnalysisOpen() {
|
|
72212
|
+
return this.sidePanelStore.mainPanel?.componentTag === "DataAnalysisPanel" && this.sidePanelStore.isMainPanelOpen || this.sidePanelStore.secondaryPanel?.componentTag === "DataAnalysisPanel" && this.sidePanelStore.isSecondaryPanelOpen;
|
|
72213
|
+
}
|
|
72214
|
+
toggleDataAnalysisPanel() {
|
|
72215
|
+
this.sidePanelStore.togglePinnedSidePanel("DataAnalysisPanel");
|
|
72216
|
+
}
|
|
70610
72217
|
};
|
|
70611
72218
|
|
|
70612
72219
|
//#endregion
|
|
@@ -70621,8 +72228,8 @@ set(value) {
|
|
|
70621
72228
|
BottomBarSheet,
|
|
70622
72229
|
BottomBarStatistic
|
|
70623
72230
|
};
|
|
70624
|
-
bottomBarRef =
|
|
70625
|
-
sheetListRef =
|
|
72231
|
+
bottomBarRef = _odoo_owl.signal.ref();
|
|
72232
|
+
sheetListRef = _odoo_owl.signal.ref();
|
|
70626
72233
|
dragAndDrop = useDragAndDropListItems();
|
|
70627
72234
|
targetScroll = void 0;
|
|
70628
72235
|
state = (0, _odoo_owl.proxy)({
|
|
@@ -70835,12 +72442,13 @@ set(value) {
|
|
|
70835
72442
|
hoveredCell;
|
|
70836
72443
|
viewStore;
|
|
70837
72444
|
zoomStore;
|
|
70838
|
-
gridRef =
|
|
70839
|
-
canvasRef =
|
|
72445
|
+
gridRef = _odoo_owl.signal.ref();
|
|
72446
|
+
canvasRef = _odoo_owl.signal.ref(HTMLCanvasElement);
|
|
70840
72447
|
setup() {
|
|
70841
72448
|
this.hoveredCell = useStore(DelayedHoveredCellStore);
|
|
70842
72449
|
this.viewStore = useStore(ViewportsStore);
|
|
70843
72450
|
this.zoomStore = useStore(ZoomStore);
|
|
72451
|
+
useLocalStore(HoveredTableStore);
|
|
70844
72452
|
const rendererStore = useLocalStore(RendererStore, OrderedLayers().filter((layer) => layer !== "Headers"));
|
|
70845
72453
|
useChildSubEnv({ getPopoverContainerRect: () => this.zoomStore.getZoomedRect(this.getGridRect()) });
|
|
70846
72454
|
useGridDrawing({
|
|
@@ -71258,8 +72866,8 @@ set(value) {
|
|
|
71258
72866
|
static components = { Menu };
|
|
71259
72867
|
props = (0, _odoo_owl.useProps)({ onClose: types.function() });
|
|
71260
72868
|
rootItems = topbarMenuRegistry.getMenuItems();
|
|
71261
|
-
menuRef =
|
|
71262
|
-
containerRef =
|
|
72869
|
+
menuRef = _odoo_owl.signal.ref();
|
|
72870
|
+
containerRef = _odoo_owl.signal.ref();
|
|
71263
72871
|
state = (0, _odoo_owl.proxy)({
|
|
71264
72872
|
menuItems: this.rootItems,
|
|
71265
72873
|
title: _t("Menu Bar"),
|
|
@@ -71332,7 +72940,7 @@ set(value) {
|
|
|
71332
72940
|
composerStore;
|
|
71333
72941
|
viewStore;
|
|
71334
72942
|
composerInterface;
|
|
71335
|
-
composerRef =
|
|
72943
|
+
composerRef = _odoo_owl.signal.ref();
|
|
71336
72944
|
menuState = (0, _odoo_owl.proxy)({ isOpen: false });
|
|
71337
72945
|
setup() {
|
|
71338
72946
|
this.composerFocusStore = useStore(ComposerFocusStore);
|
|
@@ -71411,6 +73019,49 @@ set(value) {
|
|
|
71411
73019
|
}
|
|
71412
73020
|
};
|
|
71413
73021
|
|
|
73022
|
+
//#endregion
|
|
73023
|
+
//#region src/components/standalone_grid_canvas/carousel_data_view_print.ts
|
|
73024
|
+
var CarouselDataViewPrint = class extends Component {
|
|
73025
|
+
static template = _odoo_owl.xml``;
|
|
73026
|
+
props = (0, _odoo_owl.useProps)({
|
|
73027
|
+
range: types.Range(),
|
|
73028
|
+
columnWeights: types.array().optional(),
|
|
73029
|
+
rect: types.object(),
|
|
73030
|
+
rendererStore: types.object()
|
|
73031
|
+
});
|
|
73032
|
+
viewportsStore;
|
|
73033
|
+
gridRender;
|
|
73034
|
+
setup() {
|
|
73035
|
+
useChildStoreProvider([ViewportsStore]);
|
|
73036
|
+
const store = useLocalStore(StandaloneViewportStore, this.props.range, this.props.columnWeights);
|
|
73037
|
+
this.gridRender = useLocalStore(GridRenderer, null);
|
|
73038
|
+
this.viewportsStore = useStore(ViewportsStore);
|
|
73039
|
+
(0, _odoo_owl.useEffect)(() => {
|
|
73040
|
+
store.setContainerSize(this.props.rect.width, this.props.rect.height);
|
|
73041
|
+
});
|
|
73042
|
+
(0, _odoo_owl.onMounted)(() => this.props.rendererStore.register(this));
|
|
73043
|
+
(0, _odoo_owl.onWillUnmount)(() => this.props.rendererStore.unRegister(this));
|
|
73044
|
+
}
|
|
73045
|
+
get renderingLayers() {
|
|
73046
|
+
return ["Chart"];
|
|
73047
|
+
}
|
|
73048
|
+
drawLayer(renderingContext) {
|
|
73049
|
+
const { ctx } = renderingContext;
|
|
73050
|
+
ctx.save();
|
|
73051
|
+
ctx.translate(this.props.rect.x, this.props.rect.y);
|
|
73052
|
+
ctx.beginPath();
|
|
73053
|
+
ctx.rect(0, 0, this.props.rect.width, this.props.rect.height);
|
|
73054
|
+
ctx.clip();
|
|
73055
|
+
const subRenderingCtx = {
|
|
73056
|
+
...renderingContext,
|
|
73057
|
+
sheetId: this.viewportsStore.displayedSheetId,
|
|
73058
|
+
viewports: this.viewportsStore.viewports
|
|
73059
|
+
};
|
|
73060
|
+
this.gridRender.drawLayer(subRenderingCtx, "Background", void 0);
|
|
73061
|
+
ctx.restore();
|
|
73062
|
+
}
|
|
73063
|
+
};
|
|
73064
|
+
|
|
71414
73065
|
//#endregion
|
|
71415
73066
|
//#region src/components/standalone_grid_canvas/figure_renderer_store.ts
|
|
71416
73067
|
/** Store that draws the figures directly onto the canvas */
|
|
@@ -71436,29 +73087,25 @@ set(value) {
|
|
|
71436
73087
|
drawLayer(renderingCtx) {
|
|
71437
73088
|
const { viewports, ctx } = renderingCtx;
|
|
71438
73089
|
const visibleFigures = viewports.getVisibleFigures(renderingCtx.sheetId);
|
|
71439
|
-
const
|
|
73090
|
+
const scrollOffset = viewports.getViewportOffset(renderingCtx.sheetId);
|
|
71440
73091
|
for (const figure of visibleFigures) {
|
|
71441
|
-
const
|
|
71442
|
-
|
|
73092
|
+
const figureRect = {
|
|
73093
|
+
...figure,
|
|
73094
|
+
x: figure.x - scrollOffset.x,
|
|
73095
|
+
y: figure.y - scrollOffset.y
|
|
73096
|
+
};
|
|
73097
|
+
const { x, y, width, height } = figureRect;
|
|
71443
73098
|
if (figure.tag === "chart") {
|
|
71444
73099
|
const chartId = this.getters.getChartIdFromFigureId(figure.id);
|
|
71445
|
-
if (chartId)
|
|
71446
|
-
const chartRect = {
|
|
71447
|
-
x,
|
|
71448
|
-
y,
|
|
71449
|
-
width: figure.width,
|
|
71450
|
-
height: figure.height
|
|
71451
|
-
};
|
|
71452
|
-
this.drawChart(renderingCtx, chartId, chartRect);
|
|
71453
|
-
}
|
|
73100
|
+
if (chartId) this.drawChart(renderingCtx, chartId, figureRect);
|
|
71454
73101
|
} else if (figure.tag === "image") {
|
|
71455
73102
|
const loadedImage = this.loadedImages[this.getters.getImagePath(figure.id)];
|
|
71456
|
-
if (loadedImage) ctx.drawImage(loadedImage, x, y,
|
|
71457
|
-
} else if (figure.tag === "carousel") this.drawCarousel(renderingCtx, figure);
|
|
73103
|
+
if (loadedImage) ctx.drawImage(loadedImage, x, y, width, height);
|
|
73104
|
+
} else if (figure.tag === "carousel") this.drawCarousel(renderingCtx, figure, figureRect);
|
|
71458
73105
|
if (!this.getters.isDashboard()) {
|
|
71459
73106
|
ctx.strokeStyle = GRAY_400;
|
|
71460
73107
|
ctx.lineWidth = 1;
|
|
71461
|
-
ctx.strokeRect(x, y,
|
|
73108
|
+
ctx.strokeRect(x, y, width, height);
|
|
71462
73109
|
}
|
|
71463
73110
|
}
|
|
71464
73111
|
}
|
|
@@ -71476,53 +73123,37 @@ set(value) {
|
|
|
71476
73123
|
renderingCtx.ctx.drawImage(chartCanvas, x, y, width, height);
|
|
71477
73124
|
cleanUp();
|
|
71478
73125
|
}
|
|
71479
|
-
drawCarousel(renderingCtx, figure) {
|
|
71480
|
-
const {
|
|
71481
|
-
const { x: offsetX, y: offsetY } = sheetView.getViewportOffset(renderingCtx.sheetId);
|
|
71482
|
-
const x = figure.x - offsetX;
|
|
71483
|
-
const y = figure.y - offsetY;
|
|
73126
|
+
drawCarousel(renderingCtx, figure, figureRect) {
|
|
73127
|
+
const { ctx } = renderingCtx;
|
|
71484
73128
|
const carousel = this.getters.getCarousel(figure.id);
|
|
71485
73129
|
const chartId = this.getters.getChartIdFromFigureId(figure.id);
|
|
71486
73130
|
if (!carousel) return;
|
|
73131
|
+
const layout = getCarouselLayout(figureRect, carousel, this.getters.getSelectedCarouselItem(figure.id));
|
|
71487
73132
|
const chartDefinition = chartId ? this.getters.getChartDefinition(chartId) : void 0;
|
|
73133
|
+
ctx.save();
|
|
73134
|
+
ctx.beginPath();
|
|
73135
|
+
ctx.rect(figureRect.x, figureRect.y, figureRect.width, figureRect.height);
|
|
73136
|
+
ctx.clip();
|
|
73137
|
+
ctx.fillStyle = chartDefinition?.background || this.getters.getSpreadsheetTheme().backgroundColor;
|
|
73138
|
+
ctx.fillRect(figureRect.x, figureRect.y, figureRect.width, figureRect.height);
|
|
71488
73139
|
const title = {
|
|
71489
73140
|
...DEFAULT_CAROUSEL_TITLE_STYLE,
|
|
71490
73141
|
...carousel.title
|
|
71491
73142
|
};
|
|
71492
|
-
|
|
71493
|
-
|
|
71494
|
-
if (!title.text && chartId) {
|
|
71495
|
-
const chartRect = {
|
|
71496
|
-
x,
|
|
71497
|
-
y,
|
|
71498
|
-
width: figure.width,
|
|
71499
|
-
height: figure.height
|
|
71500
|
-
};
|
|
71501
|
-
this.drawChart(renderingCtx, chartId, chartRect);
|
|
71502
|
-
} else if (title.text) {
|
|
71503
|
-
ctx.save();
|
|
71504
|
-
ctx.fillStyle = chartDefinition?.background || this.getters.getSpreadsheetTheme().backgroundColor;
|
|
71505
|
-
ctx.fillRect(x, y, figure.width, headerSize);
|
|
71506
|
-
ctx.font = computeTextFont(chartStyleToCellStyle(title));
|
|
73143
|
+
if (title.text) {
|
|
73144
|
+
ctx.font = computeTextFont(chartStyleToCellStyle(title), "px", 500);
|
|
71507
73145
|
ctx.fillStyle = title.color;
|
|
71508
|
-
ctx.
|
|
71509
|
-
|
|
71510
|
-
|
|
71511
|
-
|
|
71512
|
-
|
|
71513
|
-
|
|
71514
|
-
|
|
71515
|
-
|
|
71516
|
-
if (chartId) this.drawChart(renderingCtx, chartId, chartRect);
|
|
71517
|
-
else if (!this.getters.isDashboard()) {
|
|
71518
|
-
ctx.strokeStyle = GRAY_400;
|
|
71519
|
-
ctx.lineWidth = 1;
|
|
71520
|
-
ctx.beginPath();
|
|
71521
|
-
ctx.moveTo(x, y + headerSize);
|
|
71522
|
-
ctx.lineTo(x + figure.width, y + headerSize);
|
|
71523
|
-
ctx.stroke();
|
|
71524
|
-
}
|
|
73146
|
+
ctx.textBaseline = "middle";
|
|
73147
|
+
const textY = Math.ceil(layout.headerRect.y + layout.headerRect.height / 2);
|
|
73148
|
+
ctx.fillText(title.text, layout.headerRect.x, textY);
|
|
73149
|
+
}
|
|
73150
|
+
const separator = layout.separatorRect;
|
|
73151
|
+
if (separator) {
|
|
73152
|
+
ctx.fillStyle = GRAY_400;
|
|
73153
|
+
ctx.fillRect(separator.x, separator.y, separator.width, separator.height);
|
|
71525
73154
|
}
|
|
73155
|
+
ctx.restore();
|
|
73156
|
+
if (chartId) this.drawChart(renderingCtx, chartId, layout.contentRect);
|
|
71526
73157
|
}
|
|
71527
73158
|
};
|
|
71528
73159
|
|
|
@@ -71530,12 +73161,13 @@ set(value) {
|
|
|
71530
73161
|
//#region src/components/standalone_grid_canvas/standalone_grid_canvas.ts
|
|
71531
73162
|
var StandaloneGridCanvas = class extends Component {
|
|
71532
73163
|
static template = "o-spreadsheet-StandaloneGridCanvas";
|
|
73164
|
+
static components = { CarouselDataViewPrint };
|
|
71533
73165
|
props = (0, _odoo_owl.useProps)({
|
|
71534
73166
|
sheetId: types.UID(),
|
|
71535
73167
|
zone: types.Zone(),
|
|
71536
73168
|
renderingCtx: types.object()
|
|
71537
73169
|
});
|
|
71538
|
-
canvasRef =
|
|
73170
|
+
canvasRef = _odoo_owl.signal.ref(HTMLCanvasElement);
|
|
71539
73171
|
rendererStore;
|
|
71540
73172
|
figureRendererStore;
|
|
71541
73173
|
setup() {
|
|
@@ -71557,6 +73189,28 @@ set(value) {
|
|
|
71557
73189
|
this.figureRendererStore.addLoadedImage(path, await createImageBitmap(blob));
|
|
71558
73190
|
}));
|
|
71559
73191
|
}
|
|
73192
|
+
get carouselDataViews() {
|
|
73193
|
+
const { sheetId, viewports } = this.props.renderingCtx;
|
|
73194
|
+
return viewports.getVisibleFigures(sheetId).filter((figure) => figure.tag === "carousel").map((figure) => {
|
|
73195
|
+
const carouselItem = this.env.model.getters.getSelectedCarouselItem(figure.id);
|
|
73196
|
+
if (carouselItem?.type !== "carouselDataView" || !carouselItem.range) return;
|
|
73197
|
+
const carousel = this.env.model.getters.getCarousel(figure.id);
|
|
73198
|
+
const scroll = viewports.getViewportOffset(sheetId);
|
|
73199
|
+
const { x, y, width, height } = figure;
|
|
73200
|
+
const layout = getCarouselLayout({
|
|
73201
|
+
x: x - scroll.x,
|
|
73202
|
+
y: y - scroll.y,
|
|
73203
|
+
width,
|
|
73204
|
+
height
|
|
73205
|
+
}, carousel, carouselItem);
|
|
73206
|
+
return {
|
|
73207
|
+
range: carouselItem.range,
|
|
73208
|
+
columnWeights: carouselItem.columnWeights,
|
|
73209
|
+
rect: layout.contentRect,
|
|
73210
|
+
rendererStore: this.rendererStore
|
|
73211
|
+
};
|
|
73212
|
+
}).filter(isDefined);
|
|
73213
|
+
}
|
|
71560
73214
|
};
|
|
71561
73215
|
|
|
71562
73216
|
//#endregion
|
|
@@ -72018,7 +73672,7 @@ set(value) {
|
|
|
72018
73672
|
anchorRect: null,
|
|
72019
73673
|
menuItems: []
|
|
72020
73674
|
});
|
|
72021
|
-
namedRangeSelectorRef =
|
|
73675
|
+
namedRangeSelectorRef = _odoo_owl.signal.ref();
|
|
72022
73676
|
setup() {
|
|
72023
73677
|
this.topBarToolStore = useToolBarDropdownStore();
|
|
72024
73678
|
this.DOMFocusableElementStore = useStore(DOMFocusableElementStore);
|
|
@@ -72216,7 +73870,7 @@ set(value) {
|
|
|
72216
73870
|
anchorRect: types.Rect()
|
|
72217
73871
|
});
|
|
72218
73872
|
BORDER_POSITIONS = BORDER_POSITIONS;
|
|
72219
|
-
lineStyleButtonRef =
|
|
73873
|
+
lineStyleButtonRef = _odoo_owl.signal.ref();
|
|
72220
73874
|
borderStyles = borderStyles;
|
|
72221
73875
|
state = (0, _odoo_owl.proxy)({ activeTool: void 0 });
|
|
72222
73876
|
toggleDropdownTool(tool) {
|
|
@@ -72283,7 +73937,7 @@ set(value) {
|
|
|
72283
73937
|
});
|
|
72284
73938
|
topBarToolStore;
|
|
72285
73939
|
viewStore;
|
|
72286
|
-
borderEditorButtonRef =
|
|
73940
|
+
borderEditorButtonRef = _odoo_owl.signal.ref();
|
|
72287
73941
|
state = (0, _odoo_owl.proxy)({
|
|
72288
73942
|
currentColor: DEFAULT_BORDER_DESC.color,
|
|
72289
73943
|
currentStyle: DEFAULT_BORDER_DESC.style,
|
|
@@ -72513,7 +74167,7 @@ set(value) {
|
|
|
72513
74167
|
childClass: types.string()
|
|
72514
74168
|
});
|
|
72515
74169
|
topBarToolStore;
|
|
72516
|
-
actionRef =
|
|
74170
|
+
actionRef = _odoo_owl.signal.ref();
|
|
72517
74171
|
setup() {
|
|
72518
74172
|
this.topBarToolStore = useToolBarDropdownStore();
|
|
72519
74173
|
}
|
|
@@ -72576,7 +74230,7 @@ set(value) {
|
|
|
72576
74230
|
props = (0, _odoo_owl.useProps)({ class: types.string() });
|
|
72577
74231
|
formatNumberMenuItemSpec = formatNumberMenuItemSpec;
|
|
72578
74232
|
topBarToolStore;
|
|
72579
|
-
buttonRef =
|
|
74233
|
+
buttonRef = _odoo_owl.signal.ref();
|
|
72580
74234
|
state = (0, _odoo_owl.proxy)({
|
|
72581
74235
|
anchorRect: {
|
|
72582
74236
|
x: 0,
|
|
@@ -72865,12 +74519,12 @@ set(value) {
|
|
|
72865
74519
|
composerFocusStore;
|
|
72866
74520
|
fingerprints;
|
|
72867
74521
|
topBarToolStore;
|
|
72868
|
-
toolBarContainerRef =
|
|
72869
|
-
toolbarRef =
|
|
72870
|
-
namedRangesRef =
|
|
72871
|
-
topBarTopRef =
|
|
72872
|
-
moreToolsContainerRef =
|
|
72873
|
-
moreToolsButtonRef =
|
|
74522
|
+
toolBarContainerRef = _odoo_owl.signal.ref();
|
|
74523
|
+
toolbarRef = _odoo_owl.signal.ref();
|
|
74524
|
+
namedRangesRef = _odoo_owl.signal.ref();
|
|
74525
|
+
topBarTopRef = _odoo_owl.signal.ref();
|
|
74526
|
+
moreToolsContainerRef = _odoo_owl.signal.ref();
|
|
74527
|
+
moreToolsButtonRef = _odoo_owl.signal.ref();
|
|
72874
74528
|
spreadsheetRect = useSpreadsheetRect();
|
|
72875
74529
|
setup() {
|
|
72876
74530
|
this.composerFocusStore = useStore(ComposerFocusStore);
|
|
@@ -73132,7 +74786,7 @@ set(value) {
|
|
|
73132
74786
|
SpreadsheetPrint
|
|
73133
74787
|
};
|
|
73134
74788
|
sidePanel;
|
|
73135
|
-
spreadsheetRef =
|
|
74789
|
+
spreadsheetRef = _odoo_owl.signal.ref();
|
|
73136
74790
|
spreadsheetRect = useSpreadsheetRect();
|
|
73137
74791
|
state = (0, _odoo_owl.proxy)({ printModeEnabled: false });
|
|
73138
74792
|
_focusGrid;
|
|
@@ -79140,12 +80794,13 @@ set(value) {
|
|
|
79140
80794
|
}
|
|
79141
80795
|
sortFilterZone(sortDirection) {
|
|
79142
80796
|
const filterPosition = this.props.filterPosition;
|
|
79143
|
-
const
|
|
80797
|
+
const table = this.table;
|
|
80798
|
+
const tableZone = table?.range.zone;
|
|
79144
80799
|
if (!filterPosition || !tableZone || tableZone.top === tableZone.bottom) return;
|
|
79145
80800
|
const sheetId = this.viewStore.displayedSheetId;
|
|
79146
80801
|
const contentZone = {
|
|
79147
80802
|
...tableZone,
|
|
79148
|
-
top: tableZone.top +
|
|
80803
|
+
top: tableZone.top + table.config.numberOfHeaders
|
|
79149
80804
|
};
|
|
79150
80805
|
const sortAnchor = {
|
|
79151
80806
|
col: filterPosition.col,
|
|
@@ -79303,9 +80958,9 @@ set(value) {
|
|
|
79303
80958
|
onClosed: types.function().optional()
|
|
79304
80959
|
});
|
|
79305
80960
|
static size = { maxHeight: 500 };
|
|
79306
|
-
urlInput =
|
|
79307
|
-
suggestionListRef =
|
|
79308
|
-
urlInputContainer =
|
|
80961
|
+
urlInput = _odoo_owl.signal.ref(HTMLInputElement);
|
|
80962
|
+
suggestionListRef = _odoo_owl.signal.ref();
|
|
80963
|
+
urlInputContainer = _odoo_owl.signal.ref();
|
|
79309
80964
|
state;
|
|
79310
80965
|
viewStore;
|
|
79311
80966
|
setup() {
|
|
@@ -81423,7 +83078,7 @@ set(value) {
|
|
|
81423
83078
|
else if (_format === 0) {
|
|
81424
83079
|
const rowSeparator = this.locale.decimalSeparator === "," ? "/" : ",";
|
|
81425
83080
|
return { value: transposeMatrix(_array).flatMap((row) => row.map((value) => {
|
|
81426
|
-
return isEvaluationError(value.value) ? value.value :
|
|
83081
|
+
return isEvaluationError(value.value) ? value.value : toLocaleString(value, this.locale);
|
|
81427
83082
|
})).join(rowSeparator) };
|
|
81428
83083
|
} else return new EvaluationError(_t("Format must be 0 or 1"));
|
|
81429
83084
|
},
|
|
@@ -87471,7 +89126,7 @@ set(value) {
|
|
|
87471
89126
|
description: _t("Concatenation of two values."),
|
|
87472
89127
|
args: [arg("value1 (string)", _t("The value to which value2 will be appended.")), arg("value2 (string)", _t("The value to append to value1."))],
|
|
87473
89128
|
compute: function(value1, value2) {
|
|
87474
|
-
return { value:
|
|
89129
|
+
return { value: toLocaleString(value1, this.locale) + toLocaleString(value2, this.locale) };
|
|
87475
89130
|
},
|
|
87476
89131
|
isExported: true
|
|
87477
89132
|
};
|
|
@@ -88354,7 +90009,7 @@ set(value) {
|
|
|
88354
90009
|
description: _t("Remove non-printable characters from a piece of text."),
|
|
88355
90010
|
args: [arg("text (string)", _t("The text whose non-printable characters are to be removed."))],
|
|
88356
90011
|
compute: function(text) {
|
|
88357
|
-
const _text =
|
|
90012
|
+
const _text = toLocaleString(text, this.locale);
|
|
88358
90013
|
let cleanedStr = "";
|
|
88359
90014
|
for (const char of _text) if (char && char.charCodeAt(0) > 31) cleanedStr += char;
|
|
88360
90015
|
return { value: cleanedStr };
|
|
@@ -88365,7 +90020,7 @@ set(value) {
|
|
|
88365
90020
|
description: _t("Appends strings to one another."),
|
|
88366
90021
|
args: [arg("string (string, range<string>, repeating)", _t("String to append in sequence."))],
|
|
88367
90022
|
compute: function(...datas) {
|
|
88368
|
-
return { value: reduceAny(datas, (acc, a) => acc +
|
|
90023
|
+
return { value: reduceAny(datas, (acc, a) => acc + toLocaleString(a, this.locale), "") };
|
|
88369
90024
|
},
|
|
88370
90025
|
isExported: true
|
|
88371
90026
|
};
|
|
@@ -88373,7 +90028,7 @@ set(value) {
|
|
|
88373
90028
|
description: _t("Tests whether two strings are identical."),
|
|
88374
90029
|
args: [arg("string1 (string)", _t("The first string to compare.")), arg("string2 (string)", _t("The second string to compare."))],
|
|
88375
90030
|
compute: function(string1, string2) {
|
|
88376
|
-
return { value:
|
|
90031
|
+
return { value: toLocaleString(string1, this.locale) === toLocaleString(string2, this.locale) };
|
|
88377
90032
|
},
|
|
88378
90033
|
isExported: true
|
|
88379
90034
|
};
|
|
@@ -88385,8 +90040,8 @@ set(value) {
|
|
|
88385
90040
|
arg(`starting_at (number, default=${DEFAULT_STARTING_AT})`, _t("The character within text_to_search at which to start the search."))
|
|
88386
90041
|
],
|
|
88387
90042
|
compute: function(searchFor, textToSearch, startingAt = { value: DEFAULT_STARTING_AT }) {
|
|
88388
|
-
const _searchFor =
|
|
88389
|
-
const _textToSearch =
|
|
90043
|
+
const _searchFor = toLocaleString(searchFor, this.locale);
|
|
90044
|
+
const _textToSearch = toLocaleString(textToSearch, this.locale);
|
|
88390
90045
|
const _startingAt = toNumber(startingAt, this.locale);
|
|
88391
90046
|
if (_textToSearch === "") return new EvaluationError(_t("The text_to_search must be non-empty."));
|
|
88392
90047
|
if (_startingAt < 1) return new EvaluationError(_t("The starting_at (%s) must be greater than or equal to 1.", _startingAt));
|
|
@@ -88423,8 +90078,8 @@ set(value) {
|
|
|
88423
90078
|
description: _t("Concatenates elements of arrays with delimiter."),
|
|
88424
90079
|
args: [arg("delimiter (string)", _t("The character or string to place between each concatenated value.")), arg("value_or_array (string, range<string>, repeating)", _t("Value to be appended using delimiter."))],
|
|
88425
90080
|
compute: function(delimiter, ...valuesOrArrays) {
|
|
88426
|
-
const _delimiter =
|
|
88427
|
-
return { value: reduceAny(valuesOrArrays, (acc, a) => (acc ? acc + _delimiter : "") +
|
|
90081
|
+
const _delimiter = toLocaleString(delimiter, this.locale);
|
|
90082
|
+
return { value: reduceAny(valuesOrArrays, (acc, a) => (acc ? acc + _delimiter : "") + toLocaleString(a, this.locale), "") };
|
|
88428
90083
|
}
|
|
88429
90084
|
};
|
|
88430
90085
|
const LEFT = {
|
|
@@ -88433,7 +90088,7 @@ set(value) {
|
|
|
88433
90088
|
compute: function(text, ...args) {
|
|
88434
90089
|
const _numberOfCharacters = args.length ? toNumber(args[0], this.locale) : 1;
|
|
88435
90090
|
if (_numberOfCharacters < 0) return new EvaluationError(_t("The number_of_characters (%s) must be positive or null.", _numberOfCharacters));
|
|
88436
|
-
return { value:
|
|
90091
|
+
return { value: toLocaleString(text, this.locale).substring(0, _numberOfCharacters) };
|
|
88437
90092
|
},
|
|
88438
90093
|
isExported: true
|
|
88439
90094
|
};
|
|
@@ -88441,7 +90096,7 @@ set(value) {
|
|
|
88441
90096
|
description: _t("Length of a string."),
|
|
88442
90097
|
args: [arg("text (string)", _t("The string whose length will be returned."))],
|
|
88443
90098
|
compute: function(text) {
|
|
88444
|
-
return { value:
|
|
90099
|
+
return { value: toLocaleString(text, this.locale).length };
|
|
88445
90100
|
},
|
|
88446
90101
|
isExported: true
|
|
88447
90102
|
};
|
|
@@ -88449,7 +90104,7 @@ set(value) {
|
|
|
88449
90104
|
description: _t("Converts a specified string to lowercase."),
|
|
88450
90105
|
args: [arg("text (string)", _t("The string to convert to lowercase."))],
|
|
88451
90106
|
compute: function(text) {
|
|
88452
|
-
return { value:
|
|
90107
|
+
return { value: toLocaleString(text, this.locale).toLowerCase() };
|
|
88453
90108
|
},
|
|
88454
90109
|
isExported: true
|
|
88455
90110
|
};
|
|
@@ -88461,7 +90116,7 @@ set(value) {
|
|
|
88461
90116
|
arg("extract_length (number)", _t("The length of the segment to extract."))
|
|
88462
90117
|
],
|
|
88463
90118
|
compute: function(text, starting_at, extract_length) {
|
|
88464
|
-
const _text =
|
|
90119
|
+
const _text = toLocaleString(text, this.locale);
|
|
88465
90120
|
const _starting_at = toNumber(starting_at, this.locale);
|
|
88466
90121
|
const _extract_length = toNumber(extract_length, this.locale);
|
|
88467
90122
|
if (_starting_at < 1) return new EvaluationError(_t("The starting_at argument (%s) must be positive greater than one.", _starting_at.toString()));
|
|
@@ -88474,7 +90129,7 @@ set(value) {
|
|
|
88474
90129
|
description: _t("Capitalizes each word in a specified string."),
|
|
88475
90130
|
args: [arg("text_to_capitalize (string)", _t("The text which will be returned with the first letter of each word in uppercase and all other letters in lowercase."))],
|
|
88476
90131
|
compute: function(text) {
|
|
88477
|
-
return { value:
|
|
90132
|
+
return { value: toLocaleString(text, this.locale).replace(wordRegex, (word) => {
|
|
88478
90133
|
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
|
|
88479
90134
|
}) };
|
|
88480
90135
|
},
|
|
@@ -88624,8 +90279,8 @@ set(value) {
|
|
|
88624
90279
|
if (_position < 1) return new EvaluationError(_t("The position (%s) must be greater than or equal to 1.", _position));
|
|
88625
90280
|
const _length = toNumber(length, this.locale);
|
|
88626
90281
|
if (_length < 0) return new EvaluationError(_t("The length (%s) must be positive or zero.", _length));
|
|
88627
|
-
const _text =
|
|
88628
|
-
const _newText =
|
|
90282
|
+
const _text = toLocaleString(text, this.locale);
|
|
90283
|
+
const _newText = toLocaleString(newText, this.locale);
|
|
88629
90284
|
return { value: _text.substring(0, _position - 1) + _newText + _text.substring(_position - 1 + _length) };
|
|
88630
90285
|
},
|
|
88631
90286
|
isExported: true
|
|
@@ -88636,7 +90291,7 @@ set(value) {
|
|
|
88636
90291
|
compute: function(text, ...args) {
|
|
88637
90292
|
const _numberOfCharacters = args.length ? toNumber(args[0], this.locale) : 1;
|
|
88638
90293
|
if (_numberOfCharacters < 0) return new EvaluationError(_t("The number_of_characters (%s) must be positive or zero.", _numberOfCharacters));
|
|
88639
|
-
const _text =
|
|
90294
|
+
const _text = toLocaleString(text, this.locale);
|
|
88640
90295
|
const stringLength = _text.length;
|
|
88641
90296
|
return { value: _text.substring(stringLength - _numberOfCharacters, stringLength) };
|
|
88642
90297
|
},
|
|
@@ -88650,8 +90305,8 @@ set(value) {
|
|
|
88650
90305
|
arg(`starting_at (number, default=${DEFAULT_STARTING_AT})`, _t("The character within text_to_search at which to start the search."))
|
|
88651
90306
|
],
|
|
88652
90307
|
compute: function(searchFor, textToSearch, startingAt = { value: DEFAULT_STARTING_AT }) {
|
|
88653
|
-
const _searchFor =
|
|
88654
|
-
const _textToSearch =
|
|
90308
|
+
const _searchFor = toLocaleString(searchFor, this.locale).toLowerCase();
|
|
90309
|
+
const _textToSearch = toLocaleString(textToSearch, this.locale).toLowerCase();
|
|
88655
90310
|
const _startingAt = toNumber(startingAt, this.locale);
|
|
88656
90311
|
if (_textToSearch === "") return {
|
|
88657
90312
|
value: CellErrorType.GenericError,
|
|
@@ -88681,8 +90336,8 @@ set(value) {
|
|
|
88681
90336
|
arg(`remove_empty_text (boolean, default=${SPLIT_DEFAULT_REMOVE_EMPTY_TEXT})`, _t("Whether or not to remove empty text messages from the split results. The default behavior is to treat consecutive delimiters as one (if TRUE). If FALSE, empty cells values are added between consecutive delimiters."))
|
|
88682
90337
|
],
|
|
88683
90338
|
computeArray: function(text, delimiter, splitByEach = { value: SPLIT_DEFAULT_SPLIT_BY_EACH }, removeEmptyText = { value: SPLIT_DEFAULT_REMOVE_EMPTY_TEXT }) {
|
|
88684
|
-
const _text =
|
|
88685
|
-
const _delimiter = escapeRegExp(
|
|
90339
|
+
const _text = toLocaleString(text, this.locale);
|
|
90340
|
+
const _delimiter = escapeRegExp(toLocaleString(delimiter, this.locale));
|
|
88686
90341
|
const _splitByEach = toBoolean(splitByEach);
|
|
88687
90342
|
const _removeEmptyText = toBoolean(removeEmptyText);
|
|
88688
90343
|
if (_delimiter.length <= 0) return new EvaluationError(_t("The delimiter (%s) must be not be empty.", _delimiter));
|
|
@@ -88704,10 +90359,10 @@ set(value) {
|
|
|
88704
90359
|
compute: function(textToSearch, searchFor, replaceWith, occurrenceNumber) {
|
|
88705
90360
|
const _occurrenceNumber = toNumber(occurrenceNumber, this.locale);
|
|
88706
90361
|
if (_occurrenceNumber < 0) return new EvaluationError(_t("The occurrenceNumber (%s) must be positive or null.", _occurrenceNumber));
|
|
88707
|
-
const _textToSearch =
|
|
88708
|
-
const _searchFor =
|
|
90362
|
+
const _textToSearch = toLocaleString(textToSearch, this.locale);
|
|
90363
|
+
const _searchFor = toLocaleString(searchFor, this.locale);
|
|
88709
90364
|
if (_searchFor === "") return { value: _textToSearch };
|
|
88710
|
-
const _replaceWith =
|
|
90365
|
+
const _replaceWith = toLocaleString(replaceWith, this.locale);
|
|
88711
90366
|
const reg = new RegExp(escapeRegExp(_searchFor), "g");
|
|
88712
90367
|
if (_occurrenceNumber === 0) return { value: _textToSearch.replace(reg, _replaceWith) };
|
|
88713
90368
|
let n = 0;
|
|
@@ -88730,10 +90385,10 @@ set(value) {
|
|
|
88730
90385
|
arg("texts (string, range<string>, repeating)", _t("Text item to join."))
|
|
88731
90386
|
],
|
|
88732
90387
|
compute: function(delimiter, ignoreEmpty = { value: TEXTJOIN_DEFAULT_IGNORE_EMPTY }, ...textsOrArrays) {
|
|
88733
|
-
const _delimiter =
|
|
90388
|
+
const _delimiter = toLocaleString(delimiter, this.locale);
|
|
88734
90389
|
const _ignoreEmpty = toBoolean(ignoreEmpty);
|
|
88735
90390
|
let n = 0;
|
|
88736
|
-
return { value: reduceAny(textsOrArrays, (acc, a) => !(_ignoreEmpty &&
|
|
90391
|
+
return { value: reduceAny(textsOrArrays, (acc, a) => !(_ignoreEmpty && toLocaleString(a, this.locale) === "") ? (n++ ? acc + _delimiter : "") + toLocaleString(a, this.locale) : acc, "") };
|
|
88737
90392
|
},
|
|
88738
90393
|
isExported: true
|
|
88739
90394
|
};
|
|
@@ -88786,7 +90441,7 @@ set(value) {
|
|
|
88786
90441
|
description: _t("Removes space characters."),
|
|
88787
90442
|
args: [arg("text (string)", _t("The text or reference to a cell containing text to be trimmed."))],
|
|
88788
90443
|
compute: function(text) {
|
|
88789
|
-
return { value: trimContent(
|
|
90444
|
+
return { value: trimContent(toLocaleString(text, this.locale)) };
|
|
88790
90445
|
},
|
|
88791
90446
|
isExported: true
|
|
88792
90447
|
};
|
|
@@ -88794,7 +90449,7 @@ set(value) {
|
|
|
88794
90449
|
description: _t("Converts a specified string to uppercase."),
|
|
88795
90450
|
args: [arg("text (string)", _t("The string to convert to uppercase."))],
|
|
88796
90451
|
compute: function(text) {
|
|
88797
|
-
return { value:
|
|
90452
|
+
return { value: toLocaleString(text, this.locale).toUpperCase() };
|
|
88798
90453
|
},
|
|
88799
90454
|
isExported: true
|
|
88800
90455
|
};
|
|
@@ -88891,7 +90546,7 @@ set(value) {
|
|
|
88891
90546
|
args: [arg("url (string)", _t("The full URL of the link enclosed in quotation marks.")), arg("link_label (string, optional)", _t("The text to display in the cell, enclosed in quotation marks."))],
|
|
88892
90547
|
compute: function(url, linkLabel) {
|
|
88893
90548
|
const processedUrl = toString(url).trim();
|
|
88894
|
-
const processedLabel =
|
|
90549
|
+
const processedLabel = toLocaleString(linkLabel, this.locale) || processedUrl;
|
|
88895
90550
|
if (processedUrl === "") return { value: processedLabel };
|
|
88896
90551
|
return { value: markdownLink(processedLabel, processedUrl) };
|
|
88897
90552
|
},
|
|
@@ -89121,7 +90776,9 @@ set(value) {
|
|
|
89121
90776
|
computeCachedTextDimension,
|
|
89122
90777
|
createComputeFunction,
|
|
89123
90778
|
isMobileOS,
|
|
89124
|
-
drawHighlight
|
|
90779
|
+
drawHighlight,
|
|
90780
|
+
getBoundingRectWithMargins,
|
|
90781
|
+
hasInteractiveElementInEventTree
|
|
89125
90782
|
};
|
|
89126
90783
|
const links = {
|
|
89127
90784
|
isMarkdownLink,
|
|
@@ -89353,8 +91010,8 @@ exports.stores = stores;
|
|
|
89353
91010
|
exports.tokenColors = tokenColors;
|
|
89354
91011
|
exports.tokenize = tokenize;
|
|
89355
91012
|
|
|
89356
|
-
__info__.version = "19.5.0-alpha.
|
|
89357
|
-
__info__.date = "2026-08-
|
|
89358
|
-
__info__.hash = "
|
|
91013
|
+
__info__.version = "19.5.0-alpha.12";
|
|
91014
|
+
__info__.date = "2026-08-19T09:47:03.774Z";
|
|
91015
|
+
__info__.hash = "68ca68e";
|
|
89359
91016
|
|
|
89360
91017
|
})(this.o_spreadsheet = this.o_spreadsheet || {}, owl);
|