diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/portal_rating/static/src | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/portal_rating/static/src')
7 files changed, 892 insertions, 0 deletions
diff --git a/addons/portal_rating/static/src/js/portal_chatter.js b/addons/portal_rating/static/src/js/portal_chatter.js new file mode 100644 index 00000000..fdf0bc8d --- /dev/null +++ b/addons/portal_rating/static/src/js/portal_chatter.js @@ -0,0 +1,352 @@ +odoo.define('rating.portal.chatter', function (require) { +'use strict'; + +var core = require('web.core'); +var portalChatter = require('portal.chatter'); +var utils = require('web.utils'); +var time = require('web.time'); + +var _t = core._t; +var PortalChatter = portalChatter.PortalChatter; +var qweb = core.qweb; + +/** + * PortalChatter + * + * Extends Frontend Chatter to handle rating + */ +PortalChatter.include({ + events: _.extend({}, PortalChatter.prototype.events, { + // star based control + 'click .o_website_rating_select': '_onClickStarDomain', + 'click .o_website_rating_select_text': '_onClickStarDomainReset', + // publisher comments + 'click .o_wrating_js_publisher_comment_btn': '_onClickPublisherComment', + 'click .o_wrating_js_publisher_comment_edit': '_onClickPublisherComment', + 'click .o_wrating_js_publisher_comment_delete': '_onClickPublisherCommentDelete', + 'click .o_wrating_js_publisher_comment_submit': '_onClickPublisherCommentSubmit', + 'click .o_wrating_js_publisher_comment_cancel': '_onClickPublisherCommentCancel', + }), + xmlDependencies: (PortalChatter.prototype.xmlDependencies || []) + .concat([ + '/portal_rating/static/src/xml/portal_tools.xml', + '/portal_rating/static/src/xml/portal_chatter.xml' + ]), + + /** + * @constructor + */ + init: function (parent, options) { + this._super.apply(this, arguments); + // options + if (!_.contains(this.options, 'display_rating')) { + this.options = _.defaults(this.options, { + 'display_rating': false, + 'rating_default_value': 0.0, + }); + } + // rating card + this.set('rating_card_values', {}); + this.set('rating_value', false); + this.on("change:rating_value", this, this._onChangeRatingDomain); + }, + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + /** + * Update the messages format + * + * @param {Array<Object>} messages + * @returns {Array} + */ + preprocessMessages: function (messages) { + var self = this; + messages = this._super.apply(this, arguments); + if (this.options['display_rating']) { + _.each(messages, function (m, i) { + m.rating_value = self.roundToHalf(m['rating_value']); + m.rating = self._preprocessCommentData(m.rating, i); + }); + } + // save messages in the widget to process correctly the publisher comment templates + this.messages = messages; + return messages; + }, + /** + * Round the given value with a precision of 0.5. + * + * Examples: + * - 1.2 --> 1.0 + * - 1.7 --> 1.5 + * - 1.9 --> 2.0 + * + * @param {Number} value + * @returns Number + **/ + roundToHalf: function (value) { + var converted = parseFloat(value); // Make sure we have a number + var decimal = (converted - parseInt(converted, 10)); + decimal = Math.round(decimal * 10); + if (decimal === 5) { + return (parseInt(converted, 10) + 0.5); + } + if ((decimal < 3) || (decimal > 7)) { + return Math.round(converted); + } else { + return (parseInt(converted, 10) + 0.5); + } + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * @override + */ + _chatterInit: function () { + var self = this; + return this._super.apply(this, arguments).then(function (result) { + if (!result['rating_stats']) { + return; + } + var ratingData = { + 'avg': Math.round(result['rating_stats']['avg'] * 100) / 100, + 'percent': [], + }; + _.each(_.keys(result['rating_stats']['percent']).reverse(), function (rating) { + ratingData['percent'].push({ + 'num': rating, + 'percent': utils.round_precision(result['rating_stats']['percent'][rating], 0.01), + }); + }); + self.set('rating_card_values', ratingData); + }); + }, + /** + * @override + */ + _messageFetchPrepareParams: function () { + var params = this._super.apply(this, arguments); + if (this.options['display_rating']) { + params['rating_include'] = true; + } + return params; + }, + + /** + * Default rating data for publisher comment qweb template + * @private + * @param {Integer} messageIndex + */ + _newPublisherCommentData: function (messageIndex) { + return { + mes_index: messageIndex, + publisher_id: this.options.partner_id, + publisher_avatar: _.str.sprintf('/web/image/%s/%s/image_128/50x50', 'res.partner', this.options.partner_id), + publisher_name: _t("Write your comment"), + publisher_datetime: '', + publisher_comment: '', + }; + }, + + /** + * preprocess the rating data comming from /website/rating/comment or the chatter_init + * Can be also use to have new rating data for a new publisher comment + * @param {JSON} rawRating + * @returns {JSON} the process rating data + */ + _preprocessCommentData: function (rawRating, messageIndex) { + var ratingData = { + id: rawRating.id, + mes_index: messageIndex, + publisher_datetime: rawRating.publisher_datetime ? moment(time.str_to_datetime(rawRating.publisher_datetime)).format('MMMM Do YYYY, h:mm:ss a') : "", + publisher_comment: rawRating.publisher_comment ? rawRating.publisher_comment : '', + }; + + // split array (id, display_name) of publisher_id into publisher_id and publisher_name + if (rawRating.publisher_id && rawRating.publisher_id.length >= 2) { + ratingData.publisher_id = rawRating.publisher_id[0]; + ratingData.publisher_name = rawRating.publisher_id[1]; + ratingData.publisher_avatar = _.str.sprintf('/web/image/%s/%s/image_128/50x50', 'res.partner', ratingData.publisher_id); + } + var commentData = _.extend(this._newPublisherCommentData(messageIndex), ratingData); + return commentData; + }, + + /** --------------- + * Selection of elements for the publisher comment feature + * Only available from a source in a publisher_comment or publisher_comment_form template + */ + + _getCommentContainer: function ($source) { + return $source.parents(".o_wrating_publisher_container").first().find(".o_wrating_publisher_comment").first(); + }, + + _getCommentButton: function ($source) { + return $source.parents(".o_wrating_publisher_container").first().find(".o_wrating_js_publisher_comment_btn").first(); + }, + + _getCommentTextarea: function ($source) { + return $source.parents(".o_wrating_publisher_container").first().find(".o_portal_rating_comment_input").first(); + }, + + _focusTextComment: function ($source) { + this._getCommentTextarea($source).focus(); + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * @private + * @param {MouseEvent} ev + */ + _onClickStarDomain: function (ev) { + var $tr = this.$(ev.currentTarget); + var num = $tr.data('star'); + if ($tr.css('opacity') === '1') { + this.set('rating_value', num); + this.$('.o_website_rating_select').css({ + 'opacity': 0.5, + }); + this.$('.o_website_rating_select_text[data-star="' + num + '"]').css({ + 'visibility': 'visible', + 'opacity': 1, + }); + this.$('.o_website_rating_select[data-star="' + num + '"]').css({ + 'opacity': 1, + }); + } + }, + /** + * @private + * @param {MouseEvent} ev + */ + _onClickStarDomainReset: function (ev) { + ev.stopPropagation(); + ev.preventDefault(); + this.set('rating_value', false); + this.$('.o_website_rating_select_text').css('visibility', 'hidden'); + this.$('.o_website_rating_select').css({ + 'opacity': 1, + }); + }, + + /** + * @private + * @param {MouseEvent} ev + */ + _onClickPublisherComment: function (ev) { + var $source = this.$(ev.currentTarget); + // If the form is already present => like cancel remove the form + if (this._getCommentTextarea($source).length === 1) { + this._getCommentContainer($source).empty(); + return; + } + var messageIndex = $source.data("mes_index"); + var data = {is_publisher: this.options['is_user_publisher']}; + data.rating = this._newPublisherCommentData(messageIndex); + + var oldRating = this.messages[messageIndex].rating; + data.rating.publisher_comment = oldRating.publisher_comment ? oldRating.publisher_comment : ''; + this._getCommentContainer($source).html($(qweb.render("portal_rating.chatter_rating_publisher_form", data))); + this._focusTextComment($source); + }, + + /** + * @private + * @param {MouseEvent} ev + */ + _onClickPublisherCommentDelete: function (ev) { + var self = this; + var $source = this.$(ev.currentTarget); + + var messageIndex = $source.data("mes_index"); + var ratingId = this.messages[messageIndex].rating.id; + + this._rpc({ + route: '/website/rating/comment', + params: { + "rating_id": ratingId, + "publisher_comment": '' // Empty publisher comment means no comment + } + }).then(function (res) { + self.messages[messageIndex].rating = self._preprocessCommentData(res, messageIndex); + self._getCommentButton($source).removeClass("d-none"); + self._getCommentContainer($source).empty(); + }); + }, + + /** + * @private + * @param {MouseEvent} ev + */ + _onClickPublisherCommentSubmit: function (ev) { + var self = this; + var $source = this.$(ev.currentTarget); + + var messageIndex = $source.data("mes_index"); + var comment = this._getCommentTextarea($source).val(); + var ratingId = this.messages[messageIndex].rating.id; + + this._rpc({ + route: '/website/rating/comment', + params: { + "rating_id": ratingId, + "publisher_comment": comment + } + }).then(function (res) { + + // Modify the related message + self.messages[messageIndex].rating = self._preprocessCommentData(res, messageIndex); + if (self.messages[messageIndex].rating.publisher_comment !== '') { + // Remove the button comment if exist and render the comment + self._getCommentButton($source).addClass('d-none'); + self._getCommentContainer($source).html($(qweb.render("portal_rating.chatter_rating_publisher_comment", { + rating: self.messages[messageIndex].rating, + is_publisher: self.options.is_user_publisher + }))); + } else { + // Empty string or false considers as no comment + self._getCommentButton($source).removeClass("d-none"); + self._getCommentContainer($source).empty(); + } + }); + }, + + /** + * @private + * @param {MouseEvent} ev + */ + _onClickPublisherCommentCancel: function (ev) { + var $source = this.$(ev.currentTarget); + var messageIndex = $source.data("mes_index"); + + var comment = this.messages[messageIndex].rating.publisher_comment; + if (comment) { + var data = { + rating: this.messages[messageIndex].rating, + is_publisher: this.options.is_user_publisher + }; + this._getCommentContainer($source).html($(qweb.render("portal_rating.chatter_rating_publisher_comment", data))); + } else { + this._getCommentContainer($source).empty(); + } + }, + + /** + * @private + */ + _onChangeRatingDomain: function () { + var domain = []; + if (this.get('rating_value')) { + domain = [['rating_value', '=', this.get('rating_value')]]; + } + this._changeCurrentPage(1, domain); + }, +}); +}); diff --git a/addons/portal_rating/static/src/js/portal_composer.js b/addons/portal_rating/static/src/js/portal_composer.js new file mode 100644 index 00000000..3524a0a8 --- /dev/null +++ b/addons/portal_rating/static/src/js/portal_composer.js @@ -0,0 +1,125 @@ +odoo.define('rating.portal.composer', function (require) { +'use strict'; + +var core = require('web.core'); +var portalComposer = require('portal.composer'); + +var _t = core._t; + +var PortalComposer = portalComposer.PortalComposer; + +/** + * PortalComposer + * + * Extends Portal Composer to handle rating submission + */ +PortalComposer.include({ + events: _.extend({}, PortalComposer.prototype.events, { + 'click .stars i': '_onClickStar', + 'mouseleave .stars': '_onMouseleaveStarBlock', + 'mousemove .stars i': '_onMoveStar', + 'mouseleave .stars i': '_onMoveLeaveStar', + }), + + /** + * @constructor + */ + init: function (parent, options) { + this._super.apply(this, arguments); + + // apply ratio to default rating value + if (options.default_rating_value) { + options.default_rating_value = parseFloat(options.default_rating_value); + } + + // default options + this.options = _.defaults(this.options, { + 'default_message': false, + 'default_message_id': false, + 'default_rating_value': 0.0, + 'force_submit_url': false, + }); + // star input widget + this.labels = { + '0': "", + '1': _t("I hate it"), + '2': _t("I don't like it"), + '3': _t("It's okay"), + '4': _t("I like it"), + '5': _t("I love it"), + }; + this.user_click = false; // user has click or not + this.set("star_value", this.options.default_rating_value); + this.on("change:star_value", this, this._onChangeStarValue); + }, + /** + * @override + */ + start: function () { + var self = this; + return this._super.apply(this, arguments).then(function () { + // rating stars + self.$input = self.$('input[name="rating_value"]'); + self.$star_list = self.$('.stars').find('i'); + + // set the default value to trigger the display of star widget and update the hidden input value. + self.set("star_value", self.options.default_rating_value); + self.$input.val(self.options.default_rating_value); + }); + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * @private + */ + _onChangeStarValue: function () { + var val = this.get("star_value"); + var index = Math.floor(val); + var decimal = val - index; + // reset the stars + this.$star_list.removeClass('fa-star fa-star-half-o').addClass('fa-star-o'); + + this.$('.stars').find("i:lt(" + index + ")").removeClass('fa-star-o fa-star-half-o').addClass('fa-star'); + if (decimal) { + this.$('.stars').find("i:eq(" + index + ")").removeClass('fa-star-o fa-star fa-star-half-o').addClass('fa-star-half-o'); + } + this.$('.rate_text .badge').text(this.labels[index]); + }, + /** + * @private + */ + _onClickStar: function (ev) { + var index = this.$('.stars i').index(ev.currentTarget); + this.set("star_value", index + 1); + this.user_click = true; + this.$input.val(this.get("star_value")); + }, + /** + * @private + */ + _onMouseleaveStarBlock: function () { + this.$('.rate_text').hide(); + }, + /** + * @private + * @param {MouseEvent} ev + */ + _onMoveStar: function (ev) { + var index = this.$('.stars i').index(ev.currentTarget); + this.$('.rate_text').show(); + this.set("star_value", index + 1); + }, + /** + * @private + */ + _onMoveLeaveStar: function () { + if (!this.user_click) { + this.set("star_value", parseInt(this.$input.val())); + } + this.user_click = false; + }, +}); +}); diff --git a/addons/portal_rating/static/src/js/portal_rating_composer.js b/addons/portal_rating/static/src/js/portal_rating_composer.js new file mode 100644 index 00000000..517b18aa --- /dev/null +++ b/addons/portal_rating/static/src/js/portal_rating_composer.js @@ -0,0 +1,73 @@ +odoo.define('portal.rating.composer', function (require) { +'use strict'; + +var publicWidget = require('web.public.widget'); +var session = require('web.session'); +var portalComposer = require('portal.composer'); + +var PortalComposer = portalComposer.PortalComposer; + +/** + * RatingPopupComposer + * + * Display the rating average with a static star widget, and open + * a popup with the portal composer when clicking on it. + **/ +var RatingPopupComposer = publicWidget.Widget.extend({ + template: 'portal_rating.PopupComposer', + xmlDependencies: [ + '/portal/static/src/xml/portal_chatter.xml', + '/portal_rating/static/src/xml/portal_tools.xml', + '/portal_rating/static/src/xml/portal_rating_composer.xml', + ], + + init: function (parent, options) { + this._super.apply(this, arguments); + this.rating_avg = Math.round(options['ratingAvg'] * 100) / 100 || 0.0; + this.rating_total = options['ratingTotal'] || 0.0; + + this.options = _.defaults({}, options, { + 'token': false, + 'res_model': false, + 'res_id': false, + 'pid': 0, + 'display_composer': options['disable_composer'] ? false : !session.is_website_user, + 'display_rating': true, + 'csrf_token': odoo.csrf_token, + 'user_id': session.user_id, + }); + }, + /** + * @override + */ + start: function () { + var defs = []; + defs.push(this._super.apply(this, arguments)); + + // instanciate and insert composer widget + this._composer = new PortalComposer(this, this.options); + defs.push(this._composer.replace(this.$('.o_portal_chatter_composer'))); + + return Promise.all(defs); + }, +}); + +publicWidget.registry.RatingPopupComposer = publicWidget.Widget.extend({ + selector: '.o_rating_popup_composer', + + /** + * @override + */ + start: function () { + var ratingPopupData = this.$el.data(); + var ratingPopup = new RatingPopupComposer(this, ratingPopupData); + return Promise.all([ + this._super.apply(this, arguments), + ratingPopup.appendTo(this.$el) + ]); + }, +}); + +return RatingPopupComposer; + +}); diff --git a/addons/portal_rating/static/src/scss/portal_rating.scss b/addons/portal_rating/static/src/scss/portal_rating.scss new file mode 100644 index 00000000..6d02b1ca --- /dev/null +++ b/addons/portal_rating/static/src/scss/portal_rating.scss @@ -0,0 +1,87 @@ +/* static stars */ +$o-w-rating-star-color: #FACC2E; +.o_website_rating_static{ + color: $o-w-rating-star-color; +} + +.o_website_rating_card_container { + + .o_message_counter { + color: gray('700'); + } + + /* progress bars */ + table.o_website_rating_progress_table { + width: 100%; + overflow: visible; + + .o_website_rating_table_star_num { + min-width: 50px; + white-space: nowrap; + } + .o_website_rating_select[style*="opacity: 1"] { + cursor: pointer; + } + .o_website_rating_table_progress{ + min-width: 120px; + > .progress { + margin-bottom: 5px; + margin-left: 5px; + margin-right: 5px; + } + .o_rating_progressbar{ + background-color: $o-w-rating-star-color; + } + } + .o_website_rating_table_percent { + text-align: right; + padding-left: 5px; + font-size: $font-size-sm; + } + .o_website_rating_table_reset { + .o_website_rating_select_text { + visibility: hidden; + } + } + } + +} + +/* Star Widget */ +.o_rating_star_card{ + margin-bottom: 5px; + .stars { + display: inline-block; + color: #FACC2E; + margin-right: 15px; + } + + .stars i { + margin-right: -3px; + text-align: center; + } + + .stars.enabled{ + cursor: pointer; + } + + .rate_text{ + display: inline-block; + } +} + +/* Rating Popup Composer */ +.o_rating_popup_composer { + + .o_rating_clickable { + cursor: pointer; + } + + .o_portal_chatter_avatar { + margin-right: 10px; + } +} + +.o_rating_popup_composer_label { + color: color-yiq(white); +} diff --git a/addons/portal_rating/static/src/xml/portal_chatter.xml b/addons/portal_rating/static/src/xml/portal_chatter.xml new file mode 100644 index 00000000..93d0122c --- /dev/null +++ b/addons/portal_rating/static/src/xml/portal_chatter.xml @@ -0,0 +1,116 @@ +<?xml version="1.0" encoding="UTF-8"?> +<templates id="template" xml:space="preserve"> + <!-- + Inherited templates from portal to custom chatter with rating + --> + <t t-extend="portal.Composer"> + <t t-jquery="textarea" t-operation="inner"><t t-esc="widget.options['default_message'] ? _.str.trim(widget.options['default_message']) : ''"/></t><!-- need to be one line to avoid \t in textarea --> + <t t-jquery="form" t-operation="attributes"> + <attribute name="t-attf-action">#{widget.options['force_submit_url'] ? widget.options['force_submit_url'] : '/mail/chatter_post'}</attribute> + </t> + <t t-jquery=".o_portal_chatter_composer_form input[name='csrf_token']" t-operation="after"> + <t t-call="portal_rating.rating_star_input"> + <t t-set="default_rating" t-value="widget.options['default_rating_value']"/> + </t> + <input type="hidden" name="message_id" t-att-value="widget.options['default_message_id']" t-if="widget.options['default_message_id']"/> + </t> + </t> + + <t t-extend="portal.chatter_messages"> + <t t-jquery="t[t-raw='message.body']" t-operation="before"> + <t t-if="message['rating_value']"> + <t t-call="portal_rating.rating_stars_static"> + <t t-set="val" t-value="message.rating_value"/> + </t> + </t> + </t> + <t t-jquery=".o_portal_chatter_attachments" t-operation="after"> + <!--Only possible if a rating is link to the message, for now we can't comment if no rating + is link to the message (because publisher comment data + is on the rating.rating model - one comment max) --> + <t t-if="message.rating and message.rating.id" t-call="portal_rating.chatter_rating_publisher"> + <t t-set="is_publisher" t-value="widget.options['is_user_publisher']"/> + <t t-set="rating" t-value="message.rating"/> + </t> + </t> + </t> + + <t t-extend="portal.Chatter"> + <t t-jquery="t[t-call='portal.chatter_message_count']" t-operation="replace"> + <t t-if="widget.options['display_rating']"> + <t t-call="portal_rating.rating_card"/> + </t> + <t t-if="!widget.options['display_rating']"> + <t t-call="portal.chatter_message_count"/> + </t> + </t> + </t> + + <!-- + New templates specific of rating in Chatter + --> + <t t-name="portal_rating.chatter_rating_publisher"> + <div class="o_wrating_publisher_container"> + <button t-if="is_publisher" + t-attf-class="btn px-2 mb-2 btn-sm border o_wrating_js_publisher_comment_btn {{ rating.publisher_comment !== '' ? 'd-none' : '' }}" + t-att-data-mes_index="rating.mes_index"> + <i class="fa fa-comment text-muted mr-1"/>Comment + </button> + <div class="o_wrating_publisher_comment mt-2 mb-2"> + <t t-if="rating.publisher_comment" t-call="portal_rating.chatter_rating_publisher_comment"/> + </div> + </div> + </t> + + <t t-name="portal_rating.chatter_rating_publisher_comment"> + <div class="media o_portal_chatter_message"> + <img class="o_portal_chatter_avatar" t-att-src="rating.publisher_avatar" alt="avatar"/> + <div class="media-body"> + <div class="o_portal_chatter_message_title"> + <div class="d-inline-block"> + <h5 class="mb-1"><t t-esc="rating.publisher_name"/></h5> + </div> + <div t-if="is_publisher" class="dropdown d-inline-block"> + <button class="btn py-0" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> + <i class="fa fa-ellipsis-v"/> + </button> + <div class="dropdown-menu" aria-labelledby="dropdownMenuButton"> + <button class="dropdown-item o_wrating_js_publisher_comment_edit" t-att-data-mes_index="rating.mes_index"> + <i class="fa fa-fw fa-pencil mr-1"/>Edit + </button> + <button class="dropdown-item o_wrating_js_publisher_comment_delete" t-att-data-mes_index="rating.mes_index"> + <i class="fa fa-fw fa-trash-o mr-1"/>Delete + </button> + </div> + </div> + <p>Published on <t t-esc="rating.publisher_datetime"/></p> + </div> + <t t-esc="rating.publisher_comment"/> + </div> + </div> + </t> + <t t-name="portal_rating.chatter_rating_publisher_form"> + <div t-if="is_publisher" class="media o_portal_chatter_message shadow bg-white rounded px-3 py-3 my-1"> + <img class="o_portal_chatter_avatar" t-att-src="rating.publisher_avatar" alt="avatar"/> + <div class="media-body"> + <div class="o_portal_chatter_message_title"> + <h5 class='mb-1'><t t-esc="rating.publisher_name"/></h5> + <p>Published on <t t-esc="rating.publisher_datetime"/></p> + </div> + <textarea rows="3" class="form-control o_portal_rating_comment_input"><t t-esc="rating.publisher_comment"/></textarea> + <div> + <button class="btn btn-primary mt-2 o_wrating_js_publisher_comment_submit" t-att-data-mes_index="rating.mes_index"> + <t t-if="rating.publisher_comment === ''"> + Post comment + </t><t t-else=""> + Update comment + </t> + </button> + <button class="border btn btn-light mt-2 bg-white o_wrating_js_publisher_comment_cancel" t-att-data-mes_index="rating.mes_index"> + Cancel + </button> + </div> + </div> + </div> + </t> +</templates> diff --git a/addons/portal_rating/static/src/xml/portal_rating_composer.xml b/addons/portal_rating/static/src/xml/portal_rating_composer.xml new file mode 100644 index 00000000..d9d46d99 --- /dev/null +++ b/addons/portal_rating/static/src/xml/portal_rating_composer.xml @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="UTF-8"?> +<templates id="template" xml:space="preserve"> + <!-- + Popup Rating Composer Widget + It can alse be used to modify a message + --> + <t t-name="portal_rating.PopupComposer"> + <div class="d-flex flex-wrap align-items-center"> + <div class="text-nowrap"> + <t t-call="portal_rating.rating_stars_static"> + <t t-set="val" t-value="widget.rating_avg || 0"/> + <t t-set="inline_mode" t-value="true"/> + </t> + </div> + <button t-if="widget.options['display_composer']" type="button" + t-att-class="'btn btn-sm mx-3 ' + widget.options['link_btn_classes'] or 'btn-primary'" + data-toggle="modal" data-target="#ratingpopupcomposer"> + <t t-if="widget.options['display_composer']"> + <t t-if="widget.options['default_message_id']"> + Modify your review + </t> + <t t-else=""> + Add a review + </t> + </t> + </button> + </div> + + <div t-if="widget.options['display_composer']" class="modal fade" id="ratingpopupcomposer" tabindex="-1" role="dialog" aria-labelledby="ratingpopupcomposerlabel" aria-hidden="true"> + <div class="modal-dialog" role="document"> + <div class="modal-content"> + <div class="modal-header"> + <h5 class="modal-title o_rating_popup_composer_label" id="ratingpopupcomposerlabel"> + <t t-if="widget.options['default_message_id']"> + Modify your review + </t> + <t t-else=""> + Add a review + </t> + </h5> + <button type="button" class="close" data-dismiss="modal" aria-label="Close"> + <span>×</span> + </button> + </div> + <div class="modal-body"> + <div class="o_portal_chatter_composer"/> + </div> + </div> + </div> + </div> + </t> +</templates> diff --git a/addons/portal_rating/static/src/xml/portal_tools.xml b/addons/portal_rating/static/src/xml/portal_tools.xml new file mode 100644 index 00000000..9f314360 --- /dev/null +++ b/addons/portal_rating/static/src/xml/portal_tools.xml @@ -0,0 +1,87 @@ +<?xml version="1.0" encoding="UTF-8"?> +<templates id="template" xml:space="preserve"> + <t t-name="portal_rating.rating_stars_static"> + <t t-set="val_integer" t-value="Math.floor(val)"/> + <t t-set="val_decimal" t-value="val - val_integer"/> + <t t-set="empty_star" t-value="5 - (val_integer+Math.ceil(val_decimal))"/> + <div class="o_website_rating_static" t-att-style="inline_mode ? 'display:inline' : ''" t-attf-aria-label="#{val} stars on 5" t-attf-title="#{val} stars on 5"> + <t t-foreach="_.range(0, val_integer)" t-as="num"> + <i class="fa fa-star" role="img"></i> + </t> + <t t-if="val_decimal"> + <i class="fa fa-star-half-o" role="img"></i> + </t> + <t t-foreach="_.range(0, empty_star)" t-as="num"> + <i class="fa fa-star text-black-25" role="img"></i> + </t> + </div> + </t> + + <t t-name="portal_rating.rating_card"> + <div class="row o_website_rating_card_container"> + <div class="col-lg-3 offset-lg-2" t-if="!_.isEmpty(widget.get('rating_card_values'))"> + <p><strong>Average</strong></p> + <div class="o_website_rating_avg text-center"> + <h1><t t-esc="widget.get('rating_card_values')['avg']"/></h1> + <t t-call="portal_rating.rating_stars_static"> + <t t-set="val" t-value="widget.get('rating_card_values')['avg'] || 0"/> + </t> + <t t-call="portal.chatter_message_count"/> + </div> + </div> + <div class="col-lg-6" t-if="!_.isEmpty(widget.get('rating_card_values'))"> + <p><strong>Details</strong></p> + <div class="o_website_rating_progress_bars"> + <table class="o_website_rating_progress_table"> + <t t-foreach="widget.get('rating_card_values')['percent']" t-as="percent"> + <tr class="o_website_rating_select" t-att-data-star="percent['num']" style="opacity: 1"> + <td class="o_website_rating_table_star_num" t-att-data-star="percent['num']"> + <t t-esc="percent['num']"/> stars + </td> + <td class="o_website_rating_table_progress"> + <div class="progress"> + <div class="progress-bar o_rating_progressbar" role="progressbar" t-att-aria-valuenow="percent['percent']" aria-valuemin="0" aria-valuemax="100" t-att-style="'width:' + percent['percent'] + '%;'"> + </div> + </div> + </td> + <td class="o_website_rating_table_percent"> + <strong><t t-esc="Math.round(percent['percent'] * 100) / 100"/>%</strong> + </td> + <td class="o_website_rating_table_reset"> + <button class="btn btn-link o_website_rating_select_text" t-att-data-star="percent['num']"> + <i class="fa fa-times d-block d-sm-none" role="img" aria-label="Remove Selection"/> + <span class="d-none d-sm-block">Remove Selection</span> + </button> + </td> + </tr> + </t> + </table> + </div> + </div> + </div> + </t> + + <t t-name="portal_rating.rating_star_input"> + <div class="o_rating_star_card" t-if="widget.options['display_rating']"> + <t t-set="val_integer" t-value="Math.floor(default_rating)"/> + <t t-set="val_decimal" t-value="default_rating - val_integer"/> + <t t-set="empty_star" t-value="5 - (val_integer+Math.ceil(val_decimal))"/> + + <div class="stars enabled"> + <t t-foreach="_.range(0, val_integer)" t-as="num"> + <i class="fa fa-star" role="img" aria-label="One star" title="One star"></i> + </t> + <t t-if="val_decimal"> + <i class="fa fa-star-half-o" role="img" aria-label="Half a star" title="Half a star"></i> + </t> + <t t-foreach="_.range(0, empty_star)" t-as="num" role="img" t-attf-aria-label="#{empty_star} on 5" t-attf-title="#{empty_star} on 5"> + <i class="fa fa-star-o text-black-25"></i> + </t> + </div> + <div class="rate_text"> + <span class="badge badge-info"></span> + </div> + <input type="hidden" readonly="readonly" name="rating_value" t-att-value="default_rating || ''"/> + </div> + </t> +</templates> |
