@odoo/o-spreadsheet 19.0.4 → 19.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/o-spreadsheet.cjs.js +296 -204
- package/dist/o-spreadsheet.d.ts +227 -157
- package/dist/o-spreadsheet.esm.js +296 -204
- package/dist/o-spreadsheet.iife.js +296 -204
- package/dist/o-spreadsheet.iife.min.js +278 -285
- package/dist/o_spreadsheet.xml +66 -17
- package/package.json +1 -1
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* This file is generated by o-spreadsheet build tools. Do not edit it.
|
|
4
4
|
* @see https://github.com/odoo/o-spreadsheet
|
|
5
|
-
* @version 19.0.
|
|
6
|
-
* @date 2025-
|
|
7
|
-
* @hash
|
|
5
|
+
* @version 19.0.6
|
|
6
|
+
* @date 2025-10-16T06:39:36.282Z
|
|
7
|
+
* @hash 0d4315a
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
'use strict';
|
|
@@ -1384,9 +1384,7 @@ function removeIndexesFromArray(array, indexes) {
|
|
|
1384
1384
|
return newArray;
|
|
1385
1385
|
}
|
|
1386
1386
|
function insertItemsAtIndex(array, items, index) {
|
|
1387
|
-
|
|
1388
|
-
newArray.splice(index, 0, ...items);
|
|
1389
|
-
return newArray;
|
|
1387
|
+
return array.slice(0, index).concat(items).concat(array.slice(index));
|
|
1390
1388
|
}
|
|
1391
1389
|
function replaceItemAtIndex(array, newItem, index) {
|
|
1392
1390
|
const newArray = [...array];
|
|
@@ -4517,7 +4515,17 @@ function toNumberMatrix(data, argName) {
|
|
|
4517
4515
|
return toMatrix(data).map((row) => {
|
|
4518
4516
|
return row.map((cell) => {
|
|
4519
4517
|
if (typeof cell.value !== "number") {
|
|
4520
|
-
|
|
4518
|
+
let message = "";
|
|
4519
|
+
if (typeof cell === "object") {
|
|
4520
|
+
message = _t("Function [[FUNCTION_NAME]] expects number values for %s, but got an empty value.", argName);
|
|
4521
|
+
}
|
|
4522
|
+
else if (typeof cell === "string") {
|
|
4523
|
+
message = _t("Function [[FUNCTION_NAME]] expects number values for %s, but got a string.", argName);
|
|
4524
|
+
}
|
|
4525
|
+
else if (typeof cell === "boolean") {
|
|
4526
|
+
message = _t("Function [[FUNCTION_NAME]] expects number values for %s, but got a boolean.", argName);
|
|
4527
|
+
}
|
|
4528
|
+
throw new EvaluationError(message);
|
|
4521
4529
|
}
|
|
4522
4530
|
return cell.value;
|
|
4523
4531
|
});
|
|
@@ -5636,7 +5644,7 @@ function tokensToTextInternalFormat(tokens) {
|
|
|
5636
5644
|
* Replace in place tokens "mm" and "m" that denote minutes in date format with "MM" to avoid confusion with months.
|
|
5637
5645
|
*
|
|
5638
5646
|
* As per OpenXML specification, in date formats if a date token "m" or "mm" is followed by a date token "s" or
|
|
5639
|
-
* preceded by a data token "h", then it's not a month but
|
|
5647
|
+
* preceded by a data token "h", then it's not a month but a minute.
|
|
5640
5648
|
*/
|
|
5641
5649
|
function convertTokensToMinutesInDateFormat(tokens) {
|
|
5642
5650
|
const dateParts = tokens.filter((token) => token.type === "DATE_PART");
|
|
@@ -5679,6 +5687,9 @@ function internalFormatPartToFormat(internalFormat) {
|
|
|
5679
5687
|
case "REPEATED_CHAR":
|
|
5680
5688
|
format += "*" + token.value;
|
|
5681
5689
|
break;
|
|
5690
|
+
case "DATE_PART":
|
|
5691
|
+
format += token.value === "MM" ? "mm" : token.value; // Convert "MM" back to "mm" for minutes
|
|
5692
|
+
break;
|
|
5682
5693
|
default:
|
|
5683
5694
|
format += token.value;
|
|
5684
5695
|
}
|
|
@@ -9529,7 +9540,7 @@ class CellClipboardHandler extends AbstractCellClipboardHandler {
|
|
|
9529
9540
|
pasteCell(origin, target, clipboardOption) {
|
|
9530
9541
|
const { sheetId, col, row } = target;
|
|
9531
9542
|
const targetCell = this.getters.getEvaluatedCell(target);
|
|
9532
|
-
const originFormat = origin?.format
|
|
9543
|
+
const originFormat = origin?.format || origin.evaluatedCell.format;
|
|
9533
9544
|
if (clipboardOption?.pasteOption === "asValue") {
|
|
9534
9545
|
this.dispatch("UPDATE_CELL", {
|
|
9535
9546
|
...target,
|
|
@@ -13902,7 +13913,7 @@ const GROWTH = {
|
|
|
13902
13913
|
if (knownDataY.length === 0 || knownDataY[0].length === 0) {
|
|
13903
13914
|
return new EvaluationError(emptyDataErrorMessage("known_data_y"));
|
|
13904
13915
|
}
|
|
13905
|
-
return expM(predictLinearValues(logM(toNumberMatrix(knownDataY, "
|
|
13916
|
+
return expM(predictLinearValues(logM(toNumberMatrix(knownDataY, "known_data_y")), toNumberMatrix(knownDataX, "known_data_x"), toNumberMatrix(newDataX, "new_data_y"), toBoolean(b)));
|
|
13906
13917
|
},
|
|
13907
13918
|
};
|
|
13908
13919
|
// -----------------------------------------------------------------------------
|
|
@@ -13975,7 +13986,7 @@ const LINEST = {
|
|
|
13975
13986
|
if (dataY.length === 0 || dataY[0].length === 0) {
|
|
13976
13987
|
return new EvaluationError(emptyDataErrorMessage("data_y"));
|
|
13977
13988
|
}
|
|
13978
|
-
return fullLinearRegression(toNumberMatrix(dataX, "
|
|
13989
|
+
return fullLinearRegression(toNumberMatrix(dataX, "data_x"), toNumberMatrix(dataY, "data_y"), toBoolean(calculateB), toBoolean(verbose));
|
|
13979
13990
|
},
|
|
13980
13991
|
isExported: true,
|
|
13981
13992
|
};
|
|
@@ -13994,7 +14005,7 @@ const LOGEST = {
|
|
|
13994
14005
|
if (dataY.length === 0 || dataY[0].length === 0) {
|
|
13995
14006
|
return new EvaluationError(emptyDataErrorMessage("data_y"));
|
|
13996
14007
|
}
|
|
13997
|
-
const coeffs = fullLinearRegression(toNumberMatrix(dataX, "
|
|
14008
|
+
const coeffs = fullLinearRegression(toNumberMatrix(dataX, "data_x"), logM(toNumberMatrix(dataY, "data_y")), toBoolean(calculateB), toBoolean(verbose));
|
|
13998
14009
|
for (let i = 0; i < coeffs.length; i++) {
|
|
13999
14010
|
coeffs[i][0] = Math.exp(coeffs[i][0]);
|
|
14000
14011
|
}
|
|
@@ -14615,7 +14626,7 @@ const TREND = {
|
|
|
14615
14626
|
if (knownDataY.length === 0 || knownDataY[0].length === 0) {
|
|
14616
14627
|
return new EvaluationError(emptyDataErrorMessage("known_data_y"));
|
|
14617
14628
|
}
|
|
14618
|
-
return predictLinearValues(toNumberMatrix(knownDataY, "
|
|
14629
|
+
return predictLinearValues(toNumberMatrix(knownDataY, "known_data_y"), toNumberMatrix(knownDataX, "known_data_x"), toNumberMatrix(newDataX, "new_data_y"), toBoolean(b));
|
|
14619
14630
|
},
|
|
14620
14631
|
};
|
|
14621
14632
|
// -----------------------------------------------------------------------------
|
|
@@ -23136,6 +23147,10 @@ const chartShowValuesPlugin = {
|
|
|
23136
23147
|
}
|
|
23137
23148
|
const ctx = chart.ctx;
|
|
23138
23149
|
ctx.save();
|
|
23150
|
+
const { left, top, height, width } = chart.chartArea;
|
|
23151
|
+
ctx.beginPath();
|
|
23152
|
+
ctx.rect(left, top, width, height);
|
|
23153
|
+
ctx.clip();
|
|
23139
23154
|
ctx.textAlign = "center";
|
|
23140
23155
|
ctx.textBaseline = "middle";
|
|
23141
23156
|
ctx.miterLimit = 1; // Avoid sharp artifacts on strokeText
|
|
@@ -24179,7 +24194,7 @@ class ChartJsComponent extends owl.Component {
|
|
|
24179
24194
|
this.chart.update();
|
|
24180
24195
|
}
|
|
24181
24196
|
hasChartDataChanged() {
|
|
24182
|
-
return !deepEquals(this.currentRuntime
|
|
24197
|
+
return !deepEquals(this.getChartDataInRuntime(this.currentRuntime), this.getChartDataInRuntime(this.chartRuntime));
|
|
24183
24198
|
}
|
|
24184
24199
|
enableAnimationInChartData(chartData) {
|
|
24185
24200
|
return {
|
|
@@ -24187,6 +24202,17 @@ class ChartJsComponent extends owl.Component {
|
|
|
24187
24202
|
options: { ...chartData.options, animation: { animateRotate: true } },
|
|
24188
24203
|
};
|
|
24189
24204
|
}
|
|
24205
|
+
getChartDataInRuntime(runtime) {
|
|
24206
|
+
const data = runtime.chartJsConfig.data;
|
|
24207
|
+
return {
|
|
24208
|
+
labels: data.labels,
|
|
24209
|
+
dataset: data.datasets.map((dataset) => ({
|
|
24210
|
+
data: dataset.data,
|
|
24211
|
+
label: dataset.label,
|
|
24212
|
+
tree: dataset.tree,
|
|
24213
|
+
})),
|
|
24214
|
+
};
|
|
24215
|
+
}
|
|
24190
24216
|
get animationChartId() {
|
|
24191
24217
|
return this.props.isFullScreen ? this.props.chartId + "-fullscreen" : this.props.chartId;
|
|
24192
24218
|
}
|
|
@@ -24854,6 +24880,7 @@ class ScorecardChart extends owl.Component {
|
|
|
24854
24880
|
static template = "o-spreadsheet-ScorecardChart";
|
|
24855
24881
|
static props = {
|
|
24856
24882
|
chartId: String,
|
|
24883
|
+
isFullScreen: { type: Boolean, optional: true },
|
|
24857
24884
|
};
|
|
24858
24885
|
canvas = owl.useRef("chartContainer");
|
|
24859
24886
|
get runtime() {
|
|
@@ -25609,6 +25636,7 @@ function getChartTimeOptions(labels, labelFormat, locale) {
|
|
|
25609
25636
|
parser: luxonFormat,
|
|
25610
25637
|
displayFormats,
|
|
25611
25638
|
unit: timeUnit ?? false,
|
|
25639
|
+
tooltipFormat: luxonFormat,
|
|
25612
25640
|
};
|
|
25613
25641
|
}
|
|
25614
25642
|
/**
|
|
@@ -26677,6 +26705,7 @@ function getLineChartScales(definition, args) {
|
|
|
26677
26705
|
};
|
|
26678
26706
|
Object.assign(scales.x, axis);
|
|
26679
26707
|
scales.x.ticks.maxTicksLimit = 15;
|
|
26708
|
+
delete scales?.x?.ticks?.callback;
|
|
26680
26709
|
}
|
|
26681
26710
|
else if (axisType === "linear") {
|
|
26682
26711
|
scales.x.type = "linear";
|
|
@@ -27617,26 +27646,6 @@ function createBarChartRuntime(chart, getters) {
|
|
|
27617
27646
|
return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
|
|
27618
27647
|
}
|
|
27619
27648
|
|
|
27620
|
-
class FullScreenChartStore extends SpreadsheetStore {
|
|
27621
|
-
mutators = ["toggleFullScreenChart"];
|
|
27622
|
-
fullScreenFigure = undefined;
|
|
27623
|
-
toggleFullScreenChart(figureId) {
|
|
27624
|
-
if (this.fullScreenFigure?.id === figureId) {
|
|
27625
|
-
this.fullScreenFigure = undefined;
|
|
27626
|
-
}
|
|
27627
|
-
else {
|
|
27628
|
-
this.makeFullScreen(figureId);
|
|
27629
|
-
}
|
|
27630
|
-
}
|
|
27631
|
-
makeFullScreen(figureId) {
|
|
27632
|
-
const sheetId = this.getters.getActiveSheetId();
|
|
27633
|
-
const figure = this.getters.getFigure(sheetId, figureId);
|
|
27634
|
-
if (figure) {
|
|
27635
|
-
this.fullScreenFigure = { ...figure, x: 0, y: 0, width: 0, height: 0 };
|
|
27636
|
-
}
|
|
27637
|
-
}
|
|
27638
|
-
}
|
|
27639
|
-
|
|
27640
27649
|
const TREND_LINE_AXES_IDS = [TREND_LINE_XAXIS_ID, MOVING_AVERAGE_TREND_LINE_XAXIS_ID];
|
|
27641
27650
|
const ZOOMABLE_AXIS_IDS = ["x", ...TREND_LINE_AXES_IDS];
|
|
27642
27651
|
class ZoomableChartStore extends SpreadsheetStore {
|
|
@@ -27808,7 +27817,6 @@ chartJsExtensionRegistry.add("zoomWindowPlugin", {
|
|
|
27808
27817
|
class ZoomableChartJsComponent extends ChartJsComponent {
|
|
27809
27818
|
static template = "o-spreadsheet-ZoomableChartJsComponent";
|
|
27810
27819
|
store;
|
|
27811
|
-
fullScreenChartStore;
|
|
27812
27820
|
masterChartCanvas = owl.useRef("masterChartCanvas");
|
|
27813
27821
|
masterChart;
|
|
27814
27822
|
mode;
|
|
@@ -27819,7 +27827,6 @@ class ZoomableChartJsComponent extends ChartJsComponent {
|
|
|
27819
27827
|
removeEventListeners = () => { };
|
|
27820
27828
|
setup() {
|
|
27821
27829
|
this.store = useStore(ZoomableChartStore);
|
|
27822
|
-
this.fullScreenChartStore = useStore(FullScreenChartStore);
|
|
27823
27830
|
super.setup();
|
|
27824
27831
|
}
|
|
27825
27832
|
unmount() {
|
|
@@ -27834,12 +27841,8 @@ class ZoomableChartJsComponent extends ChartJsComponent {
|
|
|
27834
27841
|
`;
|
|
27835
27842
|
}
|
|
27836
27843
|
get sliceable() {
|
|
27837
|
-
if (this.
|
|
27838
|
-
|
|
27839
|
-
const chartFigureId = this.env.model.getters.getFigureIdFromChartId(this.props.chartId);
|
|
27840
|
-
if (fullScreenFigureId === chartFigureId) {
|
|
27841
|
-
return true;
|
|
27842
|
-
}
|
|
27844
|
+
if (this.props.isFullScreen) {
|
|
27845
|
+
return true;
|
|
27843
27846
|
}
|
|
27844
27847
|
const definition = this.env.model.getters.getChartDefinition(this.props.chartId);
|
|
27845
27848
|
return ("zoomable" in definition && definition?.zoomable) ?? false;
|
|
@@ -27902,9 +27905,6 @@ class ZoomableChartJsComponent extends ChartJsComponent {
|
|
|
27902
27905
|
const xMax = Math.max(...xValues);
|
|
27903
27906
|
return { xMin, xMax };
|
|
27904
27907
|
}
|
|
27905
|
-
get shouldAnimate() {
|
|
27906
|
-
return this.env.model.getters.isDashboard() && !this.sliceable;
|
|
27907
|
-
}
|
|
27908
27908
|
createChart(chartRuntime) {
|
|
27909
27909
|
const chartData = chartRuntime.chartJsConfig;
|
|
27910
27910
|
this.isBarChart = chartData.type === "bar";
|
|
@@ -27923,6 +27923,9 @@ class ZoomableChartJsComponent extends ChartJsComponent {
|
|
|
27923
27923
|
const masterChartCtx = this.masterChartCanvas.el.getContext("2d");
|
|
27924
27924
|
this.masterChart = new window.Chart(masterChartCtx, this.getMasterChartConfiguration(chartRuntime["masterChartConfig"]));
|
|
27925
27925
|
this.resetAxesLimits();
|
|
27926
|
+
if (this.chart?.options) {
|
|
27927
|
+
this.chart.options.animation = false;
|
|
27928
|
+
}
|
|
27926
27929
|
}
|
|
27927
27930
|
updateChartJs(chartRuntime) {
|
|
27928
27931
|
const chartData = chartRuntime.chartJsConfig;
|
|
@@ -27956,6 +27959,9 @@ class ZoomableChartJsComponent extends ChartJsComponent {
|
|
|
27956
27959
|
}
|
|
27957
27960
|
}
|
|
27958
27961
|
this.resetAxesLimits();
|
|
27962
|
+
if (this.chart?.options) {
|
|
27963
|
+
this.chart.options.animation = false;
|
|
27964
|
+
}
|
|
27959
27965
|
}
|
|
27960
27966
|
resetAxesLimits() {
|
|
27961
27967
|
if (!this.chart) {
|
|
@@ -28053,7 +28059,7 @@ class ZoomableChartJsComponent extends ChartJsComponent {
|
|
|
28053
28059
|
onPointerDownInMasterChart(ev) {
|
|
28054
28060
|
this.removeEventListeners();
|
|
28055
28061
|
const position = ev.offsetX;
|
|
28056
|
-
if (!this.masterChart?.chartArea || !this.chart?.scales
|
|
28062
|
+
if (!this.masterChart?.chartArea || !this.chart?.scales?.x) {
|
|
28057
28063
|
return;
|
|
28058
28064
|
}
|
|
28059
28065
|
const { left, right, top, bottom } = this.masterChart.chartArea;
|
|
@@ -31611,6 +31617,26 @@ function getCarouselItemTitle(getters, item) {
|
|
|
31611
31617
|
return matchedChart.displayName;
|
|
31612
31618
|
}
|
|
31613
31619
|
|
|
31620
|
+
class FullScreenFigureStore extends SpreadsheetStore {
|
|
31621
|
+
mutators = ["toggleFullScreenFigure"];
|
|
31622
|
+
fullScreenFigure = undefined;
|
|
31623
|
+
toggleFullScreenFigure(figureId) {
|
|
31624
|
+
if (this.fullScreenFigure?.id === figureId) {
|
|
31625
|
+
this.fullScreenFigure = undefined;
|
|
31626
|
+
}
|
|
31627
|
+
else {
|
|
31628
|
+
this.makeFullScreen(figureId);
|
|
31629
|
+
}
|
|
31630
|
+
}
|
|
31631
|
+
makeFullScreen(figureId) {
|
|
31632
|
+
const sheetId = this.getters.getActiveSheetId();
|
|
31633
|
+
const figure = this.getters.getFigure(sheetId, figureId);
|
|
31634
|
+
if (figure) {
|
|
31635
|
+
this.fullScreenFigure = { ...figure, x: 0, y: 0, width: 0, height: 0 };
|
|
31636
|
+
}
|
|
31637
|
+
}
|
|
31638
|
+
}
|
|
31639
|
+
|
|
31614
31640
|
/**
|
|
31615
31641
|
* Repeatedly calls a callback function with a time delay between calls.
|
|
31616
31642
|
*/
|
|
@@ -32348,12 +32374,13 @@ class MenuPopover extends owl.Component {
|
|
|
32348
32374
|
class ChartDashboardMenu extends owl.Component {
|
|
32349
32375
|
static template = "o-spreadsheet-ChartDashboardMenu";
|
|
32350
32376
|
static components = { MenuPopover };
|
|
32351
|
-
static props = { chartId: String };
|
|
32377
|
+
static props = { chartId: String, hasFullScreenButton: { type: Boolean, optional: true } };
|
|
32378
|
+
static defaultProps = { hasFullScreenButton: true };
|
|
32352
32379
|
fullScreenFigureStore;
|
|
32353
32380
|
menuState = owl.useState({ isOpen: false, anchorRect: null, menuItems: [] });
|
|
32354
32381
|
setup() {
|
|
32355
32382
|
super.setup();
|
|
32356
|
-
this.fullScreenFigureStore = useStore(
|
|
32383
|
+
this.fullScreenFigureStore = useStore(FullScreenFigureStore);
|
|
32357
32384
|
}
|
|
32358
32385
|
getMenuItems() {
|
|
32359
32386
|
return [this.fullScreenMenuItem].filter(isDefined);
|
|
@@ -32364,11 +32391,14 @@ class ChartDashboardMenu extends owl.Component {
|
|
|
32364
32391
|
}
|
|
32365
32392
|
openContextMenu(ev) {
|
|
32366
32393
|
this.menuState.isOpen = true;
|
|
32367
|
-
this.menuState.anchorRect =
|
|
32394
|
+
this.menuState.anchorRect = getBoundingRectAsPOJO(ev.currentTarget);
|
|
32368
32395
|
const figureId = this.env.model.getters.getFigureIdFromChartId(this.props.chartId);
|
|
32369
32396
|
this.menuState.menuItems = getChartMenuActions(figureId, () => { }, this.env);
|
|
32370
32397
|
}
|
|
32371
32398
|
get fullScreenMenuItem() {
|
|
32399
|
+
if (!this.props.hasFullScreenButton) {
|
|
32400
|
+
return undefined;
|
|
32401
|
+
}
|
|
32372
32402
|
const definition = this.env.model.getters.getChartDefinition(this.props.chartId);
|
|
32373
32403
|
const figureId = this.env.model.getters.getFigureIdFromChartId(this.props.chartId);
|
|
32374
32404
|
if (definition.type === "scorecard") {
|
|
@@ -32380,7 +32410,7 @@ class ChartDashboardMenu extends owl.Component {
|
|
|
32380
32410
|
label: isFullScreen ? _t("Exit Full Screen") : _t("Full Screen"),
|
|
32381
32411
|
class: `text-muted fa ${isFullScreen ? "fa-compress" : "fa-expand"}`,
|
|
32382
32412
|
onClick: () => {
|
|
32383
|
-
this.fullScreenFigureStore.
|
|
32413
|
+
this.fullScreenFigureStore.toggleFullScreenFigure(figureId);
|
|
32384
32414
|
},
|
|
32385
32415
|
};
|
|
32386
32416
|
}
|
|
@@ -32392,6 +32422,8 @@ class CarouselFigure extends owl.Component {
|
|
|
32392
32422
|
figureUI: Object,
|
|
32393
32423
|
onFigureDeleted: Function,
|
|
32394
32424
|
editFigureStyle: { type: Function, optional: true },
|
|
32425
|
+
isFullScreen: { type: Boolean, optional: true },
|
|
32426
|
+
openContextMenu: { type: Function, optional: true },
|
|
32395
32427
|
};
|
|
32396
32428
|
static components = { ChartDashboardMenu, MenuPopover };
|
|
32397
32429
|
carouselTabsRef = owl.useRef("carouselTabs");
|
|
@@ -32399,8 +32431,10 @@ class CarouselFigure extends owl.Component {
|
|
|
32399
32431
|
menuState = owl.useState({ isOpen: false, anchorRect: null, menuItems: [] });
|
|
32400
32432
|
hiddenItems = [];
|
|
32401
32433
|
animationStore;
|
|
32434
|
+
fullScreenFigureStore;
|
|
32402
32435
|
setup() {
|
|
32403
32436
|
this.animationStore = useStore(ChartAnimationStore);
|
|
32437
|
+
this.fullScreenFigureStore = useStore(FullScreenFigureStore);
|
|
32404
32438
|
owl.useEffect(() => {
|
|
32405
32439
|
if (this.selectedCarouselItem?.type === "carouselDataView") {
|
|
32406
32440
|
this.props.editFigureStyle?.({ "pointer-events": "none" });
|
|
@@ -32447,7 +32481,8 @@ class CarouselFigure extends owl.Component {
|
|
|
32447
32481
|
item,
|
|
32448
32482
|
});
|
|
32449
32483
|
if (item.type === "chart") {
|
|
32450
|
-
|
|
32484
|
+
const animationChartId = item.chartId + (this.props.isFullScreen ? "-fullscreen" : "");
|
|
32485
|
+
this.animationStore?.enableAnimationForChart(animationChartId);
|
|
32451
32486
|
}
|
|
32452
32487
|
}
|
|
32453
32488
|
get headerStyle() {
|
|
@@ -32511,6 +32546,23 @@ class CarouselFigure extends owl.Component {
|
|
|
32511
32546
|
this.menuState.anchorRect = rect;
|
|
32512
32547
|
this.menuState.menuItems = createActions(menuItems);
|
|
32513
32548
|
}
|
|
32549
|
+
toggleFullScreen() {
|
|
32550
|
+
if (this.selectedCarouselItem?.type === "chart") {
|
|
32551
|
+
this.fullScreenFigureStore.toggleFullScreenFigure(this.props.figureUI.id);
|
|
32552
|
+
}
|
|
32553
|
+
}
|
|
32554
|
+
get fullScreenButtonTitle() {
|
|
32555
|
+
return this.props.isFullScreen ? _t("Exit Full Screen") : _t("Full Screen");
|
|
32556
|
+
}
|
|
32557
|
+
get visibleCarouselItems() {
|
|
32558
|
+
return this.carousel.items.filter((item) => item.type === "carouselDataView" && this.props.isFullScreen ? false : true);
|
|
32559
|
+
}
|
|
32560
|
+
openContextMenu(event) {
|
|
32561
|
+
const target = event.currentTarget;
|
|
32562
|
+
if (target) {
|
|
32563
|
+
this.props.openContextMenu?.(getBoundingRectAsPOJO(target));
|
|
32564
|
+
}
|
|
32565
|
+
}
|
|
32514
32566
|
}
|
|
32515
32567
|
|
|
32516
32568
|
class ChartFigure extends owl.Component {
|
|
@@ -32519,6 +32571,8 @@ class ChartFigure extends owl.Component {
|
|
|
32519
32571
|
figureUI: Object,
|
|
32520
32572
|
onFigureDeleted: Function,
|
|
32521
32573
|
editFigureStyle: { type: Function, optional: true },
|
|
32574
|
+
isFullScreen: { type: Boolean, optional: true },
|
|
32575
|
+
openContextMenu: { type: Function, optional: true },
|
|
32522
32576
|
};
|
|
32523
32577
|
static components = { ChartDashboardMenu };
|
|
32524
32578
|
onDoubleClick() {
|
|
@@ -32551,6 +32605,7 @@ class ImageFigure extends owl.Component {
|
|
|
32551
32605
|
figureUI: Object,
|
|
32552
32606
|
onFigureDeleted: Function,
|
|
32553
32607
|
editFigureStyle: { type: Function, optional: true },
|
|
32608
|
+
openContextMenu: { type: Function, optional: true },
|
|
32554
32609
|
};
|
|
32555
32610
|
static components = {};
|
|
32556
32611
|
// ---------------------------------------------------------------------------
|
|
@@ -34602,8 +34657,11 @@ class Composer extends owl.Component {
|
|
|
34602
34657
|
}
|
|
34603
34658
|
const newSelection = this.contentHelper.getCurrentSelection();
|
|
34604
34659
|
this.props.composerStore.stopComposerRangeSelection();
|
|
34605
|
-
this.props.
|
|
34606
|
-
this.props.
|
|
34660
|
+
const isCurrentlyInactive = this.props.composerStore.editionMode === "inactive";
|
|
34661
|
+
this.props.onComposerContentFocused(newSelection);
|
|
34662
|
+
if (!isCurrentlyInactive) {
|
|
34663
|
+
this.props.composerStore.changeComposerCursorSelection(newSelection.start, newSelection.end);
|
|
34664
|
+
}
|
|
34607
34665
|
this.processTokenAtCursor();
|
|
34608
34666
|
}
|
|
34609
34667
|
onDblClick() {
|
|
@@ -35098,13 +35156,6 @@ class AbstractComposerStore extends SpreadsheetStore {
|
|
|
35098
35156
|
}
|
|
35099
35157
|
}
|
|
35100
35158
|
startEdition(text, selection) {
|
|
35101
|
-
if (selection) {
|
|
35102
|
-
const content = text || this.getComposerContent(this.getters.getActivePosition());
|
|
35103
|
-
const validSelection = this.isSelectionValid(content.length, selection.start, selection.end);
|
|
35104
|
-
if (!validSelection) {
|
|
35105
|
-
return;
|
|
35106
|
-
}
|
|
35107
|
-
}
|
|
35108
35159
|
const { col, row } = this.getters.getActivePosition();
|
|
35109
35160
|
this.model.dispatch("SELECT_FIGURE", { figureId: null });
|
|
35110
35161
|
this.model.dispatch("SCROLL_TO_CELL", { col, row });
|
|
@@ -35161,7 +35212,7 @@ class AbstractComposerStore extends SpreadsheetStore {
|
|
|
35161
35212
|
// ---------------------------------------------------------------------------
|
|
35162
35213
|
get currentContent() {
|
|
35163
35214
|
if (this.editionMode === "inactive") {
|
|
35164
|
-
return this.getComposerContent(this.getters.getActivePosition());
|
|
35215
|
+
return this.getComposerContent(this.getters.getActivePosition()).text;
|
|
35165
35216
|
}
|
|
35166
35217
|
return this._currentContent;
|
|
35167
35218
|
}
|
|
@@ -35360,8 +35411,9 @@ class AbstractComposerStore extends SpreadsheetStore {
|
|
|
35360
35411
|
this.sheetId = sheetId;
|
|
35361
35412
|
this.row = row;
|
|
35362
35413
|
this.editionMode = "editing";
|
|
35363
|
-
|
|
35364
|
-
this.
|
|
35414
|
+
const { text, adjustedSelection } = this.getComposerContent({ sheetId, col, row }, selection);
|
|
35415
|
+
this.initialContent = text;
|
|
35416
|
+
this.setContent(str || this.initialContent, adjustedSelection ?? selection);
|
|
35365
35417
|
this.colorIndexByRange = {};
|
|
35366
35418
|
const zone = positionToZone({ col: this.col, row: this.row });
|
|
35367
35419
|
this.captureSelection(zone, col, row);
|
|
@@ -35838,7 +35890,7 @@ class StandaloneComposerStore extends AbstractComposerStore {
|
|
|
35838
35890
|
constructor(get, args) {
|
|
35839
35891
|
super(get);
|
|
35840
35892
|
this.args = args;
|
|
35841
|
-
this._currentContent = this.getComposerContent();
|
|
35893
|
+
this._currentContent = this.getComposerContent().text;
|
|
35842
35894
|
}
|
|
35843
35895
|
getAutoCompleteProviders() {
|
|
35844
35896
|
const providersDefinitions = super.getAutoCompleteProviders();
|
|
@@ -35875,7 +35927,7 @@ class StandaloneComposerStore extends AbstractComposerStore {
|
|
|
35875
35927
|
})
|
|
35876
35928
|
.join("");
|
|
35877
35929
|
}
|
|
35878
|
-
return localizeContent(content, this.getters.getLocale());
|
|
35930
|
+
return { text: localizeContent(content, this.getters.getLocale()) };
|
|
35879
35931
|
}
|
|
35880
35932
|
stopEdition() {
|
|
35881
35933
|
this._stopEdition();
|
|
@@ -39553,6 +39605,74 @@ function getPath2D(svgPath) {
|
|
|
39553
39605
|
return path2D;
|
|
39554
39606
|
}
|
|
39555
39607
|
|
|
39608
|
+
/**
|
|
39609
|
+
* Get the relative path between two files
|
|
39610
|
+
*
|
|
39611
|
+
* Eg.:
|
|
39612
|
+
* from "folder1/file1.txt" to "folder2/file2.txt" => "../folder2/file2.txt"
|
|
39613
|
+
*/
|
|
39614
|
+
function getRelativePath(from, to) {
|
|
39615
|
+
const fromPathParts = from.split("/");
|
|
39616
|
+
const toPathParts = to.split("/");
|
|
39617
|
+
let relPath = "";
|
|
39618
|
+
let startIndex = 0;
|
|
39619
|
+
for (let i = 0; i < fromPathParts.length - 1; i++) {
|
|
39620
|
+
if (fromPathParts[i] === toPathParts[i]) {
|
|
39621
|
+
startIndex++;
|
|
39622
|
+
}
|
|
39623
|
+
else {
|
|
39624
|
+
relPath += "../";
|
|
39625
|
+
}
|
|
39626
|
+
}
|
|
39627
|
+
relPath += toPathParts.slice(startIndex).join("/");
|
|
39628
|
+
return relPath;
|
|
39629
|
+
}
|
|
39630
|
+
/**
|
|
39631
|
+
* Convert an array of element into an object where the objects keys were the elements position in the array.
|
|
39632
|
+
* Can give an offset as argument, and all the array indexes will we shifted by this offset in the returned object.
|
|
39633
|
+
*
|
|
39634
|
+
* eg. : ["a", "b"] => {0:"a", 1:"b"}
|
|
39635
|
+
*/
|
|
39636
|
+
function arrayToObject(array, indexOffset = 0) {
|
|
39637
|
+
const obj = {};
|
|
39638
|
+
for (let i = 0; i < array.length; i++) {
|
|
39639
|
+
if (array[i]) {
|
|
39640
|
+
obj[i + indexOffset] = array[i];
|
|
39641
|
+
}
|
|
39642
|
+
}
|
|
39643
|
+
return obj;
|
|
39644
|
+
}
|
|
39645
|
+
/**
|
|
39646
|
+
* In xlsx we can have string with unicode characters with the format _x00fa_.
|
|
39647
|
+
* Replace with characters understandable by JS
|
|
39648
|
+
*/
|
|
39649
|
+
function fixXlsxUnicode(str) {
|
|
39650
|
+
return str.replace(/_x([0-9a-zA-Z]{4})_/g, (match, code) => {
|
|
39651
|
+
return String.fromCharCode(parseInt(code, 16));
|
|
39652
|
+
});
|
|
39653
|
+
}
|
|
39654
|
+
/** Get a header in the SheetData. Create the header if it doesn't exist in the SheetData */
|
|
39655
|
+
function getSheetDataHeader(sheetData, dimension, index) {
|
|
39656
|
+
if (dimension === "COL") {
|
|
39657
|
+
if (!sheetData.cols[index]) {
|
|
39658
|
+
sheetData.cols[index] = {};
|
|
39659
|
+
}
|
|
39660
|
+
return sheetData.cols[index];
|
|
39661
|
+
}
|
|
39662
|
+
if (!sheetData.rows[index]) {
|
|
39663
|
+
sheetData.rows[index] = {};
|
|
39664
|
+
}
|
|
39665
|
+
return sheetData.rows[index];
|
|
39666
|
+
}
|
|
39667
|
+
/** Prefix the string by "=" if the string looks like a formula */
|
|
39668
|
+
function prefixFormulaWithEqual(formula) {
|
|
39669
|
+
if (formula[0] === "=") {
|
|
39670
|
+
return formula;
|
|
39671
|
+
}
|
|
39672
|
+
const tokens = tokenize(formula);
|
|
39673
|
+
return tokens.length === 1 && tokens[0].type !== "REFERENCE" ? formula : "=" + formula;
|
|
39674
|
+
}
|
|
39675
|
+
|
|
39556
39676
|
/**
|
|
39557
39677
|
* Map of the different types of conversions warnings and their name in error messages
|
|
39558
39678
|
*/
|
|
@@ -40075,66 +40195,6 @@ function hexaToInt(hex) {
|
|
|
40075
40195
|
*/
|
|
40076
40196
|
const DEFAULT_SYSTEM_COLOR = "FF000000";
|
|
40077
40197
|
|
|
40078
|
-
/**
|
|
40079
|
-
* Get the relative path between two files
|
|
40080
|
-
*
|
|
40081
|
-
* Eg.:
|
|
40082
|
-
* from "folder1/file1.txt" to "folder2/file2.txt" => "../folder2/file2.txt"
|
|
40083
|
-
*/
|
|
40084
|
-
function getRelativePath(from, to) {
|
|
40085
|
-
const fromPathParts = from.split("/");
|
|
40086
|
-
const toPathParts = to.split("/");
|
|
40087
|
-
let relPath = "";
|
|
40088
|
-
let startIndex = 0;
|
|
40089
|
-
for (let i = 0; i < fromPathParts.length - 1; i++) {
|
|
40090
|
-
if (fromPathParts[i] === toPathParts[i]) {
|
|
40091
|
-
startIndex++;
|
|
40092
|
-
}
|
|
40093
|
-
else {
|
|
40094
|
-
relPath += "../";
|
|
40095
|
-
}
|
|
40096
|
-
}
|
|
40097
|
-
relPath += toPathParts.slice(startIndex).join("/");
|
|
40098
|
-
return relPath;
|
|
40099
|
-
}
|
|
40100
|
-
/**
|
|
40101
|
-
* Convert an array of element into an object where the objects keys were the elements position in the array.
|
|
40102
|
-
* Can give an offset as argument, and all the array indexes will we shifted by this offset in the returned object.
|
|
40103
|
-
*
|
|
40104
|
-
* eg. : ["a", "b"] => {0:"a", 1:"b"}
|
|
40105
|
-
*/
|
|
40106
|
-
function arrayToObject(array, indexOffset = 0) {
|
|
40107
|
-
const obj = {};
|
|
40108
|
-
for (let i = 0; i < array.length; i++) {
|
|
40109
|
-
if (array[i]) {
|
|
40110
|
-
obj[i + indexOffset] = array[i];
|
|
40111
|
-
}
|
|
40112
|
-
}
|
|
40113
|
-
return obj;
|
|
40114
|
-
}
|
|
40115
|
-
/**
|
|
40116
|
-
* In xlsx we can have string with unicode characters with the format _x00fa_.
|
|
40117
|
-
* Replace with characters understandable by JS
|
|
40118
|
-
*/
|
|
40119
|
-
function fixXlsxUnicode(str) {
|
|
40120
|
-
return str.replace(/_x([0-9a-zA-Z]{4})_/g, (match, code) => {
|
|
40121
|
-
return String.fromCharCode(parseInt(code, 16));
|
|
40122
|
-
});
|
|
40123
|
-
}
|
|
40124
|
-
/** Get a header in the SheetData. Create the header if it doesn't exist in the SheetData */
|
|
40125
|
-
function getSheetDataHeader(sheetData, dimension, index) {
|
|
40126
|
-
if (dimension === "COL") {
|
|
40127
|
-
if (!sheetData.cols[index]) {
|
|
40128
|
-
sheetData.cols[index] = {};
|
|
40129
|
-
}
|
|
40130
|
-
return sheetData.cols[index];
|
|
40131
|
-
}
|
|
40132
|
-
if (!sheetData.rows[index]) {
|
|
40133
|
-
sheetData.rows[index] = {};
|
|
40134
|
-
}
|
|
40135
|
-
return sheetData.rows[index];
|
|
40136
|
-
}
|
|
40137
|
-
|
|
40138
40198
|
const XLSX_DATE_FORMAT_REGEX = /^(yy|yyyy|m{1,5}|d{1,4}|h{1,2}|s{1,2}|am\/pm|a\/m|\s|-|\/|\.|:)+$/i;
|
|
40139
40199
|
/**
|
|
40140
40200
|
* Convert excel format to o_spreadsheet format
|
|
@@ -40349,9 +40409,9 @@ function convertConditionalFormats(xlsxCfs, dxfs, warningManager) {
|
|
|
40349
40409
|
if (!rule.operator || !rule.formula || rule.formula.length === 0)
|
|
40350
40410
|
continue;
|
|
40351
40411
|
operator = CF_OPERATOR_TYPE_CONVERSION_MAP[rule.operator];
|
|
40352
|
-
values.push(
|
|
40412
|
+
values.push(prefixFormulaWithEqual(rule.formula[0]));
|
|
40353
40413
|
if (rule.formula.length === 2) {
|
|
40354
|
-
values.push(
|
|
40414
|
+
values.push(prefixFormulaWithEqual(rule.formula[1]));
|
|
40355
40415
|
}
|
|
40356
40416
|
break;
|
|
40357
40417
|
}
|
|
@@ -40509,11 +40569,6 @@ function convertIcons(xlsxIconSet, index) {
|
|
|
40509
40569
|
? ICON_SETS[iconSet].neutral
|
|
40510
40570
|
: ICON_SETS[iconSet].good;
|
|
40511
40571
|
}
|
|
40512
|
-
/** Prefix the string by "=" if the string looks like a formula */
|
|
40513
|
-
function prefixFormula(formula) {
|
|
40514
|
-
const tokens = tokenize(formula);
|
|
40515
|
-
return tokens.length === 1 && tokens[0].type !== "REFERENCE" ? formula : "=" + formula;
|
|
40516
|
-
}
|
|
40517
40572
|
// ---------------------------------------------------------------------------
|
|
40518
40573
|
// Warnings
|
|
40519
40574
|
// ---------------------------------------------------------------------------
|
|
@@ -40989,7 +41044,7 @@ function convertDataValidationRules(xlsxDataValidations, warningManager) {
|
|
|
40989
41044
|
dvRules.push(decimalRule);
|
|
40990
41045
|
break;
|
|
40991
41046
|
case "list":
|
|
40992
|
-
const listRule =
|
|
41047
|
+
const listRule = convertListRule(dvId++, dv);
|
|
40993
41048
|
dvRules.push(listRule);
|
|
40994
41049
|
break;
|
|
40995
41050
|
case "date":
|
|
@@ -41009,9 +41064,9 @@ function convertDataValidationRules(xlsxDataValidations, warningManager) {
|
|
|
41009
41064
|
return dvRules;
|
|
41010
41065
|
}
|
|
41011
41066
|
function convertDecimalRule(id, dv) {
|
|
41012
|
-
const values = [dv.formula1.toString()];
|
|
41067
|
+
const values = [prefixFormulaWithEqual(dv.formula1.toString())];
|
|
41013
41068
|
if (dv.formula2) {
|
|
41014
|
-
values.push(dv.formula2.toString());
|
|
41069
|
+
values.push(prefixFormulaWithEqual(dv.formula2.toString()));
|
|
41015
41070
|
}
|
|
41016
41071
|
return {
|
|
41017
41072
|
id: id.toString(),
|
|
@@ -41023,7 +41078,7 @@ function convertDecimalRule(id, dv) {
|
|
|
41023
41078
|
},
|
|
41024
41079
|
};
|
|
41025
41080
|
}
|
|
41026
|
-
function
|
|
41081
|
+
function convertListRule(id, dv) {
|
|
41027
41082
|
const formula1 = dv.formula1.toString();
|
|
41028
41083
|
const isRangeRule = rangeReference.test(formula1);
|
|
41029
41084
|
return {
|
|
@@ -41039,9 +41094,9 @@ function convertListrule(id, dv) {
|
|
|
41039
41094
|
}
|
|
41040
41095
|
function convertDateRule(id, dv) {
|
|
41041
41096
|
let criterion;
|
|
41042
|
-
const values = [dv.formula1.toString()];
|
|
41097
|
+
const values = [prefixFormulaWithEqual(dv.formula1.toString())];
|
|
41043
41098
|
if (dv.formula2) {
|
|
41044
|
-
values.push(dv.formula2.toString());
|
|
41099
|
+
values.push(prefixFormulaWithEqual(dv.formula2.toString()));
|
|
41045
41100
|
criterion = {
|
|
41046
41101
|
type: XLSX_DV_DATE_OPERATOR_TO_DV_TYPE_MAPPING[dv.operator],
|
|
41047
41102
|
values: getDateCriterionFormattedValues(values, DEFAULT_LOCALE),
|
|
@@ -41068,7 +41123,7 @@ function convertCustomRule(id, dv) {
|
|
|
41068
41123
|
isBlocking: dv.errorStyle !== "warning",
|
|
41069
41124
|
criterion: {
|
|
41070
41125
|
type: "customFormula",
|
|
41071
|
-
values: [
|
|
41126
|
+
values: [prefixFormulaWithEqual(dv.formula1.toString())],
|
|
41072
41127
|
},
|
|
41073
41128
|
};
|
|
41074
41129
|
}
|
|
@@ -49732,39 +49787,63 @@ class CellComposerStore extends AbstractComposerStore {
|
|
|
49732
49787
|
this.model.dispatch("AUTOFILL_TABLE_COLUMN", { ...this.currentEditedCell });
|
|
49733
49788
|
this.setContent("");
|
|
49734
49789
|
}
|
|
49735
|
-
getComposerContent(position) {
|
|
49790
|
+
getComposerContent(position, selection) {
|
|
49736
49791
|
const locale = this.getters.getLocale();
|
|
49737
49792
|
const cell = this.getters.getCell(position);
|
|
49738
49793
|
if (cell?.isFormula) {
|
|
49739
49794
|
const prettifiedContent = this.getPrettifiedFormula(cell);
|
|
49740
|
-
|
|
49795
|
+
// when a formula is prettified (multi lines, indented), adapt the cursor position
|
|
49796
|
+
// to take into account line breaks and tabs
|
|
49797
|
+
function adjustCursorIndex(targetIndex) {
|
|
49798
|
+
let adjustedIndex = 0;
|
|
49799
|
+
let originalIndex = 0;
|
|
49800
|
+
while (originalIndex < targetIndex) {
|
|
49801
|
+
adjustedIndex++;
|
|
49802
|
+
const char = prettifiedContent[adjustedIndex];
|
|
49803
|
+
if (char !== "\n" && char !== "\t") {
|
|
49804
|
+
originalIndex++;
|
|
49805
|
+
}
|
|
49806
|
+
}
|
|
49807
|
+
return adjustedIndex;
|
|
49808
|
+
}
|
|
49809
|
+
let adjustedSelection = selection;
|
|
49810
|
+
if (selection) {
|
|
49811
|
+
adjustedSelection = {
|
|
49812
|
+
start: adjustCursorIndex(selection.start),
|
|
49813
|
+
end: adjustCursorIndex(selection.end),
|
|
49814
|
+
};
|
|
49815
|
+
}
|
|
49816
|
+
return {
|
|
49817
|
+
text: localizeFormula(prettifiedContent, locale),
|
|
49818
|
+
adjustedSelection,
|
|
49819
|
+
};
|
|
49741
49820
|
}
|
|
49742
49821
|
const spreader = this.model.getters.getArrayFormulaSpreadingOn(position);
|
|
49743
49822
|
if (spreader) {
|
|
49744
|
-
return "";
|
|
49823
|
+
return { text: "" };
|
|
49745
49824
|
}
|
|
49746
49825
|
const { format, value, type, formattedValue } = this.getters.getEvaluatedCell(position);
|
|
49747
49826
|
switch (type) {
|
|
49748
49827
|
case CellValueType.empty:
|
|
49749
|
-
return "";
|
|
49828
|
+
return { text: "" };
|
|
49750
49829
|
case CellValueType.text:
|
|
49751
49830
|
case CellValueType.error:
|
|
49752
|
-
return value;
|
|
49831
|
+
return { text: value };
|
|
49753
49832
|
case CellValueType.boolean:
|
|
49754
|
-
return formattedValue;
|
|
49833
|
+
return { text: formattedValue };
|
|
49755
49834
|
case CellValueType.number:
|
|
49756
49835
|
if (format && isDateTimeFormat(format)) {
|
|
49757
49836
|
if (parseDateTime(formattedValue, locale) !== null) {
|
|
49758
49837
|
// formatted string can be parsed again
|
|
49759
|
-
return formattedValue;
|
|
49838
|
+
return { text: formattedValue };
|
|
49760
49839
|
}
|
|
49761
49840
|
// display a simplified and parsable string otherwise
|
|
49762
49841
|
const timeFormat = Number.isInteger(value)
|
|
49763
49842
|
? locale.dateFormat
|
|
49764
49843
|
: getDateTimeFormat(locale);
|
|
49765
|
-
return formatValue(value, { locale, format: timeFormat });
|
|
49844
|
+
return { text: formatValue(value, { locale, format: timeFormat }) };
|
|
49766
49845
|
}
|
|
49767
|
-
return this.numberComposerContent(value, format, locale);
|
|
49846
|
+
return { text: this.numberComposerContent(value, format, locale) };
|
|
49768
49847
|
}
|
|
49769
49848
|
}
|
|
49770
49849
|
getPrettifiedFormula(cell) {
|
|
@@ -49933,8 +50012,9 @@ class GridComposer extends owl.Component {
|
|
|
49933
50012
|
},
|
|
49934
50013
|
focus: this.focus,
|
|
49935
50014
|
isDefaultFocus: true,
|
|
49936
|
-
onComposerContentFocused: () => this.composerFocusStore.focusComposer(this.composerInterface, {
|
|
50015
|
+
onComposerContentFocused: (selection) => this.composerFocusStore.focusComposer(this.composerInterface, {
|
|
49937
50016
|
focusMode: "contentFocus",
|
|
50017
|
+
selection,
|
|
49938
50018
|
}),
|
|
49939
50019
|
onComposerCellFocused: (content) => this.composerFocusStore.focusComposer(this.composerInterface, {
|
|
49940
50020
|
focusMode: "cellFocus",
|
|
@@ -57340,12 +57420,13 @@ class DataValidationEditor extends owl.Component {
|
|
|
57340
57420
|
onCloseSidePanel: { type: Function, optional: true },
|
|
57341
57421
|
};
|
|
57342
57422
|
state = owl.useState({ rule: this.defaultDataValidationRule, errors: [] });
|
|
57423
|
+
editingSheetId;
|
|
57343
57424
|
setup() {
|
|
57425
|
+
this.editingSheetId = this.env.model.getters.getActiveSheetId();
|
|
57344
57426
|
if (this.props.rule) {
|
|
57345
|
-
const sheetId = this.env.model.getters.getActiveSheetId();
|
|
57346
57427
|
this.state.rule = {
|
|
57347
57428
|
...this.props.rule,
|
|
57348
|
-
ranges: this.props.rule.ranges.map((range) => this.env.model.getters.getRangeString(range,
|
|
57429
|
+
ranges: this.props.rule.ranges.map((range) => this.env.model.getters.getRangeString(range, this.editingSheetId)),
|
|
57349
57430
|
};
|
|
57350
57431
|
this.state.rule.criterion.type = this.props.rule.criterion.type;
|
|
57351
57432
|
}
|
|
@@ -57379,7 +57460,6 @@ class DataValidationEditor extends owl.Component {
|
|
|
57379
57460
|
const locale = this.env.model.getters.getLocale();
|
|
57380
57461
|
const criterion = rule.criterion;
|
|
57381
57462
|
const criterionEvaluator = criterionEvaluatorRegistry.get(criterion.type);
|
|
57382
|
-
const sheetId = this.env.model.getters.getActiveSheetId();
|
|
57383
57463
|
const values = criterion.values
|
|
57384
57464
|
.slice(0, criterionEvaluator.numberOfValues(criterion))
|
|
57385
57465
|
.map((value) => value?.trim())
|
|
@@ -57387,8 +57467,8 @@ class DataValidationEditor extends owl.Component {
|
|
|
57387
57467
|
.map((value) => canonicalizeContent(value, locale));
|
|
57388
57468
|
rule.criterion = { ...criterion, values };
|
|
57389
57469
|
return {
|
|
57390
|
-
sheetId,
|
|
57391
|
-
ranges: this.state.rule.ranges.map((xc) => this.env.model.getters.getRangeDataFromXc(
|
|
57470
|
+
sheetId: this.editingSheetId,
|
|
57471
|
+
ranges: this.state.rule.ranges.map((xc) => this.env.model.getters.getRangeDataFromXc(this.editingSheetId, xc)),
|
|
57392
57472
|
rule,
|
|
57393
57473
|
};
|
|
57394
57474
|
}
|
|
@@ -57915,6 +57995,7 @@ css /* scss */ `
|
|
|
57915
57995
|
.o-button {
|
|
57916
57996
|
height: 19px;
|
|
57917
57997
|
width: 19px;
|
|
57998
|
+
box-sizing: content-box;
|
|
57918
57999
|
.o-icon {
|
|
57919
58000
|
height: 14px;
|
|
57920
58001
|
width: 14px;
|
|
@@ -58660,7 +58741,7 @@ class PivotMeasureEditor extends owl.Component {
|
|
|
58660
58741
|
return undefined;
|
|
58661
58742
|
}
|
|
58662
58743
|
get isCalculatedMeasureInvalid() {
|
|
58663
|
-
return
|
|
58744
|
+
return compile(this.props.measure.computedBy?.formula ?? "").isBadExpression;
|
|
58664
58745
|
}
|
|
58665
58746
|
}
|
|
58666
58747
|
|
|
@@ -61538,16 +61619,16 @@ class ClickableCellSortIcon extends owl.Component {
|
|
|
61538
61619
|
}
|
|
61539
61620
|
}
|
|
61540
61621
|
|
|
61541
|
-
class
|
|
61542
|
-
static template = "o-spreadsheet-
|
|
61622
|
+
class FullScreenFigure extends owl.Component {
|
|
61623
|
+
static template = "o-spreadsheet-FullScreenFigure";
|
|
61543
61624
|
static props = {};
|
|
61544
|
-
static components = {
|
|
61545
|
-
|
|
61546
|
-
ref = owl.useRef("
|
|
61625
|
+
static components = { ChartFigure };
|
|
61626
|
+
fullScreenFigureStore;
|
|
61627
|
+
ref = owl.useRef("fullScreenFigure");
|
|
61547
61628
|
spreadsheetRect = useSpreadsheetRect();
|
|
61548
61629
|
figureRegistry = figureRegistry;
|
|
61549
61630
|
setup() {
|
|
61550
|
-
this.
|
|
61631
|
+
this.fullScreenFigureStore = useStore(FullScreenFigureStore);
|
|
61551
61632
|
const animationStore = useStore(ChartAnimationStore);
|
|
61552
61633
|
let lastFigureId = undefined;
|
|
61553
61634
|
owl.onWillUpdateProps(() => {
|
|
@@ -61559,7 +61640,7 @@ class FullScreenChart extends owl.Component {
|
|
|
61559
61640
|
owl.useEffect((el) => el?.focus(), () => [this.ref.el]);
|
|
61560
61641
|
}
|
|
61561
61642
|
get figureUI() {
|
|
61562
|
-
return this.
|
|
61643
|
+
return this.fullScreenFigureStore.fullScreenFigure;
|
|
61563
61644
|
}
|
|
61564
61645
|
get chartId() {
|
|
61565
61646
|
if (!this.figureUI)
|
|
@@ -61568,7 +61649,7 @@ class FullScreenChart extends owl.Component {
|
|
|
61568
61649
|
}
|
|
61569
61650
|
exitFullScreen() {
|
|
61570
61651
|
if (this.figureUI) {
|
|
61571
|
-
this.
|
|
61652
|
+
this.fullScreenFigureStore.toggleFullScreenFigure(this.figureUI.id);
|
|
61572
61653
|
}
|
|
61573
61654
|
}
|
|
61574
61655
|
onKeyDown(ev) {
|
|
@@ -61576,15 +61657,10 @@ class FullScreenChart extends owl.Component {
|
|
|
61576
61657
|
this.exitFullScreen();
|
|
61577
61658
|
}
|
|
61578
61659
|
}
|
|
61579
|
-
get
|
|
61580
|
-
if (!this.
|
|
61660
|
+
get figureComponent() {
|
|
61661
|
+
if (!this.figureUI)
|
|
61581
61662
|
return undefined;
|
|
61582
|
-
|
|
61583
|
-
const component = chartComponentRegistry.get(type);
|
|
61584
|
-
if (!component) {
|
|
61585
|
-
throw new Error(`Component is not defined for type ${type}`);
|
|
61586
|
-
}
|
|
61587
|
-
return component;
|
|
61663
|
+
return figureRegistry.get(this.figureUI.tag).Component;
|
|
61588
61664
|
}
|
|
61589
61665
|
}
|
|
61590
61666
|
|
|
@@ -64324,11 +64400,11 @@ class HeaderSizePlugin extends CorePlugin {
|
|
|
64324
64400
|
break;
|
|
64325
64401
|
}
|
|
64326
64402
|
case "ADD_COLUMNS_ROWS": {
|
|
64327
|
-
const sizes =
|
|
64403
|
+
const sizes = this.sizes[cmd.sheetId][cmd.dimension];
|
|
64328
64404
|
const addIndex = getAddHeaderStartIndex(cmd.position, cmd.base);
|
|
64329
64405
|
const baseSize = sizes[cmd.base];
|
|
64330
|
-
sizes
|
|
64331
|
-
this.history.update("sizes", cmd.sheetId, cmd.dimension,
|
|
64406
|
+
const newSizes = insertItemsAtIndex(sizes, Array(cmd.quantity).fill(baseSize), addIndex);
|
|
64407
|
+
this.history.update("sizes", cmd.sheetId, cmd.dimension, newSizes);
|
|
64332
64408
|
break;
|
|
64333
64409
|
}
|
|
64334
64410
|
case "RESIZE_COLUMNS_ROWS":
|
|
@@ -64479,9 +64555,8 @@ class HeaderVisibilityPlugin extends CorePlugin {
|
|
|
64479
64555
|
break;
|
|
64480
64556
|
}
|
|
64481
64557
|
case "ADD_COLUMNS_ROWS": {
|
|
64482
|
-
const hiddenHeaders = [...this.hiddenHeaders[cmd.sheetId][cmd.dimension]];
|
|
64483
64558
|
const addIndex = getAddHeaderStartIndex(cmd.position, cmd.base);
|
|
64484
|
-
hiddenHeaders.
|
|
64559
|
+
const hiddenHeaders = insertItemsAtIndex([...this.hiddenHeaders[cmd.sheetId][cmd.dimension]], Array(cmd.quantity).fill(false), addIndex);
|
|
64485
64560
|
this.history.update("hiddenHeaders", cmd.sheetId, cmd.dimension, hiddenHeaders);
|
|
64486
64561
|
break;
|
|
64487
64562
|
}
|
|
@@ -68665,12 +68740,12 @@ class SpreadsheetRTree {
|
|
|
68665
68740
|
this.rTrees[sheetId].remove(item, this.rtreeItemComparer);
|
|
68666
68741
|
}
|
|
68667
68742
|
rtreeItemComparer(left, right) {
|
|
68668
|
-
return (left.
|
|
68669
|
-
left.boundingBox.sheetId === right.boundingBox.sheetId &&
|
|
68743
|
+
return (left.boundingBox.sheetId === right.boundingBox.sheetId &&
|
|
68670
68744
|
left.boundingBox?.zone.left === right.boundingBox.zone.left &&
|
|
68671
68745
|
left.boundingBox?.zone.top === right.boundingBox.zone.top &&
|
|
68672
68746
|
left.boundingBox?.zone.right === right.boundingBox.zone.right &&
|
|
68673
|
-
left.boundingBox?.zone.bottom === right.boundingBox.zone.bottom
|
|
68747
|
+
left.boundingBox?.zone.bottom === right.boundingBox.zone.bottom &&
|
|
68748
|
+
deepEquals(left.data, right.data));
|
|
68674
68749
|
}
|
|
68675
68750
|
}
|
|
68676
68751
|
/**
|
|
@@ -68743,7 +68818,7 @@ class FormulaDependencyGraph {
|
|
|
68743
68818
|
* in the correct order they should be evaluated.
|
|
68744
68819
|
* This is called a topological ordering (excluding cycles)
|
|
68745
68820
|
*/
|
|
68746
|
-
getCellsDependingOn(ranges) {
|
|
68821
|
+
getCellsDependingOn(ranges, ignore) {
|
|
68747
68822
|
const visited = this.createEmptyPositionSet();
|
|
68748
68823
|
const queue = Array.from(ranges).reverse();
|
|
68749
68824
|
while (queue.length > 0) {
|
|
@@ -68758,7 +68833,7 @@ class FormulaDependencyGraph {
|
|
|
68758
68833
|
const impactedPositions = this.rTree.search(range).map((dep) => dep.data);
|
|
68759
68834
|
const nextInQueue = {};
|
|
68760
68835
|
for (const position of impactedPositions) {
|
|
68761
|
-
if (!visited.has(position)) {
|
|
68836
|
+
if (!visited.has(position) && !ignore.has(position)) {
|
|
68762
68837
|
if (!nextInQueue[position.sheetId]) {
|
|
68763
68838
|
nextInQueue[position.sheetId] = [];
|
|
68764
68839
|
}
|
|
@@ -69316,7 +69391,7 @@ class Evaluator {
|
|
|
69316
69391
|
}
|
|
69317
69392
|
invalidatePositionsDependingOnSpread(sheetId, resultZone) {
|
|
69318
69393
|
// the result matrix is split in 2 zones to exclude the array formula position
|
|
69319
|
-
const invalidatedPositions = this.formulaDependencies().getCellsDependingOn(excludeTopLeft(resultZone).map((zone) => ({ sheetId, zone })));
|
|
69394
|
+
const invalidatedPositions = this.formulaDependencies().getCellsDependingOn(excludeTopLeft(resultZone).map((zone) => ({ sheetId, zone })), this.nextPositionsToUpdate);
|
|
69320
69395
|
invalidatedPositions.delete({ sheetId, col: resultZone.left, row: resultZone.top });
|
|
69321
69396
|
this.nextPositionsToUpdate.addMany(invalidatedPositions);
|
|
69322
69397
|
}
|
|
@@ -69434,7 +69509,7 @@ class Evaluator {
|
|
|
69434
69509
|
for (const sheetId in zonesBySheetIds) {
|
|
69435
69510
|
ranges.push(...zonesBySheetIds[sheetId].map((zone) => ({ sheetId, zone })));
|
|
69436
69511
|
}
|
|
69437
|
-
return this.formulaDependencies().getCellsDependingOn(ranges);
|
|
69512
|
+
return this.formulaDependencies().getCellsDependingOn(ranges, this.nextPositionsToUpdate);
|
|
69438
69513
|
}
|
|
69439
69514
|
}
|
|
69440
69515
|
function forEachSpreadPositionInMatrix(nbColumns, nbRows, callback) {
|
|
@@ -70945,7 +71020,8 @@ class DynamicTablesPlugin extends CoreViewPlugin {
|
|
|
70945
71020
|
const topLeft = { col: unionZone.left, row: unionZone.top, sheetId };
|
|
70946
71021
|
const parentSpreadingCell = this.getters.getArrayFormulaSpreadingOn(topLeft);
|
|
70947
71022
|
if (!parentSpreadingCell) {
|
|
70948
|
-
|
|
71023
|
+
const evaluatedCell = this.getters.getEvaluatedCell(topLeft);
|
|
71024
|
+
return (evaluatedCell.value === CellErrorType.SpilledBlocked && !evaluatedCell.errorOriginPosition);
|
|
70949
71025
|
}
|
|
70950
71026
|
else if (deepEquals(parentSpreadingCell, topLeft) && getZoneArea(unionZone) === 1) {
|
|
70951
71027
|
return true;
|
|
@@ -82117,6 +82193,7 @@ class RibbonMenu extends owl.Component {
|
|
|
82117
82193
|
static components = { Menu };
|
|
82118
82194
|
rootItems = topbarMenuRegistry.getMenuItems();
|
|
82119
82195
|
menuRef = owl.useRef("menu");
|
|
82196
|
+
containerRef = owl.useRef("container");
|
|
82120
82197
|
state = owl.useState({
|
|
82121
82198
|
menuItems: this.rootItems,
|
|
82122
82199
|
title: _t("Menu Bar"),
|
|
@@ -82124,6 +82201,7 @@ class RibbonMenu extends owl.Component {
|
|
|
82124
82201
|
});
|
|
82125
82202
|
setup() {
|
|
82126
82203
|
owl.useExternalListener(window, "click", this.onExternalClick, { capture: true });
|
|
82204
|
+
owl.onMounted(this.updateShadows);
|
|
82127
82205
|
}
|
|
82128
82206
|
onExternalClick(ev) {
|
|
82129
82207
|
if (!this.menuRef.el?.contains(ev.target)) {
|
|
@@ -82136,6 +82214,7 @@ class RibbonMenu extends owl.Component {
|
|
|
82136
82214
|
this.state.parentState = { ...this.state };
|
|
82137
82215
|
this.state.menuItems = children;
|
|
82138
82216
|
this.state.title = menu.name(this.env);
|
|
82217
|
+
this.containerRef.el?.scrollTo({ top: 0 });
|
|
82139
82218
|
}
|
|
82140
82219
|
else {
|
|
82141
82220
|
this.state.menuItems = this.rootItems;
|
|
@@ -82157,6 +82236,19 @@ class RibbonMenu extends owl.Component {
|
|
|
82157
82236
|
height: `${this.props.height}px`,
|
|
82158
82237
|
});
|
|
82159
82238
|
}
|
|
82239
|
+
updateShadows() {
|
|
82240
|
+
if (!this.containerRef.el) {
|
|
82241
|
+
return;
|
|
82242
|
+
}
|
|
82243
|
+
this.containerRef.el.classList.remove("scroll-top", "scroll-bottom");
|
|
82244
|
+
const maxScroll = this.containerRef.el.scrollHeight - this.containerRef.el.clientHeight || 0;
|
|
82245
|
+
if (this.containerRef.el.scrollTop < maxScroll - 1) {
|
|
82246
|
+
this.containerRef.el.classList.add("scroll-bottom");
|
|
82247
|
+
}
|
|
82248
|
+
if (this.containerRef.el.scrollTop > 0) {
|
|
82249
|
+
this.containerRef.el.classList.add("scroll-top");
|
|
82250
|
+
}
|
|
82251
|
+
}
|
|
82160
82252
|
onClickBack() {
|
|
82161
82253
|
if (!this.state.parentState) {
|
|
82162
82254
|
this.props.onClose();
|
|
@@ -82165,6 +82257,7 @@ class RibbonMenu extends owl.Component {
|
|
|
82165
82257
|
this.state.menuItems = this.state.parentState.menuItems;
|
|
82166
82258
|
this.state.title = this.state.parentState.title;
|
|
82167
82259
|
this.state.parentState = this.state.parentState.parentState;
|
|
82260
|
+
this.containerRef.el?.scrollTo({ top: 0 });
|
|
82168
82261
|
}
|
|
82169
82262
|
get backTitle() {
|
|
82170
82263
|
return this.state.parentState ? _t("Go to previous menu") : _t("Close menu bar");
|
|
@@ -82220,6 +82313,11 @@ class SmallBottomBar extends owl.Component {
|
|
|
82220
82313
|
? this.composerFocusStore.focusMode
|
|
82221
82314
|
: "inactive";
|
|
82222
82315
|
}
|
|
82316
|
+
get showFxIcon() {
|
|
82317
|
+
return (this.focus === "inactive" &&
|
|
82318
|
+
!this.composerStore.currentContent &&
|
|
82319
|
+
!this.composerStore.placeholder);
|
|
82320
|
+
}
|
|
82223
82321
|
get rect() {
|
|
82224
82322
|
return this.composerRef.el
|
|
82225
82323
|
? getBoundingRectAsPOJO(this.composerRef.el)
|
|
@@ -82235,8 +82333,9 @@ class SmallBottomBar extends owl.Component {
|
|
|
82235
82333
|
},
|
|
82236
82334
|
focus: this.focus,
|
|
82237
82335
|
composerStore: this.composerStore,
|
|
82238
|
-
onComposerContentFocused: () => this.composerFocusStore.focusComposer(this.composerInterface, {
|
|
82336
|
+
onComposerContentFocused: (selection) => this.composerFocusStore.focusComposer(this.composerInterface, {
|
|
82239
82337
|
focusMode: "contentFocus",
|
|
82338
|
+
selection,
|
|
82240
82339
|
}),
|
|
82241
82340
|
isDefaultFocus: false,
|
|
82242
82341
|
inputStyle: cssPropertiesToCss({
|
|
@@ -82244,6 +82343,7 @@ class SmallBottomBar extends owl.Component {
|
|
|
82244
82343
|
"max-height": `130px`,
|
|
82245
82344
|
}),
|
|
82246
82345
|
showAssistant: !isIOS(), // Hide assistant on iOS as it breaks visually
|
|
82346
|
+
placeholder: this.composerStore.placeholder,
|
|
82247
82347
|
};
|
|
82248
82348
|
}
|
|
82249
82349
|
get symbols() {
|
|
@@ -82262,12 +82362,6 @@ class SmallBottomBar extends owl.Component {
|
|
|
82262
82362
|
}
|
|
82263
82363
|
|
|
82264
82364
|
const COMPOSER_MAX_HEIGHT = 300;
|
|
82265
|
-
/* svg free of use from https://uxwing.com/formula-fx-icon/ */
|
|
82266
|
-
const FX_SVG = /*xml*/ `
|
|
82267
|
-
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 121.8 122.9' width='16' height='16' focusable='false'>
|
|
82268
|
-
<path d='m28 34-4 5v2h10l-6 40c-4 22-6 28-7 30-2 2-3 3-5 3-3 0-7-2-9-4H4c-2 2-4 4-4 7s4 6 8 6 9-2 15-8c8-7 13-17 18-39l7-35 13-1 3-6H49c4-23 7-27 11-27 2 0 5 2 8 6h4c1-1 4-4 4-7 0-2-3-6-9-6-5 0-13 4-20 10-6 7-9 14-11 24h-8zm41 16c4-5 7-7 8-7s2 1 5 9l3 12c-7 11-12 17-16 17l-3-1-2-1c-3 0-6 3-6 7s3 7 7 7c6 0 12-6 22-23l3 10c3 9 6 13 10 13 5 0 11-4 18-15l-3-4c-4 6-7 8-8 8-2 0-4-3-6-10l-5-15 8-10 6-4 3 1 3 2c2 0 6-3 6-7s-2-7-6-7c-6 0-11 5-21 20l-2-6c-3-9-5-14-9-14-5 0-12 6-18 15l3 3z' fill='#BDBDBD'/>
|
|
82269
|
-
</svg>
|
|
82270
|
-
`;
|
|
82271
82365
|
css /* scss */ `
|
|
82272
82366
|
.o-topbar-composer-container {
|
|
82273
82367
|
height: ${DESKTOP_TOPBAR_TOOLBAR_HEIGHT}px;
|
|
@@ -82279,14 +82373,6 @@ css /* scss */ `
|
|
|
82279
82373
|
margin-bottom: -1px;
|
|
82280
82374
|
border: 1px solid;
|
|
82281
82375
|
font-family: ${DEFAULT_FONT};
|
|
82282
|
-
|
|
82283
|
-
/* In readonly we always show the fx icon if the composer is empty, not matter the focus */
|
|
82284
|
-
.o-composer:empty:not(:focus):not(.active)::before,
|
|
82285
|
-
&.o-topbar-composer-readonly .o-composer:empty::before {
|
|
82286
|
-
content: url("data:image/svg+xml,${encodeURIComponent(FX_SVG)}");
|
|
82287
|
-
position: relative;
|
|
82288
|
-
top: 20%;
|
|
82289
|
-
}
|
|
82290
82376
|
}
|
|
82291
82377
|
|
|
82292
82378
|
.user-select-text {
|
|
@@ -82319,6 +82405,11 @@ class TopBarComposer extends owl.Component {
|
|
|
82319
82405
|
? this.composerFocusStore.focusMode
|
|
82320
82406
|
: "inactive";
|
|
82321
82407
|
}
|
|
82408
|
+
get showFxIcon() {
|
|
82409
|
+
return (this.focus === "inactive" &&
|
|
82410
|
+
!this.composerStore.currentContent &&
|
|
82411
|
+
!this.composerStore.placeholder);
|
|
82412
|
+
}
|
|
82322
82413
|
get composerStyle() {
|
|
82323
82414
|
const style = {
|
|
82324
82415
|
padding: "5px 0px 5px 8px",
|
|
@@ -83670,7 +83761,7 @@ class Spreadsheet extends owl.Component {
|
|
|
83670
83761
|
SidePanels,
|
|
83671
83762
|
SpreadsheetDashboard,
|
|
83672
83763
|
HeaderGroupContainer,
|
|
83673
|
-
|
|
83764
|
+
FullScreenFigure,
|
|
83674
83765
|
};
|
|
83675
83766
|
sidePanel;
|
|
83676
83767
|
spreadsheetRef = owl.useRef("spreadsheet");
|
|
@@ -88394,6 +88485,7 @@ const components = {
|
|
|
88394
88485
|
Grid,
|
|
88395
88486
|
GridOverlay,
|
|
88396
88487
|
ScorecardChart,
|
|
88488
|
+
GaugeChartComponent,
|
|
88397
88489
|
LineConfigPanel,
|
|
88398
88490
|
BarConfigPanel,
|
|
88399
88491
|
PieChartDesignPanel,
|
|
@@ -88432,7 +88524,7 @@ const components = {
|
|
|
88432
88524
|
RadioSelection,
|
|
88433
88525
|
GeoChartRegionSelectSection,
|
|
88434
88526
|
ChartDashboardMenu,
|
|
88435
|
-
|
|
88527
|
+
FullScreenFigure,
|
|
88436
88528
|
};
|
|
88437
88529
|
const hooks = {
|
|
88438
88530
|
useDragAndDropListItems,
|
|
@@ -88532,6 +88624,6 @@ exports.tokenColors = tokenColors;
|
|
|
88532
88624
|
exports.tokenize = tokenize;
|
|
88533
88625
|
|
|
88534
88626
|
|
|
88535
|
-
__info__.version = "19.0.
|
|
88536
|
-
__info__.date = "2025-
|
|
88537
|
-
__info__.hash = "
|
|
88627
|
+
__info__.version = "19.0.6";
|
|
88628
|
+
__info__.date = "2025-10-16T06:39:36.282Z";
|
|
88629
|
+
__info__.hash = "0d4315a";
|