Skip to content

Commit d606807

Browse files
committed
[ADD] web_help
[FIX] web_help: Run pre-commit [FIX] web_help: Run pre-commit [FIX] web_help: Run pre-commit [FIX] web_help: Run pre-commit [IMP] Easy changeable template and buttons texts, more demo, fixed readme. [IMP] Easy changeable template and buttons texts, more demo, fixed readme. [IMP] Easy changeable template and buttons texts, more demo, fixed readme. [FIX] Grammar
1 parent 72c420f commit d606807

21 files changed

+529
-0
lines changed

setup/web_help/odoo/addons/web_help

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../../web_help

setup/web_help/setup.py

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import setuptools
2+
3+
setuptools.setup(
4+
setup_requires=['setuptools-odoo'],
5+
odoo_addon=True,
6+
)

web_help/README.rst

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
**This file is going to be generated by oca-gen-addon-readme.**
2+
3+
*Manual changes will be overwritten.*
4+
5+
Please provide content in the ``readme`` directory:
6+
7+
* **DESCRIPTION.rst** (required)
8+
* INSTALL.rst (optional)
9+
* CONFIGURE.rst (optional)
10+
* **USAGE.rst** (optional, highly recommended)
11+
* DEVELOP.rst (optional)
12+
* ROADMAP.rst (optional)
13+
* HISTORY.rst (optional, recommended)
14+
* **CONTRIBUTORS.rst** (optional, highly recommended)
15+
* CREDITS.rst (optional)
16+
17+
Content of this README will also be drawn from the addon manifest,
18+
from keys such as name, authors, maintainers, development_status,
19+
and license.
20+
21+
A good, one sentence summary in the manifest is also highly recommended.
22+
23+
24+
Automatic changelog generation
25+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
26+
27+
`HISTORY.rst` can be auto generated using `towncrier <https://pypi.org/project/towncrier>`_.
28+
29+
Just put towncrier compatible changelog fragments into `readme/newsfragments`
30+
and the changelog file will be automatically generated and updated when a new fragment is added.
31+
32+
Please refer to `towncrier` documentation to know more.
33+
34+
NOTE: the changelog will be automatically generated when using `/ocabot merge $option`.
35+
If you need to run it manually, refer to `OCA/maintainer-tools README <https://github.com/OCA/maintainer-tools>`_.

web_help/__init__.py

Whitespace-only changes.

web_help/__manifest__.py

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"name": "Help Framework",
3+
"sumamry": "This module introduces a new way to guide users",
4+
"author": "Onestein,Odoo Community Association (OCA)",
5+
"category": "Technical",
6+
"license": "AGPL-3",
7+
"version": "16.0.1.0.0",
8+
"website": "https://github.com/OCA/web",
9+
"depends": [
10+
"web",
11+
],
12+
"assets": {
13+
"web.assets_backend": [
14+
"web_help/static/src/components/highlighter/highlighter.esm.js",
15+
"web_help/static/src/components/highlighter/highlighter.scss",
16+
"web_help/static/src/components/highlighter/highlighter.xml",
17+
"web_help/static/src/helpers.esm.js",
18+
"web_help/static/src/trip.esm.js",
19+
"web_help/static/src/user_trip.esm.js",
20+
"web_help/static/src/change_password_trip.esm.js",
21+
"web_help/static/src/components/help_button/help_button.esm.js",
22+
"web_help/static/src/components/help_button/help_button.xml",
23+
"web_help/static/src/trip.xml",
24+
]
25+
},
26+
}

web_help/readme/CONTRIBUTORS.rst

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* Dennis Sluijk <[email protected]>

web_help/readme/DESCRIPTION.rst

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
This module introduces a new way to guide the user through Odoo.
2+
This module is created because **the standard Odoo tours:**
3+
4+
#. forces the user to create records;
5+
#. cannot be reopened;
6+
#. is only available for the admin user;
7+
#. the bubbles are not obvious.

web_help/readme/USAGE.rst

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
**Demo:**
2+
3+
.. image:: ../static/description/demo.gif
4+
5+
You can see the demo live by going to the users list view (Settings > Users & Companies > Users)
6+
and clicking the little '?' next to the view switcher.
7+
8+
.. image:: ../static/description/viewswitcher.png
9+
10+
Also there's a demo for the change password wizard:
11+
12+
.. image:: ../static/description/changepassword.png
13+
14+
It's easy to create your own guides, please refer to ``static/src/user_trip.esm.js`` and
15+
``static/src/change_password_trip.esm.js``
13.2 KB
Loading

web_help/static/description/demo.gif

