|
| 1 | +/** @odoo-module **/ |
| 2 | +import {Component, EventBus, useRef, useState} from "@odoo/owl"; |
| 3 | +import {registry} from "@web/core/registry"; |
| 4 | + |
| 5 | +export class Highlighter extends Component { |
| 6 | + setup() { |
| 7 | + this.state = useState({visible: false}); |
| 8 | + this.bus = this.props.bus; |
| 9 | + this.bus.on("hide", this, () => this.hide()); |
| 10 | + this.bus.on("highlight", this, (options) => this.highlight(options)); |
| 11 | + this.highlightRef = useRef("highlightRef"); |
| 12 | + this.overlayRef = useRef("overlay"); |
| 13 | + } |
| 14 | + |
| 15 | + hide() { |
| 16 | + this.state.visible = false; |
| 17 | + this.resetAnimation(); |
| 18 | + } |
| 19 | + |
| 20 | + _getBoundsOfElement($el) { |
| 21 | + const bounds = { |
| 22 | + x: Number.MAX_SAFE_INTEGER, |
| 23 | + y: Number.MAX_SAFE_INTEGER, |
| 24 | + }; |
| 25 | + |
| 26 | + let xEnd = 0, |
| 27 | + yEnd = 0; |
| 28 | + |
| 29 | + $el.filter(":visible").each(function () { |
| 30 | + const elementBounds = this.getBoundingClientRect(); |
| 31 | + if (elementBounds.x < bounds.x) { |
| 32 | + bounds.x = elementBounds.x; |
| 33 | + } |
| 34 | + if (elementBounds.y < bounds.y) { |
| 35 | + bounds.y = elementBounds.y; |
| 36 | + } |
| 37 | + if (xEnd < elementBounds.x + elementBounds.width) { |
| 38 | + xEnd = elementBounds.x + elementBounds.width; |
| 39 | + } |
| 40 | + if (yEnd < elementBounds.y + elementBounds.height) { |
| 41 | + yEnd = elementBounds.y + elementBounds.height; |
| 42 | + } |
| 43 | + }); |
| 44 | + |
| 45 | + bounds.width = xEnd - bounds.x; |
| 46 | + bounds.height = yEnd - bounds.y; |
| 47 | + return bounds; |
| 48 | + } |
| 49 | + |
| 50 | + highlight({selector, content, animate = 250, padding = 10}) { |
| 51 | + const selection = $(selector); |
| 52 | + |
| 53 | + if (!selection.length) { |
| 54 | + return console.error("Element not found.", selector); |
| 55 | + } |
| 56 | + const bounds = this._getBoundsOfElement(selection); |
| 57 | + this.state.visible = true; |
| 58 | + this.animate(content, bounds, animate, padding); |
| 59 | + } |
| 60 | + |
| 61 | + animate(content, bounds, animate = 250, padding = 10) { |
| 62 | + const $el = $(this.highlightRef.el); |
| 63 | + |
| 64 | + $el.popover("dispose"); |
| 65 | + $el.animate( |
| 66 | + { |
| 67 | + top: _.str.sprintf("%spx", Math.floor(bounds.y) - padding), |
| 68 | + left: _.str.sprintf("%spx", Math.floor(bounds.x) - padding), |
| 69 | + width: _.str.sprintf("%spx", Math.floor(bounds.width) + padding * 2), |
| 70 | + height: _.str.sprintf("%spx", Math.floor(bounds.height) + padding * 2), |
| 71 | + }, |
| 72 | + animate ? animate : 0, |
| 73 | + function () { |
| 74 | + $el.popover( |
| 75 | + _.extend(this._getPopoverOptions(), { |
| 76 | + content: content, |
| 77 | + }) |
| 78 | + ).popover("show"); |
| 79 | + }.bind(this) |
| 80 | + ); |
| 81 | + } |
| 82 | + |
| 83 | + _getPopoverOptions() { |
| 84 | + return { |
| 85 | + container: $(this.overlayRef.el), |
| 86 | + placement: "auto", |
| 87 | + html: true, |
| 88 | + trigger: "manual", |
| 89 | + boundary: "viewport", |
| 90 | + sanitize: false, |
| 91 | + template: |
| 92 | + '<div class="popover" role="tooltip"><div class="popover-body"></div></div>', |
| 93 | + }; |
| 94 | + } |
| 95 | + |
| 96 | + resetAnimation() { |
| 97 | + const $el = $(this.highlightRef.el); |
| 98 | + $el.popover("dispose"); |
| 99 | + $el.css({ |
| 100 | + top: 0, |
| 101 | + left: 0, |
| 102 | + width: 0, |
| 103 | + height: 0, |
| 104 | + }); |
| 105 | + } |
| 106 | +} |
| 107 | +Highlighter.template = "web_help.Highlighter"; |
| 108 | + |
| 109 | +export const highlighterService = { |
| 110 | + start() { |
| 111 | + const bus = new EventBus(); |
| 112 | + |
| 113 | + registry.category("main_components").add("Highlighter", { |
| 114 | + Component: Highlighter, |
| 115 | + props: {bus}, |
| 116 | + }); |
| 117 | + |
| 118 | + return { |
| 119 | + hide: () => bus.trigger("hide"), |
| 120 | + highlight: (selector, content, animate = 250, padding = 10) => |
| 121 | + bus.trigger("highlight", { |
| 122 | + selector: selector, |
| 123 | + content: content, |
| 124 | + animate: animate, |
| 125 | + padding: padding, |
| 126 | + }), |
| 127 | + }; |
| 128 | + }, |
| 129 | +}; |
| 130 | + |
| 131 | +registry.category("services").add("highlighter", highlighterService); |
0 commit comments