[IMP] spreadsheet: update o_spreadsheet to latest version
### Contains the following commits: https://github.com/odoo/o-spreadsheet/commit/59f25bb4 [REL] 16.3.0-alpha.2 https://github.com/odoo/o-spreadsheet/commit/d0189bd0 [FIX] config: bump owl version https://github.com/odoo/o-spreadsheet/commit/115e1d2b [FIX] tests: fix component tests and their organization https://github.com/odoo/o-spreadsheet/commit/e42786ad [FIX] Top_bar: Opening a menu should close the composer Task: 3076218 https://github.com/odoo/o-spreadsheet/commit/45f4c5c3 [FIX] range: show ref error after removing col/row https://github.com/odoo/o-spreadsheet/commit/c55d601d [IMP] figure: add snap to figure drag & drop https://github.com/odoo/o-spreadsheet/commit/7cbde524 [FIX] side panel: onSidePanelClose prop never call on X symbol click https://github.com/odoo/o-spreadsheet/commit/308e5d6c [IMP] figure_chart: Double click on chart open the sidepanel https://github.com/odoo/o-spreadsheet/commit/9781b7e3 [FIX] composer: fixed composer placeholder issues closes odoo/odoo#116338 Signed-off-by: Rémi Rahir (rar) <rar@odoo.com>
This commit is contained in:
@@ -225,7 +225,6 @@
|
||||
const MENU_SEPARATOR_BORDER_WIDTH = 1;
|
||||
const MENU_SEPARATOR_PADDING = 5;
|
||||
const MENU_SEPARATOR_HEIGHT = MENU_SEPARATOR_BORDER_WIDTH + 2 * MENU_SEPARATOR_PADDING;
|
||||
const FIGURE_BORDER_SIZE = 1;
|
||||
// Fonts
|
||||
const DEFAULT_FONT_WEIGHT = "400";
|
||||
const DEFAULT_FONT_SIZE = 10;
|
||||
@@ -245,6 +244,8 @@
|
||||
// Figure
|
||||
const DEFAULT_FIGURE_HEIGHT = 335;
|
||||
const DEFAULT_FIGURE_WIDTH = 536;
|
||||
const FIGURE_BORDER_WIDTH = 1;
|
||||
const MIN_FIG_SIZE = 80;
|
||||
// Chart
|
||||
const MAX_CHAR_LABEL = 20;
|
||||
const FIGURE_ID_SPLITTER = "??";
|
||||
@@ -255,7 +256,6 @@
|
||||
const DEFAULT_SCORECARD_BASELINE_COLOR_UP = "#00A04A";
|
||||
const DEFAULT_SCORECARD_BASELINE_COLOR_DOWN = "#DC6965";
|
||||
const LINE_FILL_TRANSPARENCY = 0.4;
|
||||
const MIN_FIG_SIZE = 80;
|
||||
// session
|
||||
const DEBOUNCE_TIME = 200;
|
||||
const MESSAGE_VERSION = 1;
|
||||
@@ -277,7 +277,8 @@
|
||||
ComponentsImportance[ComponentsImportance["TopBarComposer"] = 21] = "TopBarComposer";
|
||||
ComponentsImportance[ComponentsImportance["IconPicker"] = 25] = "IconPicker";
|
||||
ComponentsImportance[ComponentsImportance["Popover"] = 30] = "Popover";
|
||||
ComponentsImportance[ComponentsImportance["ChartAnchor"] = 1000] = "ChartAnchor";
|
||||
ComponentsImportance[ComponentsImportance["FigureAnchor"] = 1000] = "FigureAnchor";
|
||||
ComponentsImportance[ComponentsImportance["FigureSnapLine"] = 1001] = "FigureSnapLine";
|
||||
})(ComponentsImportance || (ComponentsImportance = {}));
|
||||
const DEFAULT_SHEETVIEW_SIZE = 1000;
|
||||
const MAXIMAL_FREEZABLE_RATIO = 0.85;
|
||||
@@ -3052,6 +3053,9 @@
|
||||
}
|
||||
return attributes;
|
||||
}
|
||||
/**
|
||||
* Transform CSS properties into a CSS string.
|
||||
*/
|
||||
function cssPropertiesToCss(attributes) {
|
||||
const str = Object.entries(attributes)
|
||||
.filter(([attName, attValue]) => attValue !== undefined)
|
||||
@@ -5072,6 +5076,10 @@
|
||||
function rectIntersection(rect1, rect2) {
|
||||
return zoneToRect(intersection(rectToZone(rect1), rectToZone(rect2)));
|
||||
}
|
||||
/** Compute the union of the rectangles, ie. the smallest rectangle that contain them all */
|
||||
function rectUnion(...rects) {
|
||||
return zoneToRect(union(...rects.map(rectToZone)));
|
||||
}
|
||||
function rectToZone(rect) {
|
||||
return {
|
||||
left: rect.x,
|
||||
@@ -7801,6 +7809,10 @@
|
||||
}
|
||||
`;
|
||||
class ChartFigure extends owl.Component {
|
||||
onDoubleClick() {
|
||||
this.env.model.dispatch("SELECT_FIGURE", { id: this.props.figure.id });
|
||||
this.env.openSidePanel("ChartPanel");
|
||||
}
|
||||
get chartType() {
|
||||
return this.env.model.getters.getChartType(this.props.figure.id);
|
||||
}
|
||||
@@ -18838,7 +18850,7 @@
|
||||
box-sizing: content-box;
|
||||
|
||||
.o-fig-anchor {
|
||||
z-index: ${ComponentsImportance.ChartAnchor};
|
||||
z-index: ${ComponentsImportance.FigureAnchor};
|
||||
position: absolute;
|
||||
width: ${ANCHOR_SIZE}px;
|
||||
height: ${ANCHOR_SIZE}px;
|
||||
@@ -19705,13 +19717,6 @@
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.o-topbar-composer .o-composer:empty:not(:focus)::before {
|
||||
/* svg free of use from https://uxwing.com/formula-fx-icon/ */
|
||||
content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 121.8 122.9' width='16' height='16' focusable='false'%3E%3Cpath 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='%23BDBDBD' /%3E%3C/svg%3E");
|
||||
position: relative;
|
||||
top: 20%;
|
||||
}
|
||||
`;
|
||||
class Composer extends owl.Component {
|
||||
constructor() {
|
||||
@@ -20439,6 +20444,266 @@
|
||||
gridPosition: { type: Object, optional: true },
|
||||
};
|
||||
|
||||
/**
|
||||
* Transform a figure with coordinates from the model, to coordinates as they are shown on the screen,
|
||||
* taking into account the scroll position of the active sheet and the frozen panes.
|
||||
*/
|
||||
function internalFigureToScreen(getters, fig) {
|
||||
return { ...fig, ...internalToScreenCoordinates(getters, { x: fig.x, y: fig.y }) };
|
||||
}
|
||||
/**
|
||||
* Transform a figure with coordinates as they are shown on the screen, to coordinates as they are in the model,
|
||||
* taking into account the scroll position of the active sheet and the frozen panes.
|
||||
*
|
||||
* Note that this isn't exactly the reverse operation as internalFigureToScreen, because the figure will always be on top
|
||||
* of the frozen panes.
|
||||
*/
|
||||
function screenFigureToInternal(getters, fig) {
|
||||
return { ...fig, ...screenCoordinatesToInternal(getters, { x: fig.x, y: fig.y }) };
|
||||
}
|
||||
function internalToScreenCoordinates(getters, { x, y }) {
|
||||
const { x: viewportX, y: viewportY } = getters.getMainViewportCoordinates();
|
||||
const { scrollX, scrollY } = getters.getActiveSheetScrollInfo();
|
||||
x = x < viewportX ? x : x - scrollX;
|
||||
y = y < viewportY ? y : y - scrollY;
|
||||
return { x, y };
|
||||
}
|
||||
function screenCoordinatesToInternal(getters, { x, y }) {
|
||||
const { x: viewportX, y: viewportY } = getters.getMainViewportCoordinates();
|
||||
const { scrollX, scrollY } = getters.getActiveSheetScrollInfo();
|
||||
x = viewportX && x < viewportX ? x : x + scrollX;
|
||||
y = viewportY && y < viewportY ? y : y + scrollY;
|
||||
return { x, y };
|
||||
}
|
||||
|
||||
function dragFigureForMove({ x: mouseX, y: mouseY }, { x: mouseInitialX, y: mouseInitialY }, initialFigure, { x: viewportX, y: viewportY }, { scrollX, scrollY }) {
|
||||
const minX = viewportX ? 0 : -scrollX;
|
||||
const minY = viewportY ? 0 : -scrollY;
|
||||
let deltaX = mouseX - mouseInitialX;
|
||||
const newX = Math.max(initialFigure.x + deltaX, minX);
|
||||
let deltaY = mouseY - mouseInitialY;
|
||||
const newY = Math.max(initialFigure.y + deltaY, minY);
|
||||
return { ...initialFigure, x: newX, y: newY };
|
||||
}
|
||||
function dragFigureForResize(initialFigure, dirX, dirY, { x: mouseX, y: mouseY }, { x: mouseInitialX, y: mouseInitialY }, keepRatio, minFigSize) {
|
||||
let { x, y, width, height } = initialFigure;
|
||||
if (keepRatio && dirX != 0 && dirY != 0) {
|
||||
const deltaX = Math.min(dirX * (mouseInitialX - mouseX), initialFigure.width - minFigSize);
|
||||
const deltaY = Math.min(dirY * (mouseInitialY - mouseY), initialFigure.height - minFigSize);
|
||||
const fraction = Math.min(deltaX / initialFigure.width, deltaY / initialFigure.height);
|
||||
width = initialFigure.width * (1 - fraction);
|
||||
height = initialFigure.height * (1 - fraction);
|
||||
if (dirX < 0) {
|
||||
x = initialFigure.x + initialFigure.width * fraction;
|
||||
}
|
||||
if (dirY < 0) {
|
||||
y = initialFigure.y + initialFigure.height * fraction;
|
||||
}
|
||||
}
|
||||
else {
|
||||
const deltaX = Math.max(dirX * (mouseX - mouseInitialX), minFigSize - initialFigure.width);
|
||||
const deltaY = Math.max(dirY * (mouseY - mouseInitialY), minFigSize - initialFigure.height);
|
||||
width = initialFigure.width + deltaX;
|
||||
height = initialFigure.height + deltaY;
|
||||
if (dirX < 0) {
|
||||
x = initialFigure.x - deltaX;
|
||||
}
|
||||
if (dirY < 0) {
|
||||
y = initialFigure.y - deltaY;
|
||||
}
|
||||
}
|
||||
return { ...initialFigure, x, y, width, height };
|
||||
}
|
||||
|
||||
const SNAP_MARGIN = 5;
|
||||
/**
|
||||
* Try to snap the given figure to other figures when moving the figure, and return the snapped
|
||||
* figure and the possible snap lines, if any were found
|
||||
*/
|
||||
function snapForMove(getters, figureToSnap, otherFigures) {
|
||||
const snappedFigure = { ...figureToSnap };
|
||||
const verticalSnapLine = getSnapLine(getters, snappedFigure, ["hCenter", "right", "left"], otherFigures, ["hCenter", "right", "left"]);
|
||||
const horizontalSnapLine = getSnapLine(getters, snappedFigure, ["vCenter", "bottom", "top"], otherFigures, ["vCenter", "bottom", "top"]);
|
||||
const { y: viewportY, x: viewportX } = getters.getMainViewportCoordinates();
|
||||
const { scrollY, scrollX } = getters.getActiveSheetScrollInfo();
|
||||
// If the snap cause the figure to change pane, we need to also apply the scroll as an offset
|
||||
if (horizontalSnapLine) {
|
||||
snappedFigure.y -= horizontalSnapLine.snapOffset;
|
||||
const isBaseFigFrozenY = figureToSnap.y < viewportY;
|
||||
const isSnappedFrozenY = snappedFigure.y < viewportY;
|
||||
if (isBaseFigFrozenY && !isSnappedFrozenY)
|
||||
snappedFigure.y += scrollY;
|
||||
else if (!isBaseFigFrozenY && isSnappedFrozenY)
|
||||
snappedFigure.y -= scrollY;
|
||||
}
|
||||
if (verticalSnapLine) {
|
||||
snappedFigure.x -= verticalSnapLine.snapOffset;
|
||||
const isBaseFigFrozenX = figureToSnap.x < viewportX;
|
||||
const isSnappedFrozenX = snappedFigure.x < viewportX;
|
||||
if (isBaseFigFrozenX && !isSnappedFrozenX)
|
||||
snappedFigure.x += scrollX;
|
||||
else if (!isBaseFigFrozenX && isSnappedFrozenX)
|
||||
snappedFigure.x -= scrollX;
|
||||
}
|
||||
return { snappedFigure, verticalSnapLine, horizontalSnapLine };
|
||||
}
|
||||
/**
|
||||
* Try to snap the given figure to the other figures when resizing the figure, and return the snapped
|
||||
* figure and the possible snap lines, if any were found
|
||||
*/
|
||||
function snapForResize(getters, resizeDirX, resizeDirY, figureToSnap, otherFigures) {
|
||||
const snappedFigure = { ...figureToSnap };
|
||||
// Vertical snap line
|
||||
const verticalSnapLine = getSnapLine(getters, snappedFigure, [resizeDirX === -1 ? "left" : "right"], otherFigures, ["right", "left"]);
|
||||
if (verticalSnapLine) {
|
||||
if (resizeDirX === 1) {
|
||||
snappedFigure.width -= verticalSnapLine.snapOffset;
|
||||
}
|
||||
else if (resizeDirX === -1) {
|
||||
snappedFigure.x -= verticalSnapLine.snapOffset;
|
||||
snappedFigure.width += verticalSnapLine.snapOffset;
|
||||
}
|
||||
}
|
||||
// Horizontal snap line
|
||||
const horizontalSnapLine = getSnapLine(getters, snappedFigure, [resizeDirY === -1 ? "top" : "bottom"], otherFigures, ["bottom", "top"]);
|
||||
if (horizontalSnapLine) {
|
||||
if (resizeDirY === 1) {
|
||||
snappedFigure.height -= horizontalSnapLine.snapOffset;
|
||||
}
|
||||
else if (resizeDirY === -1) {
|
||||
snappedFigure.y -= horizontalSnapLine.snapOffset;
|
||||
snappedFigure.height += horizontalSnapLine.snapOffset;
|
||||
}
|
||||
}
|
||||
snappedFigure.x = Math.round(snappedFigure.x);
|
||||
snappedFigure.y = Math.round(snappedFigure.y);
|
||||
snappedFigure.height = Math.round(snappedFigure.height);
|
||||
snappedFigure.width = Math.round(snappedFigure.width);
|
||||
return { snappedFigure, verticalSnapLine, horizontalSnapLine };
|
||||
}
|
||||
/**
|
||||
* Get the position of snap axes for the given figure
|
||||
*
|
||||
* @param figure the figure
|
||||
* @param axesTypes the list of axis types to return the positions of
|
||||
*/
|
||||
function getVisibleAxes(getters, figure, axesTypes) {
|
||||
const axes = axesTypes.map((axisType) => getAxis(figure, axisType));
|
||||
return axes
|
||||
.filter((axis) => isAxisVisible(getters, figure, axis))
|
||||
.map((axis) => getAxisScreenPosition(getters, figure, axis));
|
||||
}
|
||||
/**
|
||||
* We need two positions for the figure axis :
|
||||
* - the position (core) of the axis in the figure. This is used to know whether or not the axis is
|
||||
* displayed, or is hidden by the scroll/the frozen panes
|
||||
* - the position in the screen, which is used to find snap matches. We cannot use the core position for this,
|
||||
* because figures partially in frozen panes aren't displayed at their actual coordinates
|
||||
*/
|
||||
function getAxisScreenPosition(getters, figure, figureAxis) {
|
||||
const screenFigure = internalFigureToScreen(getters, figure);
|
||||
return getAxis(screenFigure, figureAxis.axisType);
|
||||
}
|
||||
function isAxisVisible(getters, figure, axis) {
|
||||
const { x: mainViewportX, y: mainViewportY } = getters.getMainViewportCoordinates();
|
||||
const axisStartEndPositions = [];
|
||||
switch (axis.axisType) {
|
||||
case "top":
|
||||
case "bottom":
|
||||
case "vCenter":
|
||||
if (figure.y < mainViewportY)
|
||||
return true;
|
||||
axisStartEndPositions.push({ x: figure.x, y: axis.position });
|
||||
axisStartEndPositions.push({ x: figure.x + figure.width, y: axis.position });
|
||||
break;
|
||||
case "left":
|
||||
case "right":
|
||||
case "hCenter":
|
||||
if (figure.x < mainViewportX)
|
||||
return true;
|
||||
axisStartEndPositions.push({ x: axis.position, y: figure.y });
|
||||
axisStartEndPositions.push({ x: axis.position, y: figure.y + figure.height });
|
||||
break;
|
||||
}
|
||||
return axisStartEndPositions.some(getters.isPositionVisible);
|
||||
}
|
||||
/**
|
||||
* Get a snap line for the given figure, if the figure can snap to any other figure
|
||||
*
|
||||
* @param figureToSnap figure to get the snap line for
|
||||
* @param figAxesTypes figure axes of the given figure to be considered to find a snap line
|
||||
* @param otherFigures figures to match against the snapped figure to find a snap line
|
||||
* @param otherAxesTypes figure axes of the other figures to be considered to find a snap line
|
||||
*/
|
||||
function getSnapLine(getters, figureToSnap, figAxesTypes, otherFigures, otherAxesTypes) {
|
||||
const axesOfFigure = getVisibleAxes(getters, figureToSnap, figAxesTypes);
|
||||
let closestMatch = undefined;
|
||||
for (const otherFigure of otherFigures) {
|
||||
const axesOfOtherFig = getVisibleAxes(getters, otherFigure, otherAxesTypes);
|
||||
for (const axisOfFigure of axesOfFigure) {
|
||||
for (const axisOfOtherFig of axesOfOtherFig) {
|
||||
if (!canSnap(axisOfFigure.position, axisOfOtherFig.position))
|
||||
continue;
|
||||
const snapOffset = axisOfFigure.position - axisOfOtherFig.position;
|
||||
if (closestMatch && snapOffset === closestMatch.snapOffset) {
|
||||
closestMatch.matchedFigIds.push(otherFigure.id);
|
||||
}
|
||||
else if (!closestMatch || Math.abs(snapOffset) <= Math.abs(closestMatch.snapOffset)) {
|
||||
closestMatch = {
|
||||
matchedFigIds: [otherFigure.id],
|
||||
snapOffset,
|
||||
snappedAxisType: axisOfFigure.axisType,
|
||||
position: axisOfOtherFig.position,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return closestMatch;
|
||||
}
|
||||
/** Check if two axes are close enough to snap */
|
||||
function canSnap(axisPosition1, axisPosition2) {
|
||||
return Math.abs(axisPosition1 - axisPosition2) <= SNAP_MARGIN;
|
||||
}
|
||||
function getAxis(fig, axisType) {
|
||||
let position = 0;
|
||||
switch (axisType) {
|
||||
case "top":
|
||||
position = fig.y;
|
||||
break;
|
||||
case "bottom":
|
||||
position = fig.y + fig.height - FIGURE_BORDER_WIDTH;
|
||||
break;
|
||||
case "vCenter":
|
||||
position = fig.y + Math.floor(fig.height / 2) - FIGURE_BORDER_WIDTH;
|
||||
break;
|
||||
case "left":
|
||||
position = fig.x;
|
||||
break;
|
||||
case "right":
|
||||
position = fig.x + fig.width - FIGURE_BORDER_WIDTH;
|
||||
break;
|
||||
case "hCenter":
|
||||
position = fig.x + Math.floor(fig.width / 2) - FIGURE_BORDER_WIDTH;
|
||||
break;
|
||||
}
|
||||
return { position, axisType: axisType };
|
||||
}
|
||||
|
||||
css /*SCSS*/ `
|
||||
.o-figure-snap-line {
|
||||
position: relative;
|
||||
z-index: ${ComponentsImportance.FigureSnapLine};
|
||||
&.vertical {
|
||||
width: 0px;
|
||||
border-left: 1px dashed black;
|
||||
}
|
||||
&.horizontal {
|
||||
border-top: 1px dashed black;
|
||||
height: 0px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
/**
|
||||
* Each figure ⭐ is positioned inside a container `div` placed and sized
|
||||
* according to the split pane the figure is part of, or a separate container for the figure
|
||||
@@ -20503,11 +20768,9 @@
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.dnd = owl.useState({
|
||||
figId: undefined,
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
draggedFigure: undefined,
|
||||
horizontalSnap: undefined,
|
||||
verticalSnap: undefined,
|
||||
});
|
||||
}
|
||||
setup() {
|
||||
@@ -20523,9 +20786,11 @@
|
||||
});
|
||||
}
|
||||
getVisibleFigures() {
|
||||
var _a;
|
||||
const visibleFigures = this.env.model.getters.getVisibleFigures();
|
||||
if (this.dnd.figId && !visibleFigures.some((figure) => figure.id === this.dnd.figId)) {
|
||||
visibleFigures.push(this.env.model.getters.getFigure(this.env.model.getters.getActiveSheetId(), this.dnd.figId));
|
||||
if (this.dnd.draggedFigure &&
|
||||
!visibleFigures.some((figure) => { var _a; return figure.id === ((_a = this.dnd.draggedFigure) === null || _a === void 0 ? void 0 : _a.id); })) {
|
||||
visibleFigures.push(this.env.model.getters.getFigure(this.env.model.getters.getActiveSheetId(), (_a = this.dnd.draggedFigure) === null || _a === void 0 ? void 0 : _a.id));
|
||||
}
|
||||
return visibleFigures;
|
||||
}
|
||||
@@ -20548,7 +20813,7 @@
|
||||
});
|
||||
}
|
||||
}
|
||||
if (this.dnd.figId) {
|
||||
if (this.dnd.draggedFigure) {
|
||||
containers.push({
|
||||
type: "dnd",
|
||||
figures: [this.getDndFigure()],
|
||||
@@ -20559,18 +20824,24 @@
|
||||
return containers;
|
||||
}
|
||||
getContainerStyle(container) {
|
||||
return this.rectToCss(this.getContainerRect(container));
|
||||
}
|
||||
rectToCss(rect) {
|
||||
return cssPropertiesToCss({
|
||||
left: `${rect.x}px`,
|
||||
top: `${rect.y}px`,
|
||||
width: `${rect.width}px`,
|
||||
height: `${rect.height}px`,
|
||||
});
|
||||
}
|
||||
getContainerRect(container) {
|
||||
const { width: viewWidth, height: viewHeight } = this.env.model.getters.getMainViewportRect();
|
||||
const { x: viewportX, y: viewportY } = this.env.model.getters.getMainViewportCoordinates();
|
||||
const left = ["bottomRight", "topRight"].includes(container) ? viewportX : 0;
|
||||
const width = viewWidth - left;
|
||||
const top = ["bottomRight", "bottomLeft"].includes(container) ? viewportY : 0;
|
||||
const height = viewHeight - top;
|
||||
return cssPropertiesToCss({
|
||||
left: `${left}px`,
|
||||
top: `${top}px`,
|
||||
width: `${width}px`,
|
||||
height: `${height}px`,
|
||||
});
|
||||
const x = ["bottomRight", "topRight"].includes(container) ? viewportX : 0;
|
||||
const width = viewWidth - x;
|
||||
const y = ["bottomRight", "bottomLeft"].includes(container) ? viewportY : 0;
|
||||
const height = viewHeight - y;
|
||||
return { x, y, width, height };
|
||||
}
|
||||
getInverseViewportPositionStyle(container) {
|
||||
const { scrollX, scrollY } = this.env.model.getters.getActiveSheetScrollInfo();
|
||||
@@ -20583,8 +20854,9 @@
|
||||
});
|
||||
}
|
||||
getFigureContainer(figure) {
|
||||
var _a;
|
||||
const { x: viewportX, y: viewportY } = this.env.model.getters.getMainViewportCoordinates();
|
||||
if (figure.id === this.dnd.figId) {
|
||||
if (figure.id === ((_a = this.dnd.draggedFigure) === null || _a === void 0 ? void 0 : _a.id)) {
|
||||
return "dnd";
|
||||
}
|
||||
else if (figure.x < viewportX && figure.y < viewportY) {
|
||||
@@ -20610,133 +20882,137 @@
|
||||
return;
|
||||
}
|
||||
const sheetId = this.env.model.getters.getActiveSheetId();
|
||||
const mouseInitialX = ev.clientX;
|
||||
const mouseInitialY = ev.clientY;
|
||||
const { x: dndInitialX, y: dndInitialY } = this.internalToScreenCoordinates(figure);
|
||||
this.dnd.x = dndInitialX;
|
||||
this.dnd.y = dndInitialY;
|
||||
this.dnd.width = figure.width;
|
||||
this.dnd.height = figure.height;
|
||||
const initialMousePosition = { x: ev.clientX, y: ev.clientY };
|
||||
const { x, y } = internalFigureToScreen(this.env.model.getters, figure);
|
||||
const initialFig = { ...figure, x, y };
|
||||
const onMouseMove = (ev) => {
|
||||
const { x: viewportX, y: viewportY } = this.env.model.getters.getMainViewportCoordinates();
|
||||
const { scrollX, scrollY } = this.env.model.getters.getActiveSheetScrollInfo();
|
||||
const minX = viewportX ? 0 : -scrollX;
|
||||
const minY = viewportY ? 0 : -scrollY;
|
||||
this.dnd.figId = figure.id;
|
||||
const newX = ev.clientX;
|
||||
let deltaX = newX - mouseInitialX;
|
||||
this.dnd.x = Math.max(dndInitialX + deltaX, minX);
|
||||
const newY = ev.clientY;
|
||||
let deltaY = newY - mouseInitialY;
|
||||
this.dnd.y = Math.max(dndInitialY + deltaY, minY);
|
||||
const getters = this.env.model.getters;
|
||||
const currentMousePosition = { x: ev.clientX, y: ev.clientY };
|
||||
const draggedFigure = dragFigureForMove(currentMousePosition, initialMousePosition, initialFig, this.env.model.getters.getMainViewportCoordinates(), getters.getActiveSheetScrollInfo());
|
||||
const otherFigures = this.getOtherFigures(figure.id);
|
||||
const internalDragged = screenFigureToInternal(getters, draggedFigure);
|
||||
const snapResult = snapForMove(getters, internalDragged, otherFigures);
|
||||
this.dnd.draggedFigure = internalFigureToScreen(getters, snapResult.snappedFigure);
|
||||
this.dnd.horizontalSnap = this.getSnap(snapResult.horizontalSnapLine);
|
||||
this.dnd.verticalSnap = this.getSnap(snapResult.verticalSnapLine);
|
||||
};
|
||||
const onMouseUp = (ev) => {
|
||||
if (!this.dnd.figId) {
|
||||
if (!this.dnd.draggedFigure) {
|
||||
return;
|
||||
}
|
||||
let { x, y } = this.screenCoordinatesToInternal(this.dnd);
|
||||
this.dnd.figId = undefined;
|
||||
let { x, y } = screenFigureToInternal(this.env.model.getters, this.dnd.draggedFigure);
|
||||
this.dnd.draggedFigure = undefined;
|
||||
this.dnd.horizontalSnap = undefined;
|
||||
this.dnd.verticalSnap = undefined;
|
||||
this.env.model.dispatch("UPDATE_FIGURE", { sheetId, id: figure.id, x, y });
|
||||
};
|
||||
startDnd(onMouseMove, onMouseUp);
|
||||
}
|
||||
/**
|
||||
* Initialize the resize of a figure with mouse movements
|
||||
*
|
||||
* @param dirX X direction of the resize. -1 : resize from the left border of the figure, 0 : no resize in X, 1 :
|
||||
* resize from the right border of the figure
|
||||
* @param dirY Y direction of the resize. -1 : resize from the top border of the figure, 0 : no resize in Y, 1 :
|
||||
* resize from the bottom border of the figure
|
||||
* @param ev Mouse Event
|
||||
*/
|
||||
startResize(figure, dirX, dirY, ev) {
|
||||
ev.stopPropagation();
|
||||
const initialX = ev.clientX;
|
||||
const initialY = ev.clientY;
|
||||
const { x: dndInitialX, y: dndInitialY } = this.internalToScreenCoordinates(figure);
|
||||
this.dnd.x = dndInitialX;
|
||||
this.dnd.y = dndInitialY;
|
||||
this.dnd.width = figure.width;
|
||||
this.dnd.height = figure.height;
|
||||
const initialMousePosition = { x: ev.clientX, y: ev.clientY };
|
||||
const { x, y } = internalFigureToScreen(this.env.model.getters, figure);
|
||||
const initialFig = { ...figure, x, y };
|
||||
const keepRatio = figureRegistry.get(figure.tag).keepRatio || false;
|
||||
const minFigSize = figureRegistry.get(figure.tag).minFigSize || MIN_FIG_SIZE;
|
||||
let onMouseMove;
|
||||
if (keepRatio && dirX != 0 && dirY != 0) {
|
||||
onMouseMove = (ev) => {
|
||||
this.dnd.figId = figure.id;
|
||||
const deltaX = Math.min(dirX * (initialX - ev.clientX), figure.width - minFigSize);
|
||||
const deltaY = Math.min(dirY * (initialY - ev.clientY), figure.height - minFigSize);
|
||||
const fraction = Math.min(deltaX / figure.width, deltaY / figure.height);
|
||||
this.dnd.width = figure.width * (1 - fraction);
|
||||
this.dnd.height = figure.height * (1 - fraction);
|
||||
if (dirX < 0) {
|
||||
this.dnd.x = dndInitialX + figure.width * fraction;
|
||||
}
|
||||
if (dirY < 0) {
|
||||
this.dnd.y = dndInitialY + figure.height * fraction;
|
||||
}
|
||||
};
|
||||
}
|
||||
else {
|
||||
onMouseMove = (ev) => {
|
||||
this.dnd.figId = figure.id;
|
||||
const deltaX = Math.max(dirX * (ev.clientX - initialX), minFigSize - figure.width);
|
||||
const deltaY = Math.max(dirY * (ev.clientY - initialY), minFigSize - figure.height);
|
||||
this.dnd.width = figure.width + deltaX;
|
||||
this.dnd.height = figure.height + deltaY;
|
||||
if (dirX < 0) {
|
||||
this.dnd.x = dndInitialX - deltaX;
|
||||
}
|
||||
if (dirY < 0) {
|
||||
this.dnd.y = dndInitialY - deltaY;
|
||||
}
|
||||
};
|
||||
}
|
||||
const onMouseMove = (ev) => {
|
||||
const currentMousePosition = { x: ev.clientX, y: ev.clientY };
|
||||
const draggedFigure = dragFigureForResize(initialFig, dirX, dirY, currentMousePosition, initialMousePosition, keepRatio, minFigSize);
|
||||
const otherFigures = this.getOtherFigures(figure.id);
|
||||
const snapResult = snapForResize(this.env.model.getters, dirX, dirY, draggedFigure, otherFigures);
|
||||
this.dnd.draggedFigure = snapResult.snappedFigure;
|
||||
this.dnd.horizontalSnap = this.getSnap(snapResult.horizontalSnapLine);
|
||||
this.dnd.verticalSnap = this.getSnap(snapResult.verticalSnapLine);
|
||||
};
|
||||
const onMouseUp = (ev) => {
|
||||
if (!this.dnd.figId) {
|
||||
if (!this.dnd.draggedFigure) {
|
||||
return;
|
||||
}
|
||||
this.dnd.figId = undefined;
|
||||
let { x, y } = this.screenCoordinatesToInternal(this.dnd);
|
||||
let { x, y } = screenFigureToInternal(this.env.model.getters, this.dnd.draggedFigure);
|
||||
const update = { x, y };
|
||||
if (dirX) {
|
||||
update.width = this.dnd.width;
|
||||
update.width = this.dnd.draggedFigure.width;
|
||||
}
|
||||
if (dirY) {
|
||||
update.height = this.dnd.height;
|
||||
update.height = this.dnd.draggedFigure.height;
|
||||
}
|
||||
this.env.model.dispatch("UPDATE_FIGURE", {
|
||||
sheetId: this.env.model.getters.getActiveSheetId(),
|
||||
id: figure.id,
|
||||
...update,
|
||||
});
|
||||
this.dnd.draggedFigure = undefined;
|
||||
this.dnd.horizontalSnap = undefined;
|
||||
this.dnd.verticalSnap = undefined;
|
||||
};
|
||||
startDnd(onMouseMove, onMouseUp);
|
||||
}
|
||||
getOtherFigures(figId) {
|
||||
return this.getVisibleFigures().filter((f) => f.id !== figId);
|
||||
}
|
||||
getDndFigure() {
|
||||
const figure = this.getVisibleFigures().find((fig) => fig.id === this.dnd.figId);
|
||||
const figure = this.getVisibleFigures().find((fig) => { var _a; return fig.id === ((_a = this.dnd.draggedFigure) === null || _a === void 0 ? void 0 : _a.id); });
|
||||
if (!figure)
|
||||
throw new Error("Dnd figure not found");
|
||||
return {
|
||||
...figure,
|
||||
x: this.dnd.x,
|
||||
y: this.dnd.y,
|
||||
width: this.dnd.width,
|
||||
height: this.dnd.height,
|
||||
...this.dnd.draggedFigure,
|
||||
};
|
||||
}
|
||||
getFigureStyle(figure) {
|
||||
if (figure.id !== this.dnd.figId)
|
||||
var _a;
|
||||
if (figure.id !== ((_a = this.dnd.draggedFigure) === null || _a === void 0 ? void 0 : _a.id))
|
||||
return "";
|
||||
return cssPropertiesToCss({
|
||||
opacity: "0.9",
|
||||
cursor: "grabbing",
|
||||
});
|
||||
}
|
||||
internalToScreenCoordinates({ x, y }) {
|
||||
const { x: viewportX, y: viewportY } = this.env.model.getters.getMainViewportCoordinates();
|
||||
const { scrollX, scrollY } = this.env.model.getters.getActiveSheetScrollInfo();
|
||||
x = x < viewportX ? x : x - scrollX;
|
||||
y = y < viewportY ? y : y - scrollY;
|
||||
return { x, y };
|
||||
getSnap(snapLine) {
|
||||
if (!snapLine || !this.dnd.draggedFigure)
|
||||
return undefined;
|
||||
const figureVisibleRects = snapLine.matchedFigIds
|
||||
.map((id) => this.getVisibleFigures().find((fig) => fig.id === id))
|
||||
.filter(isDefined$1)
|
||||
.map((fig) => {
|
||||
const figOnSCreen = internalFigureToScreen(this.env.model.getters, fig);
|
||||
const container = this.getFigureContainer(fig);
|
||||
return rectIntersection(figOnSCreen, this.getContainerRect(container));
|
||||
})
|
||||
.filter(isDefined$1);
|
||||
const containerRect = rectUnion(this.dnd.draggedFigure, ...figureVisibleRects);
|
||||
return {
|
||||
line: snapLine,
|
||||
containerStyle: this.rectToCss(containerRect),
|
||||
lineStyle: this.getSnapLineStyle(snapLine, containerRect),
|
||||
};
|
||||
}
|
||||
screenCoordinatesToInternal({ x, y }) {
|
||||
const { x: viewportX, y: viewportY } = this.env.model.getters.getMainViewportCoordinates();
|
||||
const { scrollX, scrollY } = this.env.model.getters.getActiveSheetScrollInfo();
|
||||
x = viewportX && x < viewportX ? x : x + scrollX;
|
||||
y = viewportY && y < viewportY ? y : y + scrollY;
|
||||
return { x, y };
|
||||
getSnapLineStyle(snapLine, containerRect) {
|
||||
if (!snapLine)
|
||||
return "";
|
||||
if (["top", "vCenter", "bottom"].includes(snapLine.snappedAxisType)) {
|
||||
return cssPropertiesToCss({
|
||||
top: `${snapLine.position - containerRect.y}px`,
|
||||
left: `0px`,
|
||||
width: `100%`,
|
||||
});
|
||||
}
|
||||
else {
|
||||
return cssPropertiesToCss({
|
||||
top: `0px`,
|
||||
left: `${snapLine.position - containerRect.x}px`,
|
||||
height: `100%`,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
FiguresContainer.template = "o-spreadsheet-FiguresContainer";
|
||||
@@ -30242,6 +30518,7 @@
|
||||
}
|
||||
else if (range.zone[start] >= min && range.zone[end] <= max) {
|
||||
changeType = "REMOVE";
|
||||
newRange = range.clone({ ...this.getInvalidRange() });
|
||||
}
|
||||
else if (range.zone[start] <= max && range.zone[end] >= max) {
|
||||
const toRemove = max - range.zone[start] + 1;
|
||||
@@ -30307,8 +30584,10 @@
|
||||
return { changeType: "NONE" };
|
||||
}
|
||||
const invalidSheetName = this.getters.getSheetName(cmd.sheetId);
|
||||
const sheetId = "";
|
||||
range = range.clone({ sheetId, invalidSheetName });
|
||||
range = range.clone({
|
||||
...this.getInvalidRange(),
|
||||
invalidSheetName,
|
||||
});
|
||||
return { changeType: "REMOVE", range };
|
||||
}, cmd.sheetId);
|
||||
break;
|
||||
@@ -30568,6 +30847,15 @@
|
||||
}
|
||||
return str;
|
||||
}
|
||||
getInvalidRange() {
|
||||
return {
|
||||
parts: [],
|
||||
prefixSheet: false,
|
||||
zone: { left: -1, top: -1, right: -1, bottom: -1 },
|
||||
sheetId: "",
|
||||
invalidXc: INCORRECT_RANGE_STRING,
|
||||
};
|
||||
}
|
||||
}
|
||||
RangeAdapter.getters = [
|
||||
"getRangeString",
|
||||
@@ -33464,6 +33752,20 @@
|
||||
}
|
||||
return result;
|
||||
}
|
||||
isPositionVisible(position) {
|
||||
const { scrollX, scrollY } = this.getters.getActiveSheetScrollInfo();
|
||||
const { x: mainViewportX, y: mainViewportY } = this.getters.getMainViewportCoordinates();
|
||||
const { width, height } = this.getters.getSheetViewDimension();
|
||||
if (position.x >= mainViewportX &&
|
||||
(position.x < mainViewportX + scrollX || position.x > width + scrollX + mainViewportX)) {
|
||||
return false;
|
||||
}
|
||||
if (position.y >= mainViewportY &&
|
||||
(position.y < mainViewportY + scrollY || position.y > height + scrollY + mainViewportY)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
getFrozenSheetViewRatio(sheetId) {
|
||||
const { xSplit, ySplit } = this.getters.getPaneDivisions(sheetId);
|
||||
const offsetCorrectionX = this.getters.getColDimensions(sheetId, xSplit).start;
|
||||
@@ -33492,6 +33794,7 @@
|
||||
"getSheetViewVisibleCols",
|
||||
"getSheetViewVisibleRows",
|
||||
"getFrozenSheetViewRatio",
|
||||
"isPositionVisible",
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -40591,6 +40894,11 @@
|
||||
margin-top: -1px;
|
||||
border: 1px solid;
|
||||
z-index: ${ComponentsImportance.TopBarComposer};
|
||||
|
||||
.o-composer:empty:not(:focus)::before {
|
||||
/* svg free of use from https://uxwing.com/formula-fx-icon/ */
|
||||
content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 -10 121.8 142.9' width='24' height='24' focusable='false'%3E%3Cpath 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='%23BDBDBD' /%3E%3C/svg%3E");
|
||||
}
|
||||
}
|
||||
`;
|
||||
class TopBarComposer extends owl.Component {
|
||||
@@ -40997,6 +41305,7 @@
|
||||
this.state.menuState.parentMenu = menu;
|
||||
this.isSelectingMenu = true;
|
||||
this.openedEl = ev.target;
|
||||
this.env.model.dispatch("STOP_EDITION");
|
||||
}
|
||||
closeMenus() {
|
||||
this.state.activeTool = "";
|
||||
@@ -41370,8 +41679,10 @@
|
||||
this.sidePanel.isOpen = true;
|
||||
}
|
||||
closeSidePanel() {
|
||||
var _a, _b;
|
||||
this.sidePanel.isOpen = false;
|
||||
this.focusGrid();
|
||||
(_b = (_a = this.sidePanel.panelProps) === null || _a === void 0 ? void 0 : _a.onCloseSidePanel) === null || _b === void 0 ? void 0 : _b.call(_a);
|
||||
}
|
||||
toggleSidePanel(panel, panelProps) {
|
||||
if (this.sidePanel.isOpen && panel === this.sidePanel.component) {
|
||||
@@ -43810,7 +44121,7 @@
|
||||
if (currentPosition <= position && position < currentPosition + header.size) {
|
||||
return {
|
||||
index: parseInt(headerIndex),
|
||||
offset: convertDotValueToEMU(position - currentPosition + FIGURE_BORDER_SIZE),
|
||||
offset: convertDotValueToEMU(position - currentPosition + FIGURE_BORDER_WIDTH),
|
||||
};
|
||||
}
|
||||
else {
|
||||
@@ -43819,7 +44130,7 @@
|
||||
}
|
||||
return {
|
||||
index: headers.length - 1,
|
||||
offset: convertDotValueToEMU(position - currentPosition + FIGURE_BORDER_SIZE),
|
||||
offset: convertDotValueToEMU(position - currentPosition + FIGURE_BORDER_WIDTH),
|
||||
};
|
||||
}
|
||||
function createChartDrawing(figure, sheet, chartRelId) {
|
||||
@@ -45080,9 +45391,9 @@
|
||||
Object.defineProperty(exports, '__esModule', { value: true });
|
||||
|
||||
|
||||
__info__.version = '16.3.0-alpha.1';
|
||||
__info__.date = '2023-03-16T12:15:48.320Z';
|
||||
__info__.hash = 'f41d64a';
|
||||
__info__.version = '16.3.0-alpha.2';
|
||||
__info__.date = '2023-03-23T11:47:46.254Z';
|
||||
__info__.hash = '59f25bb';
|
||||
|
||||
|
||||
})(this.o_spreadsheet = this.o_spreadsheet || {}, owl);
|
||||
|
||||
@@ -520,6 +520,7 @@
|
||||
t-on-contextmenu.prevent.stop="onContextMenu"
|
||||
t-ref="figure"
|
||||
t-att-style="props.style"
|
||||
t-att-data-id="props.figure.id"
|
||||
tabindex="0"
|
||||
t-on-keydown="(ev) => this.onKeyDown(ev)"
|
||||
t-on-keyup.stop="">
|
||||
@@ -592,7 +593,7 @@
|
||||
</t>
|
||||
|
||||
<t t-name="o-spreadsheet-ChartFigure" owl="1">
|
||||
<div class="o-chart-container w-100 h-100">
|
||||
<div class="o-chart-container w-100 h-100" t-on-dblclick="onDoubleClick">
|
||||
<t
|
||||
t-component="chartComponent"
|
||||
figure="this.props.figure"
|
||||
@@ -624,6 +625,20 @@
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
<div
|
||||
class="o-figure-container position-absolute pe-none overflow-hidden"
|
||||
t-if="dnd.horizontalSnap"
|
||||
t-att-style="dnd.horizontalSnap.containerStyle"
|
||||
t-att-data-id="'HorizontalSnapContainer'">
|
||||
<div class="o-figure-snap-line horizontal" t-att-style="dnd.horizontalSnap.lineStyle"/>
|
||||
</div>
|
||||
<div
|
||||
class="o-figure-container position-absolute pe-none overflow-hidden"
|
||||
t-if="dnd.verticalSnap"
|
||||
t-att-style="dnd.verticalSnap.containerStyle"
|
||||
t-att-data-id="'VerticalSnapContainer'">
|
||||
<div class="o-figure-snap-line vertical" t-att-style="dnd.verticalSnap.lineStyle"/>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="o-spreadsheet-ImageFigure" owl="1">
|
||||
@@ -1780,7 +1795,7 @@
|
||||
'o-invalid': isInvalid || !range.isValidRange,
|
||||
'text-decoration-underline': range.isFocused and state.mode === 'select-range'
|
||||
}"
|
||||
t-ref="{{range.isFocused ? 'focused' : 'unfocused'}}Input"
|
||||
t-ref="{{range.isFocused ? 'focusedInput' : 'unfocusedInput' + range_index}}"
|
||||
/>
|
||||
<button
|
||||
class="o-btn o-remove-selection"
|
||||
@@ -3535,7 +3550,7 @@
|
||||
</t>
|
||||
</odoo>
|
||||
<!--
|
||||
__info__.version = '16.3.0-alpha.1';
|
||||
__info__.date = '2023-03-16T12:15:52.711Z';
|
||||
__info__.hash = 'f41d64a';
|
||||
__info__.version = '16.3.0-alpha.2';
|
||||
__info__.date = '2023-03-23T11:47:49.776Z';
|
||||
__info__.hash = '59f25bb';
|
||||
-->
|
||||
|
||||
Reference in New Issue
Block a user