[REF] pos_restaurant: rewrite resize logic for tables on floor screen

The previous code had a lot of repetition and made use of deprecated
features such as useListener. This commit refactors the resize logic by
creating a simple ResizeHandle component that encapsulates the drag
handle logic, it then communicates delta in x and y to the EditableTable
which applies it to the correct side and adds constraints on the
coordinates.

It also slightly improves the UX for round tables by also giving them
"corner" drag handles, which let you resize two dimensions at once
instead of just one, this also makes the code more uniform between the
table types, the only difference being where we position the drag
handles so that they're visible.

closes odoo/odoo#112430

Signed-off-by: Trinh Jacky (trj) <trj@odoo.com>
This commit is contained in:
Samuel Degueldre
2023-02-13 11:29:55 +01:00
parent 7f0a084243
commit 621c8cf5fe
18 changed files with 288 additions and 502 deletions
@@ -0,0 +1,24 @@
/** @odoo-module */
import { Component, useState } from "@odoo/owl";
export class EditBar extends Component {
static template = "pos_restaurant.EditBar";
static props = {
selectedTable: [{ type: Object, shape: { shape: String, "*": true } }, { value: false }],
floorMapScrollTop: Number,
createTable: Function,
duplicateTable: Function,
renameTable: Function,
changeSeatsNum: Function,
changeShape: Function,
setTableColor: Function,
setFloorColor: Function,
deleteTable: Function,
};
setup() {
super.setup();
this.state = useState({ isColorPicker: false });
}
}
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates id="template" xml:space="preserve">
<t t-name="EditBar" owl="1">
<t t-name="pos_restaurant.EditBar" owl="1">
<div class="edit-bar" t-attf-style="top:{{props.floorMapScrollTop}}px;">
<span class="edit-button" t-on-click.stop="props.createTable">
<i class="fa fa-plus" role="img" aria-label="Add" title="Add"></i>
@@ -0,0 +1,121 @@
/** @odoo-module */
import { Draggable } from "@point_of_sale/js/Misc/Draggable";
import { ResizeHandle } from "./resize_handle";
import { onWillUnmount, useEffect, useRef, Component } from "@odoo/owl";
import { Table } from "./table";
const MIN_TABLE_SIZE = 30; // px
function constrain(num, [min, max]) {
return Math.min(Math.max(num, min), max);
}
export class EditableTable extends Component {
static template = "pos_restaurant.EditableTable";
static components = { Draggable, ResizeHandle };
static props = {
onSaveTable: Function,
limit: { type: Object, shape: { el: [HTMLElement, { value: null }] } },
table: Table.props.table,
};
setup() {
super.setup();
useEffect(this._setElementStyle.bind(this));
this.root = useRef("root");
this.handles = {
"top left": ["minX", "minY"],
"top right": ["maxX", "minY"],
"bottom left": ["minX", "maxY"],
"bottom right": ["maxX", "maxY"],
};
onWillUnmount(() => this.props.onSaveTable(this.props.table));
}
onHandleMoveStart() {
this.startTable = { ...this.props.table };
}
onHandleMove(dir, { dx, dy }) {
const table = this.startTable;
// Working with min/max x and y makes constraints much easier to apply uniformly
const { width, height, position_h: minX, position_v: minY } = table;
const maxX = minX + width;
const maxY = minY + height;
const newTable = { minX, minY, maxX, maxY };
const [moveX, moveY] = this.handles[dir];
const { limits } = this;
const bounds = {
maxX: [minX + MIN_TABLE_SIZE, limits.maxX],
minX: [limits.minX, maxX - MIN_TABLE_SIZE],
maxY: [minY + MIN_TABLE_SIZE, limits.maxY],
minY: [limits.minY, maxY - MIN_TABLE_SIZE],
};
newTable[moveX] = constrain(newTable[moveX] + dx, bounds[moveX]);
newTable[moveY] = constrain(newTable[moveY] + dy, bounds[moveY]);
// Convert back to server format at the end
this.props.table.position_h = newTable.minX;
this.props.table.position_v = newTable.minY;
this.props.table.width = newTable.maxX - newTable.minX;
this.props.table.height = newTable.maxY - newTable.minY;
this._setElementStyle();
}
/**
* Offsets the resize handles from the edge of the table. For square tables,
* the offset is half the width of the handle (we just want a quarter circle
* to be visible), for round tables it's half the width plus the distance of
* the middle of the rounded border's arc to the edge.
*
* @param {`${'top'|'bottom'} ${'left'|'right'}`} handleName the handle for
* which to compute the style
* @returns {string} the value of the style attribute for the given handle
*/
computeHandleStyle(handleName) {
const table = this.props.table;
// 24 is half the handle's width
let offset = -24;
if (table.shape === "round") {
// min(width/2, height/2) is the real border radius
// 0.2929 is (1 - cos(45°)) to get in the middle of the border's arc
offset += Math.min(table.width / 2, table.height / 2) * 0.2929;
}
return handleName
.split(" ")
.map((dir) => `${dir}: ${offset}px;`)
.join(" ");
}
get limits() {
const limitRect = this.props.limit.el.getBoundingClientRect();
const offsetParentRect = this.root.el.offsetParent.getBoundingClientRect();
return {
minY: limitRect.top - offsetParentRect.top,
minX: limitRect.left - offsetParentRect.left,
maxY: limitRect.top - offsetParentRect.top + limitRect.height,
maxX: limitRect.left - offsetParentRect.left + limitRect.width,
};
}
_setElementStyle() {
const table = this.props.table;
Object.assign(this.root.el.style, {
left: `${table.position_h}px`,
top: `${table.position_v}px`,
width: `${table.width}px`,
height: `${table.height}px`,
background: table.color || "rgb(53, 211, 116)",
"line-height": `${table.height}px`,
"border-radius": table.shape === "round" ? "1000px" : "3px",
"font-size": table.height >= 150 && table.width >= 150 ? "32px" : "16px",
});
}
onDragEnd(event) {
const { loc } = event.detail;
const table = this.props.table;
table.position_v = loc.top;
table.position_h = loc.left;
}
}
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates id="template" xml:space="preserve">
<t t-name="pos_restaurant.EditableTable" owl="1">
<Draggable limitArea="'.floor-map'">
<div class="table selected" t-on-click.stop="" t-ref="root" t-on-drag-end="onDragEnd">
<span class="label drag-handle">
<t t-esc="props.table.name" />
</span>
<span class="table-seats">
<t t-esc="props.table.seats" />
</span>
<t t-foreach="handles" t-as="handle" t-key="handle">
<ResizeHandle class="'table-handle'" onMove="(delta) => this.onHandleMove(handle, delta)" onMoveStart.bind="onHandleMoveStart" style="computeHandleStyle(handle)"/>
</t>
</div>
</Draggable>
</t>
</templates>
@@ -1,6 +1,5 @@
/** @odoo-module */
import { LegacyComponent } from "@web/legacy/legacy_component";
import { debounce } from "@web/core/utils/timing";
import { registry } from "@web/core/registry";
@@ -8,27 +7,24 @@ import { TextInputPopup } from "@point_of_sale/js/Popups/TextInputPopup";
import { NumberPopup } from "@point_of_sale/js/Popups/NumberPopup";
import { ConfirmPopup } from "@point_of_sale/js/Popups/ConfirmPopup";
import { EditableTable } from "./EditableTable";
import { EditBar } from "./EditBar";
import { TableWidget } from "./TableWidget";
import { EditableTable } from "./editable_table";
import { EditBar } from "./edit_bar";
import { Table } from "./table";
import { usePos } from "@point_of_sale/app/pos_hook";
import { useService } from "@web/core/utils/hooks";
import { Component, onPatched, onMounted, onWillUnmount, useRef, useState } from "@odoo/owl";
const { onPatched, onMounted, onWillUnmount, useRef, useState } = owl;
export class FloorScreen extends LegacyComponent {
static components = { EditableTable, EditBar, TableWidget };
static template = "FloorScreen";
export class FloorScreen extends Component {
static components = { EditableTable, EditBar, Table };
static template = "pos_restaurant.FloorScreen";
static props = { isShown: Boolean, floor: { type: true, optional: true } };
static storeOnOrder = false;
/**
* @param {Object} props
* @param {Object} props.floor
*/
setup() {
super.setup();
this.pos = usePos();
this.popup = useService("popup");
this.rpc = useService("rpc");
const floor = this.props.floor ? this.props.floor : this.env.pos.floors[0];
this.state = useState({
selectedFloorId: floor.id,
@@ -93,6 +89,7 @@ export class FloorScreen extends LegacyComponent {
height: 75,
shape: "square",
seats: 1,
color: "rgb(53, 211, 116)",
};
}
newTable.name = this._getNewTableName(newTable.name);
@@ -298,6 +295,7 @@ export class FloorScreen extends LegacyComponent {
return;
}
const originalSelectedTableId = this.state.selectedTableId;
this.env.pos.tables_by_id[originalSelectedTableId].active = false;
await this.rpc({
model: "restaurant.table",
method: "create_from_ui",
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="FloorScreen" owl="1">
<t t-name="pos_restaurant.FloorScreen" owl="1">
<div class="floor-screen screen">
<div class="screen-content-flexbox">
<t t-if="env.pos.floors.length > 1">
@@ -29,8 +29,8 @@
</div>
<div t-else="" class="tables">
<t t-foreach="activeTables" t-as="table" t-key="table.id">
<TableWidget t-if="table.id !== state.selectedTableId" onClick.bind="onSelectTable" table="table" />
<EditableTable t-else="" table="table" onSaveTable.bind="onSaveTable" />
<Table t-if="table.id !== state.selectedTableId" onClick.bind="onSelectTable" table="table" />
<EditableTable t-else="" table="table" onSaveTable.bind="onSaveTable" limit="floorMapRef" />
</t>
</div>
<span t-if="env.pos.user.role == 'manager'" class="edit-button editing" t-att-class="{ active: state.isEditMode }" t-on-click.stop="toggleEditMode"
@@ -0,0 +1,64 @@
/** @odoo-module */
import { Component, onWillUnmount } from "@odoo/owl";
export class ResizeHandle extends Component {
static template = "pos_restaurant.ResizeHandle";
static props = { onMove: Function, onMoveStart: Function, class: String, style: String };
setup() {
this.onMouseMove = (ev) => {
this.onMove({ x: ev.clientX, y: ev.clientY });
};
this.onTouchMove = (ev) => {
this.onMove({
x: ev.touches[0].clientX,
y: ev.touches[0].clientY,
});
};
onWillUnmount(() => {
document.removeEventListener("mousemove", this.onMouseMove);
document.removeEventListener("touchmove", this.onTouchMove);
});
}
onMouseDown(ev) {
this.startPosition = { x: ev.clientX, y: ev.clientY };
this.props.onMoveStart();
document.addEventListener("mousemove", this.onMouseMove);
document.addEventListener(
"mouseup",
(ev) => {
ev.stopPropagation();
document.removeEventListener("mousemove", this.onMouseMove);
},
{ once: true, capture: true }
);
}
onTouchStart(ev) {
this.startPosition = { x: ev.touches[0].clientX, y: ev.touches[0].clientY };
this.props.onMoveStart();
document.addEventListener("touchmove", this.onTouchMove);
document.addEventListener(
"touchend",
(ev) => {
ev.stopPropagation();
document.removeEventListener("touchmove", this.onTouchMove);
},
{ once: true, capture: true }
);
}
/**
* Calls the onMove props with the delta x/y since the dragging began.
*
* @param {{x: number, y: number}} param0 the new position of the cursor
*/
onMove({ x, y }) {
this.props.onMove({
dx: x - this.startPosition.x,
dy: y - this.startPosition.y,
});
}
}
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates id="template" xml:space="preserve">
<t t-name="pos_restaurant.ResizeHandle" owl="1">
<span t-att-class="props.class" t-att-style="props.style" t-on-touchstart="onTouchStart" t-on-mousedown="onMouseDown"/>
</t>
</template>
@@ -1,42 +1,39 @@
/** @odoo-module */
import { LegacyComponent } from "@web/legacy/legacy_component";
import { Component } from "@odoo/owl";
/**
* props: {
* onClick: callback,
* table: table object,
* }
*/
export class TableWidget extends LegacyComponent {
static template = "TableWidget";
export class Table extends Component {
static template = "pos_restaurant.Table";
static props = {
onClick: Function,
table: {
type: Object,
shape: {
position_h: Number,
position_v: Number,
width: Number,
height: Number,
shape: String,
color: [String, { value: false }],
name: String,
seats: Number,
"*": true,
},
},
};
setup() {
owl.onMounted(this.onMounted);
}
onMounted() {
get style() {
const table = this.props.table;
function unit(val) {
return `${val}px`;
}
const style = {
width: unit(table.width),
height: unit(table.height),
"line-height": unit(table.height),
top: unit(table.position_v),
left: unit(table.position_h),
"border-radius": table.shape === "round" ? unit(1000) : "3px",
};
if (table.color) {
style.background = table.color;
}
if (table.height >= 150 && table.width >= 150) {
style["font-size"] = "32px";
}
Object.assign(this.el.style, style);
const tableCover = this.el.querySelector(".table-cover");
Object.assign(tableCover.style, { height: `${Math.ceil(this.fill * 100)}%` });
return `
width: ${table.width}px;
height: ${table.height}px;
line-height: ${table.height}px;
top: ${table.position_v}px;
left: ${table.position_h}px;
border-radius: ${table.shape === "round" ? 1000 : 3}px;
background: ${table.color || "rgb(53, 211, 116)"};
font-size: ${table.height >= 150 && table.width >= 150 ? 32 : 16}px;
`;
}
get fill() {
const customerCount = this.env.pos.getCustomerCount(this.props.table.id);
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates id="template" xml:space="preserve">
<t t-name="TableWidget" owl="1">
<div class="table" t-on-click.stop="() => props.onClick(props.table)">
<span class="table-cover" t-att-class="{ full: fill >= 1 }"></span>
<t t-name="pos_restaurant.Table" owl="1">
<div class="table" t-on-click.stop="() => props.onClick(props.table)" t-att-style="style">
<span class="table-cover" t-att-class="{ full: fill >= 1 }" t-att-style="`height: ${Math.ceil(fill * 100)}%`"></span>
<span t-att-class="orderCountClass" t-att-hidden="orderCount === 0">
<t t-esc="orderCount" />
</span>
@@ -3,7 +3,7 @@
import { patch } from "@web/core/utils/patch";
import { PosStore } from "@point_of_sale/app/pos_store";
import { PaymentScreen } from "@point_of_sale/js/Screens/PaymentScreen/PaymentScreen";
import { FloorScreen } from "@pos_restaurant/js/Screens/FloorScreen/FloorScreen";
import { FloorScreen } from "@pos_restaurant/app/floor_screen/floor_screen";
const NON_IDLE_EVENTS = [
"mousemove",
@@ -1,325 +0,0 @@
/** @odoo-module */
import { useListener } from "@web/core/utils/hooks";
import { LegacyComponent } from "@web/legacy/legacy_component";
const { onMounted, useExternalListener } = owl;
export class Resizeable extends LegacyComponent {
static template = "Resizeable";
setup() {
super.setup();
useExternalListener(document, "mousemove", this.resizeN);
useExternalListener(document, "mouseup", this.endResizeN);
useListener("mousedown", ".resize-handle-n", this.startResizeN);
useExternalListener(document, "mousemove", this.resizeS);
useExternalListener(document, "mouseup", this.endResizeS);
useListener("mousedown", ".resize-handle-s", this.startResizeS);
useExternalListener(document, "mousemove", this.resizeW);
useExternalListener(document, "mouseup", this.endResizeW);
useListener("mousedown", ".resize-handle-w", this.startResizeW);
useExternalListener(document, "mousemove", this.resizeE);
useExternalListener(document, "mouseup", this.endResizeE);
useListener("mousedown", ".resize-handle-e", this.startResizeE);
useExternalListener(document, "mousemove", this.resizeNW);
useExternalListener(document, "mouseup", this.endResizeNW);
useListener("mousedown", ".resize-handle-nw", this.startResizeNW);
useExternalListener(document, "mousemove", this.resizeNE);
useExternalListener(document, "mouseup", this.endResizeNE);
useListener("mousedown", ".resize-handle-ne", this.startResizeNE);
useExternalListener(document, "mousemove", this.resizeSW);
useExternalListener(document, "mouseup", this.endResizeSW);
useListener("mousedown", ".resize-handle-sw", this.startResizeSW);
useExternalListener(document, "mousemove", this.resizeSE);
useExternalListener(document, "mouseup", this.endResizeSE);
useListener("mousedown", ".resize-handle-se", this.startResizeSE);
useExternalListener(document, "touchmove", this.resizeN);
useExternalListener(document, "touchend", this.endResizeN);
useListener("touchstart", ".resize-handle-n", this.startResizeN);
useExternalListener(document, "touchmove", this.resizeS);
useExternalListener(document, "touchend", this.endResizeS);
useListener("touchstart", ".resize-handle-s", this.startResizeS);
useExternalListener(document, "touchmove", this.resizeW);
useExternalListener(document, "touchend", this.endResizeW);
useListener("touchstart", ".resize-handle-w", this.startResizeW);
useExternalListener(document, "touchmove", this.resizeE);
useExternalListener(document, "touchend", this.endResizeE);
useListener("touchstart", ".resize-handle-e", this.startResizeE);
useExternalListener(document, "touchmove", this.resizeNW);
useExternalListener(document, "touchend", this.endResizeNW);
useListener("touchstart", ".resize-handle-nw", this.startResizeNW);
useExternalListener(document, "touchmove", this.resizeNE);
useExternalListener(document, "touchend", this.endResizeNE);
useListener("touchstart", ".resize-handle-ne", this.startResizeNE);
useExternalListener(document, "touchmove", this.resizeSW);
useExternalListener(document, "touchend", this.endResizeSW);
useListener("touchstart", ".resize-handle-sw", this.startResizeSW);
useExternalListener(document, "touchmove", this.resizeSE);
useExternalListener(document, "touchend", this.endResizeSE);
useListener("touchstart", ".resize-handle-se", this.startResizeSE);
this.size = { height: 0, width: 0 };
this.loc = { top: 0, left: 0 };
this.tempSize = {};
onMounted(() => {
this.limitArea = this.props.limitArea
? document.querySelector(this.props.limitArea)
: this.el.offsetParent;
this.limitAreaBoundingRect = this.limitArea.getBoundingClientRect();
if (this.limitArea === this.el.offsetParent) {
this.limitLeft = 0;
this.limitTop = 0;
this.limitRight = this.limitAreaBoundingRect.width;
this.limitBottom = this.limitAreaBoundingRect.height;
} else {
this.limitLeft = -this.el.offsetParent.offsetLeft;
this.limitTop = -this.el.offsetParent.offsetTop;
this.limitRight =
this.limitAreaBoundingRect.width - this.el.offsetParent.offsetLeft;
this.limitBottom =
this.limitAreaBoundingRect.height - this.el.offsetParent.offsetTop;
}
this.limitAreaWidth = this.limitAreaBoundingRect.width;
this.limitAreaHeight = this.limitAreaBoundingRect.height;
});
}
startResizeN(event) {
let realEvent;
if (event instanceof CustomEvent) {
realEvent = event.detail;
} else {
realEvent = event;
}
const { y } = this._getEventLoc(realEvent);
this.isResizingN = true;
this.startY = y;
this.size.height = this.el.offsetHeight;
this.loc.top = this.el.offsetTop;
event.stopPropagation();
}
resizeN(event) {
if (this.isResizingN) {
const { y: newY } = this._getEventLoc(event);
let dY = newY - this.startY;
if (dY < 0 && Math.abs(dY) > this.loc.top) {
dY = -this.loc.top;
} else if (dY > 0 && dY > this.size.height) {
dY = this.size.height;
}
this.el.style.height = `${this.size.height - dY}px`;
this.el.style.top = `${this.loc.top + dY}px`;
}
}
endResizeN() {
if (this.isResizingN && !this.isResizingE && !this.isResizingW && !this.isResizingS) {
this.isResizingN = false;
this._triggerResizeEnd();
}
}
startResizeS(event) {
let realEvent;
if (event instanceof CustomEvent) {
realEvent = event.detail;
} else {
realEvent = event;
}
const { y } = this._getEventLoc(realEvent);
this.isResizingS = true;
this.startY = y;
this.size.height = this.el.offsetHeight;
this.loc.top = this.el.offsetTop;
event.stopPropagation();
}
resizeS(event) {
if (this.isResizingS) {
const { y: newY } = this._getEventLoc(event);
let dY = newY - this.startY;
if (dY > 0 && dY > this.limitAreaHeight - (this.size.height + this.loc.top)) {
dY = this.limitAreaHeight - (this.size.height + this.loc.top);
} else if (dY < 0 && Math.abs(dY) > this.size.height) {
dY = -this.size.height;
}
this.el.style.height = `${this.size.height + dY}px`;
}
}
endResizeS() {
if (!this.isResizingN && !this.isResizingE && !this.isResizingW && this.isResizingS) {
this.isResizingS = false;
this._triggerResizeEnd();
}
}
startResizeW(event) {
let realEvent;
if (event instanceof CustomEvent) {
realEvent = event.detail;
} else {
realEvent = event;
}
const { x } = this._getEventLoc(realEvent);
this.isResizingW = true;
this.startX = x;
this.size.width = this.el.offsetWidth;
this.loc.left = this.el.offsetLeft;
event.stopPropagation();
}
resizeW(event) {
if (this.isResizingW) {
const { x: newX } = this._getEventLoc(event);
let dX = newX - this.startX;
if (dX > 0 && dX > this.size.width) {
dX = this.size.width;
} else if (dX < 0 && Math.abs(dX) > this.loc.left + Math.abs(this.limitLeft)) {
dX = -this.loc.left + this.limitLeft;
}
this.el.style.width = `${this.size.width - dX}px`;
this.el.style.left = `${this.loc.left + dX}px`;
}
}
endResizeW() {
if (!this.isResizingN && !this.isResizingE && this.isResizingW && !this.isResizingS) {
this.isResizingW = false;
this._triggerResizeEnd();
}
}
startResizeE(event) {
let realEvent;
if (event instanceof CustomEvent) {
realEvent = event.detail;
} else {
realEvent = event;
}
const { x } = this._getEventLoc(realEvent);
this.isResizingE = true;
this.startX = x;
this.size.width = this.el.offsetWidth;
this.loc.left = this.el.offsetLeft;
event.stopPropagation();
}
resizeE(event) {
if (this.isResizingE) {
const { x: newX } = this._getEventLoc(event);
let dX = newX - this.startX;
if (
dX > 0 &&
dX >
this.limitAreaWidth -
(this.size.width + this.loc.left + Math.abs(this.limitLeft))
) {
dX =
this.limitAreaWidth -
(this.size.width + this.loc.left + Math.abs(this.limitLeft));
} else if (dX < 0 && Math.abs(dX) > this.size.width) {
dX = -this.size.width;
}
this.el.style.width = `${this.size.width + dX}px`;
}
}
endResizeE() {
if (!this.isResizingN && this.isResizingE && !this.isResizingW && !this.isResizingS) {
this.isResizingE = false;
this._triggerResizeEnd();
}
}
startResizeNW(event) {
this.startResizeN(event);
this.startResizeW(event);
}
resizeNW(event) {
this.resizeN(event);
this.resizeW(event);
}
endResizeNW() {
if (this.isResizingN && !this.isResizingE && this.isResizingW && !this.isResizingS) {
this.isResizingN = false;
this.isResizingW = false;
this._triggerResizeEnd();
}
}
startResizeNE(event) {
this.startResizeN(event);
this.startResizeE(event);
}
resizeNE(event) {
this.resizeN(event);
this.resizeE(event);
}
endResizeNE() {
if (this.isResizingN && this.isResizingE && !this.isResizingW && !this.isResizingS) {
this.isResizingN = false;
this.isResizingE = false;
this._triggerResizeEnd();
}
}
startResizeSE(event) {
this.startResizeS(event);
this.startResizeE(event);
}
resizeSE(event) {
this.resizeS(event);
this.resizeE(event);
}
endResizeSE() {
if (!this.isResizingN && this.isResizingE && !this.isResizingW && this.isResizingS) {
this.isResizingS = false;
this.isResizingE = false;
this._triggerResizeEnd();
}
}
startResizeSW(event) {
this.startResizeS(event);
this.startResizeW(event);
}
resizeSW(event) {
this.resizeS(event);
this.resizeW(event);
}
endResizeSW() {
if (!this.isResizingN && !this.isResizingE && this.isResizingW && this.isResizingS) {
this.isResizingS = false;
this.isResizingW = false;
this._triggerResizeEnd();
}
}
_getEventLoc(event) {
let coordX, coordY;
if (event.touches && event.touches[0]) {
coordX = event.touches[0].clientX;
coordY = event.touches[0].clientY;
} else {
coordX = event.clientX;
coordY = event.clientY;
}
return {
x: coordX,
y: coordY,
};
}
_triggerResizeEnd() {
const size = {
height: this.el.offsetHeight,
width: this.el.offsetWidth,
};
const loc = {
top: this.el.offsetTop,
left: this.el.offsetLeft,
};
this.trigger("resize-end", { size, loc });
}
}
@@ -1,14 +0,0 @@
/** @odoo-module */
import { LegacyComponent } from "@web/legacy/legacy_component";
const { useState } = owl;
export class EditBar extends LegacyComponent {
static template = "EditBar";
setup() {
super.setup();
this.state = useState({ isColorPicker: false });
}
}
@@ -1,59 +0,0 @@
/** @odoo-module */
import { useListener } from "@web/core/utils/hooks";
import { LegacyComponent } from "@web/legacy/legacy_component";
import { Draggable } from "@point_of_sale/js/Misc/Draggable";
import { Resizeable } from "../../Resizeable";
const { onMounted, onPatched } = owl;
export class EditableTable extends LegacyComponent {
static template = "EditableTable";
static components = { Draggable, Resizeable };
setup() {
super.setup();
useListener("resize-end", this._onResizeEnd);
useListener("drag-end", this._onDragEnd);
onPatched(this._setElementStyle.bind(this));
onMounted(this._setElementStyle.bind(this));
}
_setElementStyle() {
const table = this.props.table;
function unit(val) {
return `${val}px`;
}
const style = {
width: unit(table.width),
height: unit(table.height),
"line-height": unit(table.height),
top: unit(table.position_v),
left: unit(table.position_h),
"border-radius": table.shape === "round" ? unit(1000) : "3px",
};
if (table.color) {
style.background = table.color;
}
if (table.height >= 150 && table.width >= 150) {
style["font-size"] = "32px";
}
Object.assign(this.el.style, style);
}
_onResizeEnd(event) {
const { size, loc } = event.detail;
const table = this.props.table;
table.width = size.width;
table.height = size.height;
table.position_v = loc.top;
table.position_h = loc.left;
this.props.onSaveTable(this.props.table);
}
_onDragEnd(event) {
const { loc } = event.detail;
const table = this.props.table;
table.position_v = loc.top;
table.position_h = loc.left;
this.props.onSaveTable(this.props.table);
}
}
@@ -4,7 +4,7 @@ import { ReceiptScreen } from "@point_of_sale/js/Screens/ReceiptScreen/ReceiptSc
import { usePos } from "@point_of_sale/app/pos_hook";
import { patch } from "@web/core/utils/patch";
import { onWillUnmount } from "@odoo/owl";
import { FloorScreen } from "@pos_restaurant/js/Screens/FloorScreen/FloorScreen";
import { FloorScreen } from "@pos_restaurant/app/floor_screen/floor_screen";
patch(ReceiptScreen, "pos_restaurant.ReceiptScreen", {
showBackToFloorButton: true,
@@ -77,7 +77,7 @@
border-radius: 3px;
cursor: pointer;
box-shadow: 0px 3px rgba(0,0,0,0.07);
transition: background, background-color 300ms ease-in-out;
transition: background-color 300ms ease-in-out;
overflow: hidden;
}
.floor-map .table .table-cover {
@@ -217,26 +217,19 @@
position: absolute;
width: 48px;
height: 48px;
left: 50%;
top: 50%;
border-radius: 24px;
background: white;
box-shadow: 0px 2px 3px rgba(0,0,0,0.2);
/* See o-grab-cursor mixin */
cursor: url(/web/static/img/openhand.cur), grab;
transition: all 150ms linear;
transition: width 150ms linear, height 150ms linear;
z-index: 100;
transform: translate(-50%, -50%);
}
.floor-map .table.selected .table-handle:hover {
width: 60px;
height: 60px;
border-radius: 30px;
}
.floor-map .table .table-handle.top { top: 0; }
.floor-map .table .table-handle.bottom { top: 100%; }
.floor-map .table .table-handle.left { left: 0; }
.floor-map .table .table-handle.right { left: 100%; }
.floor-map .table .order-count {
position: absolute;
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="Resizeable" owl="1">
<t t-slot="default"></t>
</t>
</templates>
@@ -1,31 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates id="template" xml:space="preserve">
<t t-name="EditableTable" owl="1">
<Draggable limitArea="'.floor-map'">
<Resizeable limitArea="'.floor-map'">
<div class="table selected" t-on-click.stop="">
<span class="label drag-handle">
<t t-esc="props.table.name" />
</span>
<span class="table-seats">
<t t-esc="props.table.seats" />
</span>
<t t-if="props.table.shape === 'round'">
<div class="table-handle top resize-handle-n"></div>
<div class="table-handle bottom resize-handle-s"></div>
<div class="table-handle left resize-handle-w"></div>
<div class="table-handle right resize-handle-e"></div>
</t>
<t t-if="props.table.shape === 'square'">
<span class='table-handle top right resize-handle-ne'></span>
<span class='table-handle top left resize-handle-nw'></span>
<span class='table-handle bottom right resize-handle-se'></span>
<span class='table-handle bottom left resize-handle-sw'></span>
</t>
</div>
</Resizeable>
</Draggable>
</t>
</templates>