summaryrefslogtreecommitdiff
path: root/addons/web/static/src
diff options
context:
space:
mode:
Diffstat (limited to 'addons/web/static/src')
-rw-r--r--addons/web/static/src/img/smile.svg1
-rw-r--r--addons/web/static/src/js/chrome/abstract_web_client.js33
-rw-r--r--addons/web/static/src/js/chrome/action_manager.js5
-rw-r--r--addons/web/static/src/js/chrome/view_manager.js18
-rw-r--r--addons/web/static/src/js/views/form/form_controller.js8
-rw-r--r--addons/web/static/src/js/views/form/form_renderer.js5
-rw-r--r--addons/web/static/src/js/widgets/rainbow_man.js77
-rw-r--r--addons/web/static/src/less/animation.less14
-rw-r--r--addons/web/static/src/less/rainbow.less250
-rw-r--r--addons/web/static/src/xml/rainbow_man.xml52
10 files changed, 424 insertions, 39 deletions
diff --git a/addons/web/static/src/img/smile.svg b/addons/web/static/src/img/smile.svg
new file mode 100644
index 00000000000..d5d5fa78af3
--- /dev/null
+++ b/addons/web/static/src/img/smile.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 136 136"><title>face</title><path d="M68 134A66 66 0 1 0 2 68a66 66 0 0 0 66 66z" fill="#fbfcfd" stroke="#a1acba" stroke-width="4"/><path d="M109.4 19.2A64.7 64.7 0 0 1 132 68.5 63.8 63.8 0 0 1 68.5 132a64.7 64.7 0 0 1-49.3-22.6A64.8 64.8 0 0 0 61.5 125 63.8 63.8 0 0 0 125 61.5a64.8 64.8 0 0 0-15.6-42.3z" style="isolation:isolate" fill="#ecf1ff"/><path d="M49.1 62.1a10 10 0 0 0-14.1 0M101.1 62.1a10 10 0 0 0-14.1 0" fill="none" stroke="#a1acba" stroke-linecap="round" stroke-width="5"/><path d="M42 88a36.8 36.8 0 0 0 52 0" fill="none" stroke="#a1acba" stroke-linecap="round" stroke-linejoin="round" stroke-width="5"/></svg>
diff --git a/addons/web/static/src/js/chrome/abstract_web_client.js b/addons/web/static/src/js/chrome/abstract_web_client.js
index 481e9a7fc54..2a5bb5310ce 100644
--- a/addons/web/static/src/js/chrome/abstract_web_client.js
+++ b/addons/web/static/src/js/chrome/abstract_web_client.js
@@ -21,6 +21,7 @@ var Dialog = require('web.Dialog');
var Loading = require('web.Loading');
var mixins = require('web.mixins');
var NotificationManager = require('web.notification').NotificationManager;
+var RainbowMan = require('web.rainbow_man');
var session = require('web.session');
var Widget = require('web.Widget');
@@ -43,13 +44,13 @@ var AbstractWebClient = Widget.extend(mixins.ServiceProvider, {
// downstream widgets. Mainly side effects, such as rpcs, notifications
// or cache.
- // notifications and warnings
+ // notifications, warnings and effects
notification: function (e) {
if(this.notification_manager) {
this.notification_manager.notify(e.data.title, e.data.message, e.data.sticky);
}
},
- warning: '_displayWarning',
+ warning: '_onDisplayWarning',
load_views: function (event) {
var params = {
model: event.data.modelName,
@@ -65,6 +66,7 @@ var AbstractWebClient = Widget.extend(mixins.ServiceProvider, {
.load_filters(event.data.dataset, event.data.action_id)
.then(event.data.on_success);
},
+ show_effect: '_onShowEffect',
// session
get_session: function (event) {
if (event.data.callback) {
@@ -82,7 +84,6 @@ var AbstractWebClient = Widget.extend(mixins.ServiceProvider, {
}
});
},
- show_wow: '_onShowWow',
},
init: function (parent) {
this.client_options = {};
@@ -300,7 +301,8 @@ var AbstractWebClient = Widget.extend(mixins.ServiceProvider, {
/**
* Displays a warning in a dialog of with the NotificationManager
-
+ *
+ * @private
* @param {OdooEvent} e
* @param {string} e.data.message the warning's message
* @param {string} e.data.title the warning's title
@@ -308,7 +310,7 @@ var AbstractWebClient = Widget.extend(mixins.ServiceProvider, {
* @param {boolean} [e.data.sticky] whether or not the warning should be
* sticky (if displayed with the NotificationManager)
*/
- _displayWarning: function (e) {
+ _onDisplayWarning: function (e) {
var data = e.data;
if (data.type === 'dialog') {
new Dialog(this, {
@@ -321,20 +323,21 @@ var AbstractWebClient = Widget.extend(mixins.ServiceProvider, {
}
},
/**
- * Displays a thumb up, heart or peace image (randomly) for a moment (e.g.
- * used when an opportunity is won)
+ * Displays a visual effect (for example, a rainbowman0
*
* @private
+ * @param {OdooEvent} e
+ * @param {Object} [e.data] - key-value options to decide rainbowman
+ * behavior / appearance
*/
- _onShowWow: function () {
- var className = 'o_wow_thumbs';
- if (Math.random() > 0.9) {
- var otherClasses = ['o_wow_peace', 'o_wow_heart'];
- className = otherClasses[Math.floor(Math.random()*otherClasses.length)];
+ _onShowEffect: function (e) {
+ var data = e.data || {};
+ var type = data.type || 'rainbow_man';
+ if (type === 'rainbow_man') {
+ new RainbowMan(data).appendTo(this.$el);
+ } else {
+ throw new Error('Unknown effect type: ' + type);
}
- var $body = $('body');
- $body.addClass(className);
- setTimeout($body.removeClass.bind($body, className), 1000);
},
});
diff --git a/addons/web/static/src/js/chrome/action_manager.js b/addons/web/static/src/js/chrome/action_manager.js
index c81479ec1e0..280a90cec8d 100644
--- a/addons/web/static/src/js/chrome/action_manager.js
+++ b/addons/web/static/src/js/chrome/action_manager.js
@@ -862,6 +862,11 @@ var ActionManager = Widget.extend({
options.on_close();
}
this.dialog_stop();
+ // Display rainbowman on appropriate actions
+ if (action.effect) {
+ this.trigger_up('show_effect', action.effect);
+ }
+
return $.when();
},
ir_actions_server: function (action, options) {
diff --git a/addons/web/static/src/js/chrome/view_manager.js b/addons/web/static/src/js/chrome/view_manager.js
index 11be6fa4e6e..14ed4bdebc7 100644
--- a/addons/web/static/src/js/chrome/view_manager.js
+++ b/addons/web/static/src/js/chrome/view_manager.js
@@ -550,6 +550,9 @@ var ViewManager = Widget.extend(ControlPanelMixin, {
* @param {String} [action_data.special=null] special action handlers, closes the dialog if set
* @param {String} [action_data.type='workflow'] the action type, if present, one of ``'object'``, ``'action'`` or ``'workflow'``
* @param {Object} [action_data.context=null] additional action context, to add to the current context
+ * @param {string} [action_data.effect] if given, a visual effect (a
+ * rainbowman by default) will be displayed when the action is complete,
+ * with the string (evaluated) given as options.
* @param {DataSet} dataset a dataset object used to communicate with the server
* @param {integer[]} [res_ids] the res_ids of the objects on which the action is to be applied (fallback on env.ids if not set)
* @param {Function} on_closed callback to execute when dialog is closed or when the action does not generate any result (no new action)
@@ -562,6 +565,14 @@ var ViewManager = Widget.extend(ControlPanelMixin, {
// response handler
var handler = function (action) {
+ // show effect if button have effect attribute
+ // Rainbowman can be displayed from two places : from attribute on a button, or from python.
+ // Code below handles the first case i.e 'effect' attribute on button.
+ var effect = false;
+ if (action_data.effect) {
+ effect = pyeval.py_eval(action_data.effect);
+ };
+
if (action && action.constructor === Object) {
// filter out context keys that are specific to the current action.
// Wrong default_* and search_default_* values will no give the expected result
@@ -580,11 +591,16 @@ var ViewManager = Widget.extend(ControlPanelMixin, {
}
ncontext.add(action.context || {});
action.context = ncontext;
+ // In case effect data is returned from python and also there is rainbow
+ // attribute on button, priority is given to button attribute
+ action.effect = effect || action.effect;
return self.do_action(action, {
on_close: result_handler,
});
} else {
- self.do_action({"type":"ir.actions.act_window_close"});
+ // If action doesn't return anything, but have effect
+ // attribute on button, display rainbowman
+ self.do_action({"type":"ir.actions.act_window_close", 'effect': effect});
return result_handler();
}
};
diff --git a/addons/web/static/src/js/views/form/form_controller.js b/addons/web/static/src/js/views/form/form_controller.js
index f741e98bf4d..c95278aa255 100644
--- a/addons/web/static/src/js/views/form/form_controller.js
+++ b/addons/web/static/src/js/views/form/form_controller.js
@@ -244,7 +244,7 @@ var FormController = BasicController.extend({
* @override
* @private
*/
- _disableButtons: function () {
+ _disableButtons: function () {
this._super.apply(this, arguments);
this.renderer.disableButtons();
},
@@ -254,7 +254,7 @@ var FormController = BasicController.extend({
* @override
* @private
*/
- _enableButtons: function () {
+ _enableButtons: function () {
this._super.apply(this, arguments);
this.renderer.enableButtons();
},
@@ -380,10 +380,6 @@ var FormController = BasicController.extend({
});
}
- if (event.data.showWow) {
- def.then(this.trigger_up.bind(this, 'show_wow'));
- }
-
def.always(this._enableButtons.bind(this));
},
/**
diff --git a/addons/web/static/src/js/views/form/form_renderer.js b/addons/web/static/src/js/views/form/form_renderer.js
index 25b72e4fb5a..f7c4861dd2a 100644
--- a/addons/web/static/src/js/views/form/form_renderer.js
+++ b/addons/web/static/src/js/views/form/form_renderer.js
@@ -117,14 +117,14 @@ var FormRenderer = BasicRenderer.extend({
* Disable stat buttons so that they can't be clicked anymore
*
*/
- disableButtons: function() {
+ disableButtons: function() {
this.$('.oe_button_box button').attr('disabled', true);
},
/**
* Enable stat buttons so they can be clicked again
*
*/
- enableButtons: function() {
+ enableButtons: function() {
this.$('.oe_button_box button').removeAttr('disabled');
},
/**
@@ -206,7 +206,6 @@ var FormRenderer = BasicRenderer.extend({
self.trigger_up('button_clicked', {
attrs: node.attrs,
record: self.state,
- showWow: $el.hasClass('o_wow'),
});
});
},
diff --git a/addons/web/static/src/js/widgets/rainbow_man.js b/addons/web/static/src/js/widgets/rainbow_man.js
new file mode 100644
index 00000000000..53624b6322d
--- /dev/null
+++ b/addons/web/static/src/js/widgets/rainbow_man.js
@@ -0,0 +1,77 @@
+odoo.define('web.rainbow_man', function (require) {
+"use strict";
+
+var ajax = require("web.ajax");
+var Widget = require('web.Widget');
+var core = require('web.core');
+var qweb = core.qweb;
+var _t = core._t;
+
+var RainbowMan = Widget.extend({
+ template: 'rainbow_man.notification',
+ /**
+ * @override
+ * @constructor
+ * @param {Object} [options] - key-value options to decide rainbowman behavior / appearance
+ * @param {string} [options.message] - Message to be displayed on rainbowman card
+ * @param {string} [options.fadeout]
+ * Delay for rainbowman to disappear - [options.fadeout='fast'] will make rainbowman
+ * dissapear quickly, [options.fadeout='medium'] and [options.fadeout='slow'] will
+ * wait little longer before disappearing (can be used when [options.message]
+ * is longer), [options.fadeout='no'] will keep rainbowman on screen until
+ * user clicks anywhere outside rainbowman
+ * @param {string} [options.img_url] - URL of the image to be displayed
+ * @param {Boolean} [options.click_close] - If true, destroys rainbowman on click outside
+ */
+ init: function (options) {
+ this._super.apply(this, arguments);
+ var rainbowDelay = {slow: 4500, medium: 3500, fast:2000, no: false };
+ this.options = _.defaults(options || {}, {
+ fadeout: 'medium',
+ img_url: '/web/static/src/img/smile.svg',
+ message: _t('Well Done!'),
+ click_close: true,
+ });
+ this.delay = rainbowDelay[this.options.fadeout];
+ core.bus.on('clear_uncommitted_changes', this, this.destroy);
+ },
+ /**
+ * @override
+ */
+ willStart: function () {
+ var def;
+ if (!qweb.has_template(this.template)) {
+ // we need to manually load the templates when the rainbon_man is used in
+ // the frontend, for example, it may be displayed at the end of a tour.
+ def = ajax.loadXML("/web/static/src/xml/rainbow_man.xml", qweb);
+ }
+ return $.when(this._super.apply(this, arguments), def);
+ },
+ /**
+ * @override
+ */
+ start: function () {
+ var self = this;
+ if (this.options.click_close) {
+ core.bus.on('click', this, function (ev) {
+ if (ev.originalEvent && ev.target.className.indexOf('o_reward') === -1) {
+ this.destroy();
+ }
+ });
+ }
+ if (this.delay) {
+ setTimeout(function () {
+ self.$el.addClass('o_reward_fading');
+ setTimeout(function () {
+ self.destroy();
+ }, 600); // destroy only after fadeout animation is completed
+ }, this.delay);
+ }
+ this.$('.o_reward_msg_content').append(this.options.message);
+ return this._super.apply(this, arguments);
+ }
+});
+
+return RainbowMan;
+
+});
diff --git a/addons/web/static/src/less/animation.less b/addons/web/static/src/less/animation.less
index c9fe2ae7af4..6da0a1f9d64 100644
--- a/addons/web/static/src/less/animation.less
+++ b/addons/web/static/src/less/animation.less
@@ -160,17 +160,3 @@
font-size: 30vw;
}
}
-
-/************** USAGE of ANIMATIONS **************/
-// Animation on wow button
-// TODO the only difference here is the "content" property, just make an
-// 'o_wow' class for the other properties to avoid generating useless CSS
-.o_wow_thumbs:after {
- .o-display-marked("\f164", 20%, 40%, @odoo-brand-optional);
-}
-.o_wow_peace:after {
- .o-display-marked("\f118", 20%, 40%, @odoo-brand-optional);
-}
-.o_wow_heart:after {
- .o-display-marked("\f004", 20%, 40%, @odoo-brand-optional);
-}
diff --git a/addons/web/static/src/less/rainbow.less b/addons/web/static/src/less/rainbow.less
new file mode 100644
index 00000000000..ba63437fc2f
--- /dev/null
+++ b/addons/web/static/src/less/rainbow.less
@@ -0,0 +1,250 @@
+/************** rainbow *****************/
+
+// Variables
+@reward-size: 400px;
+@reward-text-color: #727880;
+@reward-base-time: 1.4s;
+
+// o_reward
+.o_reward {
+ will-change: transform;
+ z-index: @zindex-modal + 1;
+ padding: 50px;
+ margin: -5% auto 0 -(@reward-size / 2);
+ background-image: -webkit-radial-gradient(#EDEFF4 30%, transparent 70%, transparent);
+ background-image: -o-radial-gradient(#EDEFF4 30%, transparent 70%, transparent);
+ background-image: radial-gradient(#EDEFF4 30%, transparent 70%, transparent);
+
+ .o-animation(reward-fading, @reward-base-time * 0.5, ease-in-out, 0s, 1, normal, forwards);
+ .o-position-absolute(20%, auto, auto, 50%);
+ .size(@reward-size, @reward-size);
+
+ &.o_reward_fading {
+ display: block;
+ .o-animation(reward-fading-reverse, (@reward-base-time * 0.4), ease-in-out, 0s, 1, normal, forwards);
+
+ .o_reward_face_group {
+ .o-animation(reward-jump-reverse, (@reward-base-time * 0.4), ease-in-out, 0s, 1, normal, forwards);
+ }
+
+ .o_reward_rainbow {
+ path {
+ .o-animation(reward-rainbow-reverse, (@reward-base-time * 0.5), ease-out, 0s, 1, normal, forwards);
+ }
+ }
+
+ }
+
+ .o_reward_face, .o_reward_stars, .o_reward_shadow, .o_reward_thumbup {
+ margin: 0 auto;
+ }
+
+ .o_reward_rainbow {
+ path {
+ stroke-dasharray: 600;
+ stroke-dashoffset: 0;
+ fill: none;
+ stroke-linecap: round;
+ stroke-width: 21px;
+ .o-animation(reward-rainbow, (@reward-base-time ), ease-out, 0s, 1, normal, forwards);
+ }
+ }
+
+ .o_reward_face_group {
+ transform-origin: center;
+ .o-animation(reward-jump, @reward-base-time * 0.8, ease-in-out);
+ .o-position-absolute(6%, 0, 0, 0);
+ .size(100%, 60%);
+ }
+
+ .o_reward_face {
+ display: block;
+ top: 42%;
+ position: relative;
+ border-radius: 100%;
+ background: center center / contain no-repeat;
+ .o-animation(reward-float, @reward-base-time, ease-in-out, @reward-base-time, infinite, alternate);
+ .size(34%, 56.67%);
+ }
+
+ .o_reward_stars {
+ display: block;
+ .size(@reward-size * 0.75, @reward-size / 2);
+ .o-position-absolute(18%, 7%);
+
+ svg {
+ transform-origin: center center;
+ .o-position-absolute(28%, @left:3%);
+ .o-animation(reward-stars, @reward-base-time, ease-in-out, 0s, infinite, alternate-reverse);
+
+ &.star2, &.star4 {
+ .o-animation(reward-stars, (@reward-base-time*1.2), ease-in-out, 0s, infinite, alternate);
+ }
+
+ &.star2 {
+ left: 20%;
+ top: 2%;
+ }
+
+ &.star3 {
+ left: 49%;
+ top: 6%;
+ }
+
+ &.star4 {
+ left: 70%;
+ top: 27%;
+ }
+
+ }
+ }
+
+ .o_reward_thumbup {
+ width: 40px;
+ display: block;
+ .o-animation(reward-scale, @reward-base-time * 0.5, ease-in-out, 0s, infinite, alternate);
+ .o-position-absolute(63%, auto, auto, 65%);
+ }
+
+ .o_reward_msg_container {
+ will-change: transform;
+ padding-top: 11%;
+ width: 70%;
+ margin-left: 15%;
+
+ // Translate before animate
+ .o-transform(translateY(5px));
+
+ .o-animation(reward-float, @reward-base-time, ease-in-out, @reward-base-time, infinite, alternate-reverse);
+ .o-position-absolute(85%, auto, auto, 0%);
+
+ .o_reward_thumb_right {
+ height: 40px;
+ z-index: 1;
+ .o-position-absolute(0, auto, auto, 16%);
+ }
+
+ .o_reward_msg {
+ margin-left: 7%;
+ margin-top: -9.5%;
+ padding: 25px 15px 20px;
+ background: white;
+ border: 1px solid #ecf1ff;
+ border-top-width: 0;
+ display: inline-block;
+
+ // Reset margins for first and penultimate childs (the last one is shadow)
+ *:first-child {
+ margin-top: 0;
+ }
+ *:nth-last-child(2) {
+ margin-bottom: 0;
+ }
+ }
+
+ .o_reward_msg_content {
+ position: relative;
+ font-family: sans-serif;
+ text-align: left;
+ color: @reward-text-color;
+ }
+
+ .o_reward_shadow_container {
+ .o-transform(~"translateY(0px) rotateZ(0)");
+ .o-animation(reward-float, @reward-base-time, ease-in-out, @reward-base-time, infinite, alternate);
+ }
+
+ .o_reward_shadow {
+ .size(100%, 12px);
+ background-color: #e7eaf0;
+ border-radius: 100%;
+ .o-transform(~"scale(0.8) rotateZ(0)");
+ .o-animation(reward-scale, @reward-base-time, ease-in-out, @reward-base-time, infinite, alternate);
+ .o-position-absolute(auto, auto, -40px, 0);
+ }
+ }
+}
+
+@keyframes reward-fading {
+ 0% {
+ opacity: 0;
+ }
+ 100% {
+ opacity: 1;
+ }
+}
+
+@keyframes reward-fading-reverse {
+ 100% {
+ opacity: 0;
+ }
+}
+
+@keyframes reward-jump {
+ 0% {
+ .o-transform(~"scale(0.5)");
+ }
+ 50% {
+ .o-transform(~"scale(1.05)");
+ }
+ to {
+ .o-transform(~"scale(1)");
+ }
+}
+
+@keyframes reward-jump-reverse {
+ 0% {
+ .o-transform(~"scale(1)");
+ }
+ 50% {
+ .o-transform(~"scale(1.05)");
+ }
+ to {
+ .o-transform(~"scale(0.5)");
+ }
+}
+
+@keyframes reward-rainbow {
+ 0% {
+ stroke-dashoffset: -500;
+ }
+ to {
+ stroke-dashoffset: 0;
+ }
+}
+
+@keyframes reward-rainbow-reverse {
+ to {
+ stroke-dashoffset: -500;
+ }
+}
+
+@keyframes reward-float {
+ from {
+ .o-transform(~"translateY(0px)");
+ }
+ to {
+ .o-transform(~"translateY(5px)");
+ }
+}
+
+@keyframes reward-stars {
+ from {
+ .o-transform(~"scale(0.3) rotate(0deg)");
+ }
+ 50% {
+ .o-transform(~"scale(1.0) rotate(20deg)");
+ }
+ to {
+ .o-transform(~"scale(0.3) rotate(80deg)");
+ }
+}
+
+@keyframes reward-scale {
+ from {
+ .o-transform(~"scale(0.8)");
+ }
+ to {
+ .o-transform(~"scale(1.0)");
+ }
+}
diff --git a/addons/web/static/src/xml/rainbow_man.xml b/addons/web/static/src/xml/rainbow_man.xml
new file mode 100644
index 00000000000..744912a9a94
--- /dev/null
+++ b/addons/web/static/src/xml/rainbow_man.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<templates xml:space="preserve">
+ <t t-name="rainbow_man.notification">
+ <div class="o_reward">
+ <svg class="o_reward_rainbow" viewBox="0 0 340 180">
+ <path d="M270,170a100,100,0,0,0-200,0" style="stroke:#FF9E80;"/>
+ <path d="M290,170a120,120,0,0,0-240,0" style="stroke:#FFE57F;"/>
+ <path d="M310,170a140,140,0,0,0-280,0" style="stroke:#80D8FF;"/>
+ <path d="M330,170a160,160,0,0,0-320,0" style="stroke:#c794ba;"/>
+ </svg>
+ <div class="o_reward_stars" t-foreach="[1, 2, 3, 4]" t-as="star">
+ <t t-call="rainbow_man.star">
+ <t t-set="class" t-value="star"/>
+ </t>
+ </div>
+ <div class="o_reward_face_group">
+ <svg style="display:none">
+ <symbol id="thumb">
+ <path d="M10,52 C6,51 3,48 3,44 C2,42 3,39 5,38 C3,36 2,34 2,32 C2,29 3,27 5,26 C3,24 2,21 2,19 C2,15 7,12 10,12 L23,12 C23,11 23,11 23,11 L23,10 C23,8 24,6 25,4 C27,2 29,2 31,2 C33,2 35,2 36,4 C38,5 39,7 39,10 L39,38 C39,41 37,45 35,47 C32,50 28,51 25,52 L10,52 L10,52 Z" fill="#FBFBFC"/>
+ <polygon fill="#ECF1FF" points="25 11 25 51 5 52 5 12"/>
+ <path d="M31,0 C28,0 26,1 24,3 C22,5 21,7 21,10 L10,10 C8,10 6,11 4,12 C2,14 1,16 1,19 C1,21 1,24 2,26 C1,27 1,29 1,32 C1,34 1,36 2,38 C1,40 0,42 1,45 C1,50 5,53 10,54 L25,54 C29,54 33,52 36,49 C39,46 41,42 41,38 L41,10 C41,4 36,3.38176876e-16 31,0 M31,4 C34,4 37,6 37,10 L37,38 C37,41 35,44 33,46 C31,48 28,49 25,50 L10,50 C7,49 5,47 5,44 C4,41 6,38 9,37 L9,37 C6,37 5,35 5,32 C5,28 6,26 9,26 L9,26 C6,26 5,22 5,19 C5,16 8,14 11,14 L23,14 C24,14 25,12 25,11 L25,10 C25,7 28,4 31,4" id="Shape" fill="#A1ACBA"/>
+ </symbol>
+ </svg>
+ <span class="o_reward_face" t-attf-style="background-image:url(#{widget.options.img_url});"/>
+
+ <svg class="o_reward_thumbup" viewBox="0 0 42 60">
+ <use href="#thumb"/>
+ </svg>
+
+ <div class="o_reward_msg_container">
+ <svg class="o_reward_thumb_right" viewBox="0 0 100 55">
+ <use href="#thumb" transform="scale(-1, 1.2) rotate(-90) translate(-42,-60)"/>
+ </svg>
+ <div class="o_reward_msg">
+ <div class="o_reward_msg_card">
+ <div class="o_reward_msg_content"/>
+ <div class="o_reward_shadow_container">
+ <div class="o_reward_shadow"/>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ </div>
+ </t>
+ <t t-name="rainbow_man.star">
+ <svg t-attf-class="star{{ star }}" width="35px" height="34px" viewBox="0 0 35 34">
+ <path fill="#fff" d="M33,15.9 C26.3557814,13.6951256 21.1575294,8.45974313 19,1.8 C19,1.24771525 18.5522847,0.8 18,0.8 C17.4477153,0.8 17,1.24771525 17,1.8 C14.6431206,8.69377078 9.02624222,13.9736364 2,15.9 C1.36487254,15.9 0.85,16.4148725 0.85,17.05 C0.85,17.6851275 1.36487254,18.2 2,18.2 C8.6215326,20.3844521 13.8155479,25.5784674 16,32.2 C16,32.7522847 16.4477153,33.2 17,33.2 C17.5522847,33.2 18,32.7522847 18,32.2 C20.3568794,25.3062292 25.9737578,20.0263636 33,18.1 C33.6351275,18.1 34.15,17.5851275 34.15,16.95 C34.15,16.3148725 33.6351275,15.8 33,15.8"></path>
+ </svg>
+ </t>
+</templates>