diff --git a/addons/website_sale_slides/__manifest__.py b/addons/website_sale_slides/__manifest__.py index 951611eba629e4d78be33541b130e74d82705e43..d37fb3dbcda2d1b4bcb9c6008507206a652d64e7 100644 --- a/addons/website_sale_slides/__manifest__.py +++ b/addons/website_sale_slides/__manifest__.py @@ -11,6 +11,7 @@ 'installable': True, 'auto_install': True, 'data': [ + 'views/assets.xml', 'views/slide_channel_views.xml', 'views/website_slides_templates.xml', ], diff --git a/addons/website_sale_slides/static/src/js/slides_course_quiz.js b/addons/website_sale_slides/static/src/js/slides_course_quiz.js new file mode 100644 index 0000000000000000000000000000000000000000..9dc0154596a5a09567c666d0307fa46f029ff00c --- /dev/null +++ b/addons/website_sale_slides/static/src/js/slides_course_quiz.js @@ -0,0 +1,25 @@ +odoo.define('website_sale_slides.quiz', function (require) { +"use strict"; + +var sAnimations = require('website.content.snippets.animation'); +var Quiz = require('website_slides.quiz'); + +sAnimations.registry.websiteSlidesQuizNoFullscreen.include({ + _extractChannelData: function (slideData){ + return _.extend({}, this._super.apply(this, arguments), { + productId: slideData.productId, + enroll: slideData.enroll, + currencyName: slideData.currencyName, + currencySymbol: slideData.currencySymbol, + price: slideData.price, + hasDiscountedPrice: slideData.hasDiscountedPrice + }); + } +}); + +Quiz.include({ + xmlDependencies: (Quiz.prototype.xmlDependencies || []).concat( + ["/website_sale_slides/static/src/xml/website_sale_slides_quiz.xml"] + ) +}); +}); diff --git a/addons/website_sale_slides/static/src/xml/website_sale_slides_quiz.xml b/addons/website_sale_slides/static/src/xml/website_sale_slides_quiz.xml new file mode 100644 index 0000000000000000000000000000000000000000..bcf0dc5a032dd8b26566f6f21535c176e84b4479 --- /dev/null +++ b/addons/website_sale_slides/static/src/xml/website_sale_slides_quiz.xml @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="UTF-8"?> +<templates xml:space="preserve"> + <t t-extend="slide.slide.quiz.validation"> + <t t-jquery="#validation" t-operation="append"> + <div t-if="widget.readonly && widget.publicUser && widget.channel.channelEnroll == 'payment'" class="alert alert-info d-flex align-items-center justify-content-between"> + <div> + <b class="h5 mb-0">Sign in and buy the course to join it and take the quiz!</b> + <span class="my-0 h4" style="line-height: 1"> + <span title="Succeed and gain karma" aria-label="Succeed and gain karma" class="badge badge-pill badge-warning text-white font-weight-bold ml-3 px-2 py-1"> + + <t t-esc="widget.quiz.quizKarmaGain"/> XP + </span> + </span> + </div> + <div> + <a t-att-href="'/web/login?redirect=' + widget.redirectURL" class="btn btn-primary font-weight-bold text-uppercase">Sign in</a> + <span t-if="widget.channel.signupAllowed" class="d-block mt-2">Don't have an account ? <a class="font-weight-bold" t-att-href="'/web/signup?redirect=' + widget.url">Sign Up !</a></span> + </div> + </div> + <div t-if="!widget.publicUser && widget.channel.channelEnroll == 'payment'" class="card col-md-3"> + <div class="card-body d-flex flex-column align-items-center"> + <a role="button" + class="btn btn-primary btn-block o_wslides_js_course_join_link d-block mb-2" + title="Start Course" aria-label="Start Course Channel" + t-attf-href="/shop/cart/update?product_id=#{widget.channel.productId}&express=1" + t-att-data-channel-id="widget.slide.channelId"> + <span class="text-white text-uppercase font-weight-bold"> + Buy the course + </span> + </a> + <div class="text-center"> + <del t-if="widget.channel.hasDiscountedPrice" + class="text-600 text-nowrap oe_default_price d-inline" + t-esc="widget.channel.listPrice"/> + <span class="oe_price font-weight-bold text-nowrap h3 my-2" + t-esc="widget.channel.price"/> + <span t-if="widget.channel.currencySymbol" class="text-nowrap font-weight-bold h3 my-2" itemprop="priceCurrency" t-esc="widget.channel.currencySymbol"/> + <span t-else="" class="text-nowrap ml-1" itemprop="priceCurrency" t-esc="widget.channel.currencyName"/> + </div> + </div> + </div> + </t> + </t> +</templates> diff --git a/addons/website_sale_slides/views/assets.xml b/addons/website_sale_slides/views/assets.xml new file mode 100644 index 0000000000000000000000000000000000000000..ce0ecddc580599efa55eaf6635f08590b6dcf8ff --- /dev/null +++ b/addons/website_sale_slides/views/assets.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" ?> +<odoo> + <data> + <template id="assets_frontend" inherit_id="website.assets_frontend" name="Buy course on quiz"> + <xpath expr="//script[last()]" position="after"> + <script type="text/javascript" src="/website_sale_slides/static/src/js/slides_course_quiz.js"/> + </xpath> + </template> + </data> +</odoo> diff --git a/addons/website_sale_slides/views/website_slides_templates.xml b/addons/website_sale_slides/views/website_slides_templates.xml index 39c6747ed2642cf6dc0bb4d70bbe10a287716cb2..a4f885182e7a1937fd5ab4a27486f7ccb5d3ac26 100644 --- a/addons/website_sale_slides/views/website_slides_templates.xml +++ b/addons/website_sale_slides/views/website_slides_templates.xml @@ -31,4 +31,26 @@ </xpath> </template> +<template id="lesson_content_quiz" inherit_id="website_slides.lesson_content_quiz"> + <xpath expr="//div[hasclass('o_wslides_js_lesson_quiz')]" position="attributes"> + <attribute name="t-att-data-price">product_info['price'] if product_info else None</attribute> + <attribute name="t-att-data-currency-name">product_info['currency_id'].name if product_info else None</attribute> + <attribute name="t-att-data-currency-symbol">product_info['currency_id'].symbol if product_info else None</attribute> + <attribute name="t-att-data-has-discounted-price">product_info['has_discounted_price'] if product_info else None</attribute> + <attribute name="t-att-data-product-id">slide.channel_id.product_id.id if slide.channel_id.product_id else None</attribute> + <attribute name="t-att-data-list-price">product_info['list_price'] if product_info else None</attribute> + </xpath> +</template> + +<template id="slide_fullscreen" inherit_id="website_slides.slide_fullscreen"> + <xpath expr="//div[hasclass('o_wslides_fs_main')]" position="attributes"> + <attribute name="t-att-data-price">product_info['price'] if product_info else None</attribute> + <attribute name="t-att-data-currency-name">product_info['currency_id'].name if product_info else None</attribute> + <attribute name="t-att-data-currency-symbol">product_info['currency_id'].symbol if product_info else None</attribute> + <attribute name="t-att-data-has-discounted-price">product_info['has_discounted_price'] if product_info else None</attribute> + <attribute name="t-att-data-product-id">slide.channel_id.product_id.id if product_info else None</attribute> + <attribute name="t-att-data-list-price">product_info['list_price'] if product_info else None</attribute> + </xpath> +</template> + </data></odoo> diff --git a/addons/website_slides/controllers/main.py b/addons/website_slides/controllers/main.py index dfcc4e4e7dee3831de9d568a2dc6143b9f4e37ec..5b690682d359d23680cf6808f5917339e2731fbf 100644 --- a/addons/website_slides/controllers/main.py +++ b/addons/website_slides/controllers/main.py @@ -537,6 +537,12 @@ class WebsiteSlides(WebsiteProfile): 'search_uncategorized': kwargs.get('search_uncategorized') }) + values['channel'] = slide.channel_id + values = self._prepare_additional_channel_values(values, **kwargs) + values.pop('channel', None) + + values['signup_allowed'] = request.env['res.users'].sudo()._get_signup_invitation_scope() == 'b2c' + if kwargs.get('fullscreen') == '1': return request.render("website_slides.slide_fullscreen", values) return request.render("website_slides.slide_main", values) diff --git a/addons/website_slides/static/src/js/slides_course_fullscreen_player.js b/addons/website_slides/static/src/js/slides_course_fullscreen_player.js index 91ff6bdac5d1d2bc2cd3f7785fa403378306fb6c..e6118b5f0823be3d2f9e606cab2d9983eafaf7f9 100644 --- a/addons/website_slides/static/src/js/slides_course_fullscreen_player.js +++ b/addons/website_slides/static/src/js/slides_course_fullscreen_player.js @@ -299,14 +299,15 @@ odoo.define('website_slides.fullscreen', function (require) { * @param {Object} slides Contains the list of all slides of the course * @param {integer} defaultSlideId Contains the ID of the slide requested by the user */ - init: function (el, slides, defaultSlideId){ + init: function (parent, slides, defaultSlideId, channelData){ var result = this._super.apply(this,arguments); this.initialSlideID = defaultSlideId; this.slides = this._preprocessSlideData(slides); - + this.channel = channelData; var slide; + var urlParams = $.deparam.querystring(); if (defaultSlideId) { - slide = findSlide(this.slides, {id: defaultSlideId}); + slide = findSlide(this.slides, {id: defaultSlideId, isQuiz: urlParams.quiz === "1" }); } else { slide = this.slides[0]; } @@ -417,7 +418,11 @@ odoo.define('website_slides.fullscreen', function (require) { urlParts[urlParts.length-1] = this.get('slide').slug; var url = urlParts.join('/'); this.$('.o_wslides_fs_exit_fullscreen').attr('href', url); - var fullscreenUrl = _.str.sprintf('%s?fullscreen=1', url); + var params = {'fullscreen': 1 }; + if (this.get('slide').isQuiz){ + params.quiz = 1; + } + var fullscreenUrl = _.str.sprintf('%s?%s', url, $.param(params)); history.pushState(null, '', fullscreenUrl); }, /** @@ -437,7 +442,7 @@ odoo.define('website_slides.fullscreen', function (require) { // display quiz slide, or quiz attached to a slide if (slide.type === 'quiz' || slide.isQuiz) { $content.addClass('bg-white'); - var QuizWidget = new Quiz(this, slide); + var QuizWidget = new Quiz(this, slide, this.channel); return QuizWidget.appendTo($content); } @@ -490,13 +495,13 @@ odoo.define('website_slides.fullscreen', function (require) { _onChangeSlide: function () { var self = this; var slide = this.get('slide'); + self._pushUrlState(); return this._fetchSlideContent().then(function() { // render content return self._renderSlide(); }).then(function() { if (slide._autoSetDone && !session.is_website_user) { // no useless RPC call self._setCompleted(slide.id); } - self._pushUrlState(); }); }, /** @@ -568,10 +573,13 @@ odoo.define('website_slides.fullscreen', function (require) { xmlDependencies: ['/website_slides/static/src/xml/website_slides_fullscreen.xml'], start: function (){ var defs = [this._super.apply(this, arguments)]; - var fullscreen = new Fullscreen(this, this._getSlides(), this._getCurrentSlideID()); + var fullscreen = new Fullscreen(this, this._getSlides(), this._getCurrentSlideID(), this._extractChannelData()); defs.push(fullscreen.attachTo(".o_wslides_fs_main")); return $.when.apply($, defs); }, + _extractChannelData: function (){ + return this.$el.data(); + }, _getCurrentSlideID: function (){ return parseInt(this.$('.o_wslides_fs_sidebar_list_item.active').data('id')); }, diff --git a/addons/website_slides/static/src/js/slides_course_join.js b/addons/website_slides/static/src/js/slides_course_join.js index 278d9c4672fd6b83c9ec9f405a17641a045fa0eb..61523850912e7b6b01e5a77420a26c4c8bdc3473 100644 --- a/addons/website_slides/static/src/js/slides_course_join.js +++ b/addons/website_slides/static/src/js/slides_course_join.js @@ -9,10 +9,17 @@ require('website_slides.slides'); var _t = core._t; var CourseJoinWidget = Widget.extend({ + template: 'slide.course.join', + xmlDependencies: ['/website_slides/static/src/xml/channel_management.xml'], events: { 'click .o_wslides_js_course_join_link': '_onClickJoin', }, + init: function (parent, channelId){ + this.channelId = channelId; + return this._super.apply(this, arguments); + }, + //-------------------------------------------------------------------------- // Private //-------------------------------------------------------------------------- @@ -42,7 +49,7 @@ var CourseJoinWidget = Widget.extend({ * @private */ _onClickJoin: function (event) { - var channelId = $(event.currentTarget).data('channel-id'); + var channelId = this.channelId || $(event.currentTarget).data('channel-id'); var self = this; this._rpc({ route: '/slides/channel/join', diff --git a/addons/website_slides/static/src/js/slides_course_quiz.js b/addons/website_slides/static/src/js/slides_course_quiz.js index 23a5435788015b4517ea0e186c56dc0ede17c006..0a8299d74e5176e7283e21eaf97d9ca62ef3fac3 100644 --- a/addons/website_slides/static/src/js/slides_course_quiz.js +++ b/addons/website_slides/static/src/js/slides_course_quiz.js @@ -3,6 +3,9 @@ odoo.define('website_slides.quiz', function (require) { var sAnimations = require('website.content.snippets.animation'); var core = require('web.core'); var Widget = require('web.Widget'); + var session = require('web.session'); + + var CourseJoinWidget = require('website_slides.course.join.widget').courseJoinWidget; var QWeb = core.qweb; var _t = core._t; @@ -34,7 +37,7 @@ odoo.define('website_slides.quiz', function (require) { * @param {Object} slide_data holding all the classic slide informations * @param {Object} quiz_data : optional quiz data to display. If not given, will be fetched. (questions and answers). */ - init: function (parent, slide_data, quiz_data) { + init: function (parent, slide_data, channel_data, quiz_data) { this.slide = _.defaults(slide_data, { id: 0, name: '', @@ -44,6 +47,9 @@ odoo.define('website_slides.quiz', function (require) { }); this.quiz = quiz_data || false; this.readonly = slide_data.readonly || false; + this.publicUser = session.is_website_user; + this.redirectURL = encodeURIComponent(document.URL); + this.channel = channel_data; return this._super.apply(this, arguments); }, @@ -69,6 +75,7 @@ odoo.define('website_slides.quiz', function (require) { self._renderAnswers(); self._renderAnswersHighlighting(); self._renderValidationInfo(); + new CourseJoinWidget(self, self.channel.channelId).appendTo(self.$('.o_wslides_course_join_widget')); }); }, @@ -285,6 +292,7 @@ odoo.define('website_slides.quiz', function (require) { var defs = [this._super.apply(this, arguments)]; this.$('.o_wslides_js_lesson_quiz').each(function () { var slideData = $(this).data(); + var channelData = self._extractChannelData(slideData); slideData.quizData = { questions: self._extractQuestionsAndAnswers(), quizKarmaMax: slideData.quizKarmaMax, @@ -292,7 +300,7 @@ odoo.define('website_slides.quiz', function (require) { quizKarmaGain: slideData.quizKarmaGain, quizAttemptsCount: slideData.quizAttemptsCount, }; - defs.push(new Quiz(self, slideData, slideData.quizData).attachTo($(this))); + defs.push(new Quiz(self, slideData, channelData, slideData.quizData).attachTo($(this))); }); return $.when.apply($, defs); }, @@ -318,6 +326,14 @@ odoo.define('website_slides.quiz', function (require) { // Private //--------------------------------------------------------------------- + _extractChannelData: function (slideData){ + return { + id: slideData.channelId, + channelEnroll: slideData.enroll, + signupAllowed: slideData.signupAllowed + }; + }, + /** * Extract data from exiting DOM rendered server-side, to have the list of questions with their * relative answers. diff --git a/addons/website_slides/static/src/xml/channel_management.xml b/addons/website_slides/static/src/xml/channel_management.xml new file mode 100644 index 0000000000000000000000000000000000000000..a616cc1baa1d9b1b33a4624c882f2ee0a0e85a00 --- /dev/null +++ b/addons/website_slides/static/src/xml/channel_management.xml @@ -0,0 +1,12 @@ +<templates> + <t t-name="slide.course.join"> + <div class="p-0 col-md-2"> + <a role="button" + class="btn btn-primary btn-block o_wslides_js_course_join_link text-uppercase font-weight-bold" + title="Start Course" aria-label="Start Course Channel" + href="#"> + <span class="cta-title text_small_caps">Join Course</span> + </a> + </div> + </t> +</templates> diff --git a/addons/website_slides/static/src/xml/slide_quiz.xml b/addons/website_slides/static/src/xml/slide_quiz.xml index 645ecae94cd27ce0c0dc645160e7ddef44905f91..f8f84d20d8ed49eb299f6f09cdeb84114da4d5e4 100644 --- a/addons/website_slides/static/src/xml/slide_quiz.xml +++ b/addons/website_slides/static/src/xml/slide_quiz.xml @@ -45,28 +45,42 @@ </t> <t t-name="slide.slide.quiz.validation"> - <div t-if="widget.readonly" class="alert alert-info"> - <b>Join course to take quiz</b> - <span class="my-0 h4" style="line-height: 1"> - <span title="Succeed and gain karma" aria-label="Succeed and gain karma" class="badge badge-pill badge-warning text-white font-weight-bold ml-3 px-2 py-1"> - + <t t-esc="widget.quiz.quizKarmaGain"/> XP - </span> - </span> - </div> - <div t-else="" class="d-flex align-items-center justify-content-between"> - <div t-att-class="'d-flex align-items-center' + (widget.slide.completed ? ' alert alert-success my-0 py-1 px-3' : '')"> - <button t-if="! widget.slide.completed" role="button" title="Check answers" aria-label="Check answers" - class="btn btn-primary text-uppercase font-weight-bold o_wslides_js_lesson_quiz_submit">Check your answers</button> - <b t-else="" class="my-0 h5">Done !</b> - <span class="my-0 h5" style="line-height: 1"> - <span role="button" title="Succeed and gain karma" aria-label="Succeed and gain karma" class="badge badge-pill badge-warning text-white font-weight-bold ml-3 px-2"> - + <t t-if="! widget.slide.completed" t-esc="widget.quiz.quizKarmaGain"/><t t-else="" t-esc="widget.quiz.quizKarmaWon"/> XP + <div id="validation"> + <div t-if="widget.readonly && !widget.publicUser && widget.channel.channelEnroll == 'public'" class="o_wslides_course_join_widget"> + <!-- Here comes the Join button rendered by the widget --> + </div> + <div t-if="widget.readonly && widget.publicUser && widget.channel.channelEnroll == 'public'" class="alert alert-info d-flex align-items-center justify-content-between"> + <div> + <b class="h5 mb-0">Sign in and join the course to take the quiz!</b> + <span class="my-0 h4" style="line-height: 1"> + <span title="Succeed and gain karma" aria-label="Succeed and gain karma" class="badge badge-pill badge-warning text-white font-weight-bold ml-3 px-2 py-1"> + + <t t-esc="widget.quiz.quizKarmaGain"/> XP + </span> + </span> + </div> + <div> + <a t-att-href="'/web/login?redirect=' + widget.redirectURL" class="btn btn-primary font-weight-bold text-uppercase">Sign in</a> + <span t-if="widget.channel.signupAllowed" class="d-block mt-2">Don't have an account ? <a class="font-weight-bold" t-att-href="'/web/signup?redirect=' + widget.url">Sign Up !</a></span> + </div> + </div> + <div t-if="widget.readonly && widget.channel.channelEnroll == 'invite'" class="alert alert-info"> + <b>This course is private. <a href="/contactus" class="font-weight-bold">Contact the website administrator</a> to enroll.</b> + </div> + <div t-if="!widget.readonly" class="d-flex align-items-center justify-content-between"> + <div t-att-class="'d-flex align-items-center' + (widget.slide.completed ? ' alert alert-success my-0 py-1 px-3' : '')"> + <button t-if="! widget.slide.completed" role="button" title="Check answers" aria-label="Check answers" + class="btn btn-primary text-uppercase font-weight-bold o_wslides_js_lesson_quiz_submit">Check your answers</button> + <b t-else="" class="my-0 h5">Done !</b> + <span class="my-0 h5" style="line-height: 1"> + <span role="button" title="Succeed and gain karma" aria-label="Succeed and gain karma" class="badge badge-pill badge-warning text-white font-weight-bold ml-3 px-2"> + + <t t-if="!widget.slide.completed" t-esc="widget.quiz.quizKarmaGain"/><t t-else="" t-esc="widget.quiz.quizKarmaWon"/> XP + </span> </span> - </span> + </div> + <button t-if="widget.slide.completed && widget.slide.hasNext" class="btn btn-primary o_wslides_quiz_continue"> + Continue <i class="fa fa-chevron-right ml-1"/> + </button> </div> - <button t-if="widget.slide.completed && widget.slide.hasNext" class="btn btn-primary o_wslides_quiz_continue"> - Continue <i class="fa fa-chevron-right ml-1"/> - </button> </div> </t> diff --git a/addons/website_slides/views/website_slides_templates_lesson.xml b/addons/website_slides/views/website_slides_templates_lesson.xml index 77e154b1ccc0f159b1121f7fd36ab32bd41c88c6..6bf4256ea29e4c9232325236cb67c20881714f58 100644 --- a/addons/website_slides/views/website_slides_templates_lesson.xml +++ b/addons/website_slides/views/website_slides_templates_lesson.xml @@ -404,7 +404,10 @@ t-att-data-quiz-karma-gain="quiz_karma_gain" t-att-data-quiz-karma-won="quiz_karma_won" t-att-data-has-next="1 if next_slide else 0" - t-att-data-next-slide-url="'/slides/slide/%s' % (slug(next_slide)) if next_slide else None"> + t-att-data-next-slide-url="'/slides/slide/%s' % (slug(next_slide)) if next_slide else None" + t-att-data-channel-id="slide.channel_id.id" + t-att-data-channel-enroll="slide.channel_id.enroll" + t-att-data-signup-allowed="signup_allowed"> <div t-foreach="slide_questions" t-as="question" t-att-class="'o_wslides_js_lesson_quiz_question my-5 %s' % ('completed-disabled' if slide_completed else ('disabled' if not slide.channel_id.is_member else ''))" t-att-data-question-id="question['id']" t-att-data-title="question['question']"> <div class="h4"> diff --git a/addons/website_slides/views/website_slides_templates_lesson_fullscreen.xml b/addons/website_slides/views/website_slides_templates_lesson_fullscreen.xml index 7022de0df9967ed4d7da4d6971d67f483be80f43..43b5f3483752d766cbd093d7bd952249e8730a20 100644 --- a/addons/website_slides/views/website_slides_templates_lesson_fullscreen.xml +++ b/addons/website_slides/views/website_slides_templates_lesson_fullscreen.xml @@ -7,7 +7,10 @@ <link rel="canonical" t-att-href="slide.website_url" /> </t> <t t-call="website.layout"> - <div class="o_wslides_fs_main d-flex flex-column font-weight-light"> + <div class="o_wslides_fs_main d-flex flex-column font-weight-light" + t-att-data-channel-id="slide.channel_id.id" + t-att-data-channel-enroll="slide.channel_id.enroll" + t-att-data-signup-allowed="signup_allowed"> <div class="o_wslides_slide_fs_header d-flex flex-shrink-0 text-white"> <div class="d-flex">