85.7 KB
Loading
4.33 KB
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/** @odoo-module **/
2+
import {Trip} from "@web_help/trip.esm";
3+
import {registry} from "@web/core/registry";
4+
5+
export class ChangePasswordTrip extends Trip {
6+
setup() {
7+
this.addStep({
8+
selector: "th[data-name='new_passwd'], td[name='new_passwd']",
9+
content: this.env._t("Change the password here, make sure it's secure."),
10+
});
11+
12+
this.addStep({
13+
selector: "button[name='change_password_button']",
14+
content: this.env._t("Click here to confirm it."),
15+
});
16+
}
17+
}
18+
19+
registry.category("trips").add("change_password_trip", {
20+
Trip: ChangePasswordTrip,
21+
selector: (model, viewType) =>
22+
model === "change.password.wizard" && viewType === "form",
23+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/** @odoo-module **/
2+
import LegacyControlPanel from "web.ControlPanel";
3+
import {ControlPanel} from "@web/search/control_panel/control_panel";
4+
import {findTrip} from "@web_help/helpers.esm";
5+
import {Component, useState} from "@odoo/owl";
6+
import {ActionDialog} from "@web/webclient/actions/action_dialog";
7+
8+
export class HelpButton extends Component {
9+
setup() {
10+
const foundTrip = findTrip(this.props.resModel, this.props.viewType);
11+
this.state = useState({
12+
TripClass: foundTrip,
13+
});
14+
}
15+
16+
onClick() {
17+
const TripClass = this.state.TripClass;
18+
const trip = new TripClass(this.env);
19+
trip.setup();
20+
trip.start();
21+
}
22+
}
23+
24+
HelpButton.template = "web_help.HelpButton";
25+
26+
Object.assign(ControlPanel.components, {HelpButton});
27+
Object.assign(LegacyControlPanel.components, {HelpButton});
28+
Object.assign(ActionDialog.components, {HelpButton});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?xml version="1.0" encoding="UTF-8" ?>
2+
<templates>
3+
<t t-name="web_help.HelpButton" owl="1">
4+
<button
5+
class="btn ml-2 js_web_help_btn"
6+
t-att-class="props.btnClass || 'btn-secondary'"
7+
t-on-click="onClick"
8+
t-if="state.TripClass"
9+
>
10+
<i class="fa fa-question" />
11+
</button>
12+
</t>
13+
<t t-inherit="web.ControlPanel.Regular" t-inherit-mode="extension">
14+
<xpath expr="//div[hasclass('o_cp_bottom')]" t-operation="inside">
15+
<nav class="btn-group">
16+
<HelpButton
17+
resModel="env.searchModel.resModel"
18+
viewType="env.config.viewType"
19+
/>
20+
</nav>
21+
</xpath>
22+
</t>
23+
<t t-inherit="web.ControlPanel.Small" t-inherit-mode="extension">
24+
<xpath expr="//div[hasclass('o_cp_bottom')]" t-operation="inside">
25+
<nav class="btn-group">
26+
<HelpButton
27+
resModel="env.searchModel.resModel"
28+
viewType="env.config.viewType"
29+
/>
30+
</nav>
31+
</xpath>
32+
</t>
33+
34+
<t t-inherit="web.ActionDialog.header" t-inherit-mode="extension">
35+
<xpath expr="//h4[hasclass('modal-title')]" t-operation="after">
36+
<HelpButton
37+
resModel="props.actionProps.resModel"
38+
viewType="props.actionProps.type"
39+
btnClass="'btn-link p-1'"
40+
/>
41+
</xpath>
42+
</t>
43+
</templates>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
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);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
.web_help_overlay {
2+
top: 0px;
3+
left: 0px;
4+
width: 100%;
5+
height: 100%;
6+
position: fixed;
7+
z-index: 1151;
8+
9+
.web_help_highlight {
10+
position: absolute;
11+
outline: 1000vw solid rgba(0, 0, 0, 0.7);
12+
}
13+
14+
.popover {
15+
background-color: transparent;
16+
border: none;
17+
18+
.popover-body {
19+
color: #fff;
20+
font-size: 1.2rem;
21+
}
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?xml version="1.0" encoding="UTF-8" ?>
2+
<templates>
3+
4+
<t t-name="web_help.Highlighter" owl="1">
5+
<div class="web_help_highlighter">
6+
<div
7+
class="web_help_overlay"
8+
t-att-class="{'d-none': !state.visible}"
9+
t-ref="overlay"
10+
>
11+
<div class="web_help_highlight" t-ref="highlightRef" />
12+
</div>
13+
</div>
14+
</t>
15+
</templates>

web_help/static/src/helpers.esm.js

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/** @odoo-module **/
2+
import {registry} from "@web/core/registry";
3+
4+
export function findTrip(model, viewType) {
5+
const trips = registry.category("trips").getAll();
6+
const matchedTrips = _.filter(trips, function (trip) {
7+
return trip.selector(model, viewType);
8+
});
9+
if (matchedTrips.length >= 1) {
10+
if (matchedTrips.length != 1) {
11+
console.warning("More than one trip found", model, viewType);
12+
}
13+
return matchedTrips[0].Trip;
14+
}
15+
return null;
16+
}

0 commit comments

Comments
 (0)