diff options
| author | qmo-odoo <qmo@odoo.com> | 2018-11-21 09:06:40 +0000 |
|---|---|---|
| committer | Thibault Delavallée <tde@odoo.com> | 2019-02-19 13:11:15 +0000 |
| commit | c564ae6c87a79832d94de99d650d437fcf68b1c1 (patch) | |
| tree | 93a3afba839f793db887dbb0947d2eb92ce55f8d /addons/website_slides/static/src/js | |
| parent | 7a80ebe35edba3d76ca7167a2ef13e26b1cac9d4 (diff) | |
[IMP] website_slides: Add elearning feature to slides
Purpose of this commit is to introduce a new way of displaying and managing
slides in eLearning module. Its purpose is to give a better experience to
users when going through a course. Notably a fullscreen mode allows to take
lessons one by one without going out of the elearning display. Integration
of all slide types eases taking the course step by step.
Quiz are introduced in this commit. Those are an addition to slides with
some question / answers (multiple choice). It allows customer to gain karma
and improves gamification. Certifications are still done using the survey
application. Quiz targets only small tests at the end of a given slide.
A new widget is added to display and control slide display in fullscreen
mode. Old display is still available for documentation channels or when
going out of fullscreen mode, to see details on comments / review / statistics
and have access to share options.
This commit is linked to task ID 1902304 and PR #29876. It closes the main
work on refactoring website slides into eLearning, with other tasks already
merged in current community [1][2][3][4][5].
[1] Task ID 1940360 landed at b2149bc7a274217a0b96e587abd52093bd98e5e8: certification inclusion
[2] Task ID 1936153 landed at 6c6179e57cede3d9889c33873740a7b1ddecda2e: homepage inclusion
[3] Task ID 1937160 landed at 8b7605c9e5df4a700b0139d1781db24ae5d31626: selling courses
[4] Task ID 1922159 and 1940516 landed at 5fa651cb013ac67d7ba592498c4cf2770dec2483 and 6a64c3ce821f080969e49e5e760e52ae9bfc26cc:
new user profile and gamification of slides / forum
[5] Task ID 1938643 landed at 77b5673a0dd220b3c28e88f8acbb5274b486c5bd: upload channel/slide
Diffstat (limited to 'addons/website_slides/static/src/js')
9 files changed, 1246 insertions, 7 deletions
diff --git a/addons/website_slides/static/src/js/slides_category.js b/addons/website_slides/static/src/js/slides_category.js new file mode 100644 index 00000000000..d1d7efb7747 --- /dev/null +++ b/addons/website_slides/static/src/js/slides_category.js @@ -0,0 +1,148 @@ +odoo.define('website_slides.add.section', function (require) { + 'use strict'; + + var sAnimations = require('website.content.snippets.animation'); + var core = require('web.core'); + var Widget = require('web.Widget'); + + var _t = core._t; + + var SectionDialog = Widget.extend({ + template: 'website.slide.add.section', + events: { + 'hidden.bs.modal': 'destroy', + 'click button.save': '_save', + 'click button[data-dismiss="modal"]': '_cancel', + 'change input#upload': '_slideUpload', + 'change input#url': '_slideUrl', + 'click .list-group-item': function (ev) { + this.$('.list-group-item').removeClass('active'); + $(ev.target).closest('li').addClass('active'); + } + }, + + /** + * @override + * @param {Object} el + * @param {number} channel_id + */ + init: function (el, channelID) { + this._super(el, channelID); + this.channel_id = parseInt(channelID, 10); + this.index_content = ''; + }, + /** + * @override + */ + start: function () { + this.$el.modal({ + backdrop: 'static' + }); + + return this._super.apply(this, arguments); + }, + _getValue: function () { + var canvas = this.$('#data_canvas')[0], + values = { + 'channel_id': this.channel_id || '', + 'url': this.$('#url').val(), + 'name': this.$('#section_name').val() + }; + return values; + }, + /** + * @private + */ + _validate: function () { + this.$('.form-group').removeClass('o_has_error').find('.form-control, .custom-select').removeClass('is-invalid'); + if (!this.$('#name').val()) { + this.$('#name').closest('.form-group').addClass('o_has_error').find('.form-control, .custom-select').addClass('is-invalid'); + return false; + } + var url = this.$('#url').val() ? this.is_valid_url : false; + if (!(this.file.name || url)) { + this.$('#url').closest('.form-group').addClass('o_has_error').find('.form-control, .custom-select').addClass('is-invalid'); + return false; + } + return true; + }, + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * @override + * @param {Object} ev + */ + _save: function (ev) { + var self = this; + + var values = this._getValue(); + if ($(ev.target).data('published')) { + values.website_published = true; + } + this.$('.oe_slides_upload_loading').show(); + this.$('.modal-footer, .modal-body').hide(); + this._rpc({ + route: '/slides/add_category', + params: values, + }).then(function (data) { + if (data.error) { + self._displayAlert(data.error); + self.$('.oe_slides_upload_loading').hide(); + self.$('.modal-footer, .modal-body').show(); + + } else { + window.location = data.url; + } + }); + }, + /** + * @override + */ + _cancel: function () { + this.trigger('cancel'); + }, + + }); + + sAnimations.registry.websiteSlidesSection = sAnimations.Class.extend({ + selector: '.oe_slide_js_add_section', + xmlDependencies: ['/website_slides/static/src/xml/website_slides_upload.xml'], + read_events: { + 'click': '_onAddSectionClick', + }, + + /** + * @override + */ + start: function () { + // Automatically open the upload dialog if requested from query string + if ($.deparam.querystring().enable_slide_upload !== undefined) { + this._openDialog(this.$el.attr('channel_id')); + } + return this._super.apply(this, arguments); + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + _openDialog: function (channelID) { + new SectionDialog(this, channelID).appendTo(document.body); + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * @private + * @param {Event} ev + */ + _onAddSectionClick: function (ev) { + console.log('test'); + this._openDialog($(ev.currentTarget).attr('channel_id')); + }, + }); + });
\ No newline at end of file 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 new file mode 100644 index 00000000000..1da173a2f46 --- /dev/null +++ b/addons/website_slides/static/src/js/slides_course_fullscreen_player.js @@ -0,0 +1,427 @@ +var onYouTubeIframeAPIReady; + +odoo.define('website_slides.fullscreen', function (require) { + 'use strict'; + var sAnimations = require('website.content.snippets.animation'); + var Widget = require('web.Widget'); + var core = require('web.core'); + var QWeb = core.qweb; + + var tag = document.createElement('script'); + + var QuizWidget = require('website_slides.quiz'); + + tag.src = "https://www.youtube.com/iframe_api"; + var firstScriptTag = document.getElementsByTagName('script')[0]; + firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); + + + var Fullscreen = Widget.extend({ + /** + * @override + * @param {Object} el + * @param {Object} data holding channelId and optionally upload and publish control parameters + */ + init: function(el, course_id,slide_id, user_id){ + this._super.apply(this,arguments); + this.courseID = parseInt(course_id, 10); + this.slideID = parseInt(slide_id, 10); + this.userID = parseInt(user_id, 10); + this.course = undefined; + this.slide = undefined; + this.slides = []; + this.nextSlide = undefined; + this.previousSlide = undefined; + this.url = undefined; + this.urlToSmallScreen = undefined; + this.activetab = undefined; + this.player = undefined; + this.goToQuiz = false; + this.answeredQuestions = []; + this.slideTitle = undefined; + }, + start: function(){ + var self = this; + this.url = window.location.pathname; + this.urlToSmallScreen = this.url.replace('/fullscreen',''); + this._getSlides(); + this._renderPlayer(); + this._bindListEvents(); + + $(document).keydown(this._onKeyDown.bind(this)); + return this._super.apply(this, arguments); + }, + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + /** + * @private + * Renders the player accordingly to the current slide + */ + _renderPlayer: function(){ + var self = this; + var embed_url; + if(self.slide.slide_type !== 'webpage' || self.slide.htmlContent){ + if((self.slide.slide_type == "quiz" || self.slide.hasQuiz) && !self.slide.quiz){ + self._fetchQuiz(); + } else { + embed_url = $(this.slide.embed_code).attr('src'); + if (self.slide.slide_type === "video"){ + embed_url = "https://" + embed_url + "&rel=0&autoplay=1&enablejsapi=1&origin=" + window.location.origin; + } + $('.o_wslides_fullscreen_player').html(QWeb.render('website.slides.fullscreen', { + slide: self.slide, + nextSlide: self.nextSlide, + questions: self.slide.quiz ? self.slide.quiz.questions: '', + reward: self.slide.quiz ? self.slide.quiz.nb_attempts < 3 ? self.slide.quiz.possible_rewards[self.slide.quiz.nb_attempts] : self.slide.quiz.possible_rewards[3]: self.slide.maxPoints, + embed_url: embed_url, + question_count: self.slide.quiz ? self.slide.quiz.questions.length : '', + letters: self.slide.quiz ? self.letters : '', + showMiniQuiz: self.goToQuiz + })); + if(self.slide.slide_type === "video"){ + self._renderYoutubeIframe(); + } + if(self.slide.slide_type == 'webpage'){ + self._renderWebpage(); + } + if((self.slide.slide_type == "presentation" || self.slide.slide_type == "document" || self.slide.slide_type == "infographic" || self.slide.slide_type == "webpage") && !self.slide.quiz){ + self._setSlideStateAsDone(); + } + if ((self.slide.quiz && self.slide.slide_type === "quiz") || self.goToQuiz){ + self._renderQuiz(); + } + } + } else { + self._fetchHtmlContent(); + } + self._renderTitle(); + }, + _renderYoutubeIframe: function(){ + var self = this; + /** + * Due to issues of synchronization between the youtube api script and the widget's instanciation. + */ + try{ + self._setupYoutubePlayer(); + } + catch { + onYouTubeIframeAPIReady = function(){ + var self = this; + self._setupYoutubePlayer(); + }.bind(this) + } + }, + _renderWebpage: function(){ + var self = this; + $(self.slide.htmlContent).appendTo('.o_wslides_fullscreen_webpage_content'); + }, + _renderQuiz: function(){ + var self = this; + var Quiz = new QuizWidget(this, self.slide); + Quiz.appendTo('.o_wslides_fullscreen_player'); + $('.next-slide').click(function(){ + self._goToNextSlide(); + }) + $('.back-to-video').click(function(){ + self.goToQuiz = false; + self._renderPlayer(); + }); + }, + _renderTitle: function(){ + var self = this; + $('.o_wslides_fullscreen_slide_title').empty().html(QWeb.render('website.course.fullscreen.title', { + slide: self.slide, + miniQuiz: self.goToQuiz + })); + }, + /** + * @private + * Links the youtube api to the iframe present in the template + */ + _setupYoutubePlayer: function(){ + var self = this; + self.player = new YT.Player('youtube-player', { + host: 'https://www.youtube.com', + playerVars: {'autoplay': 1, 'origin': window.location.origin}, + autoplay: 1, + events: { + 'onReady': self._onPlayerReady, + 'onStateChange': this._onPlayerStateChange.bind(self) + } + }); + }, + /** + * @param {*} event + * Specific method of the youtube api. + * Whenever the player starts playing, a setinterval is created. + * This setinterval is used to check te user's progress in the video. + * Once the user reaches a particular time in the video, the slide will be considered as completed if the video doesn't have a mini-quiz. + * This method also allows to automatically go to the next slide (or the quiz associated to the current video) once the video is over + */ + _onPlayerStateChange: function(event){ + var self = this; + var tid; + clearInterval(self.tid); + if (event.data == YT.PlayerState.PLAYING && !self.slide.done) { + self.tid = setInterval(function(){ + if(event.target.getCurrentTime){ + var currentTime = event.target.getCurrentTime(); + var totalTime = event.target.getDuration(); + if(totalTime && currentTime > totalTime - 30){ + clearInterval(self.tid); + if(!self.slide.hasQuiz && !self.slide.done){ + self.slide.done = true; + self._setSlideStateAsDone(); + } + } + } + }, 1000); + } + if(event.data == YT.PlayerState.ENDED){ + self.player = undefined; + self._goToNextSlide(); + } + }, + /** + * @private + * Creates slides objects from every slide-list-cells attributes + */ + _getSlides: function(){ + var self = this; + var slides = $('.o_wslides_fullscreen_sidebar_slide_tab'); + for(var i = 0; i < slides.length;i++){ + var slide = $(slides[i]); + self.slides.push({ + id: parseInt(slide.attr('slide_id'), 10), + name: slide.attr('slide_name'), + embed_code: slide.attr('slide_embed_code'), + slide_type: slide.attr('slide_type'), + done: slide.attr('done'), + hasQuiz: slide.attr('quiz'), + slug: slide.attr('slide_slug'), + htmlContent: undefined + }); + this._getActiveSlide(); + } + }, + /** + * @private + * @param {object} slide + * Fetch the quiz for a particular slide + */ + _fetchQuiz: function(){ + var self = this; + self._rpc({ + route:"/slide/quiz/get", + params: { + 'slide_id': self.slide.id + } + }).then(function(data){ + if(data){ + self.slide.quiz = data; + self._renderPlayer(); + } + }) + }, + _fetchHtmlContent: function(){ + var self = this; + self._rpc({ + route:"/slide/html_content/get", + params: { + 'slide_id': self.slide.id + } + }).then(function(data){ + self.slide.htmlContent = data.html_content; + self._renderPlayer(); + }) + }, + /** + * @private + * Once the completion conditions are filled, + * sends a json request to the backend to set the relation between the slide and the user as being completed + */ + _setSlideStateAsDone: function(){ + var self = this; + self._rpc({ + route: '/slides/set_completed', + params: { + slide_id: self.slide.id, + } + }).then(function(data){ + $('#check-'+self.slide.id).replaceWith($('<i class="check-done o_wslides_slide_completed fa fa-check-circle"></i>')) + self.slide.done = true; + clearInterval(self.tid); + self.channelCompletion = data.channel_completion; + self._updateProgressbar(); + }); + }, + _updateProgressbar: function(){ + var self = this; + var completion = self.channelCompletion <= 100 ? self.channelCompletion : 100; + $('.o_wslides_fullscreen_sidebar_progress_gauge').css('width', completion + "%" ); + $('.o_wslides_progress_percentage').text(completion); + }, + /** + * @private + * Creates an array of letters to be used in the quiz with a foreach + */ + _generateQuizLetters: function(){ + var letters = []; + for(var i = 65; i < 91; i++){ + letters.push(String.fromCharCode(i)); + } + return letters; + }, + _goToNextSlide: function(){ + var self = this; + clearInterval(self.tid); + self.player = undefined; + self.goToQuiz = self.slide.hasQuiz && !self.goToQuiz; + if(self.nextSlide && !self.goToQuiz){ + self.slide = self.nextSlide; + self.index++; + self._setActiveTab(); + self._renderPlayer(); + self._setPreviousAndNextSlides(); + self._updateUrl(); + history.pushState(null,'',self.url); + } + else if(self.nextSlide){ + self._renderPlayer(); + } + }, + _goToPreviousSlide: function(){ + var self = this; + clearInterval(self.tid); + self.goToQuiz = false; + self.player = undefined; + if(self.previousSlide){ + self.slide = self.previousSlide; + self.index--; + self._setActiveTab(); + self._renderPlayer(); + self._setPreviousAndNextSlides(); + self._updateUrl(); + history.pushState(null,'',self.url); + } + }, + _setPreviousAndNextSlides: function(){ + var self = this; + self.previousSlide = self.index > 0 ? self.slides[self.index-1] : undefined; + self.nextSlide = self.index < (self.slides.length - 1) ? self.slides[self.index+1] : undefined; + }, + /** + * Changes the url whenever the user changes slides. + * This allows the user to refresh the page and stay at the right video + */ + _updateUrl: function(){ + var self = this; + var url = window.location.pathname.split('/'); + url[url.length-1] = self.slide.slug; + url = url.join('/'); + self.url = url; + self.urlToSmallScreen = self.url; + self.url += "?fullscreen=1"; + $('.o_wslides_small_screen').attr('href', self.urlToSmallScreen); + }, + /** + * Whenever the user changes slide, change the active tab + */ + _setActiveTab: function(){ + var self = this; + self.activeTab.removeClass('active'); + $('li.active').removeClass('active'); + $('li[slide_id='+self.slide.id+']').addClass('active'); + self.activeTab = $('.o_wslides_fullscreen_sidebar_slide_tab[index="'+self.index+'"]') + self.activeTab.addClass('active'); + }, + /** + * The first time the user gets on the player, + * get the slide that is represented by the active tab in the sidebar + */ + _getActiveSlide: function(){ + var self = this; + self.activeTab = $('.o_wslides_fullscreen_sidebar_slide_tab.active'); + self.index = parseInt(self.activeTab.attr('index'), 10); + self.slide = self.slides[self.index]; + self._setPreviousAndNextSlides(); + }, + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + _onListCellClick: function(ev){ + var self = this; + clearInterval(self.tid); + self.player = undefined; + var target = $(ev.currentTarget); + self.goToQuiz = false; + if(target[0] !== self.activeTab[0]){ + self.activeTab.removeClass('active'); + target.addClass('active'); + self.index = parseInt(target.attr('index')); + self._getActiveSlide(); + self._renderPlayer(); + $('li.active').removeClass('active'); + $('li[slide_id='+self.slide.id+']').addClass('active'); + self._setPreviousAndNextSlides(); + self._updateUrl(); + history.pushState(null,'',self.url); + } + }, + _onMiniQuizClick: function(ev){ + var self = this; + self.index = parseInt($(ev.currentTarget).attr('index')); + self.slide = self.slides[self.index]; + self.goToQuiz = true; + self._setPreviousAndNextSlides(); + self._renderPlayer(); + self._setActiveTab(); + self._updateUrl(); + history.pushState(null,'' ,self.url); + }, + /** + * @private + * Binds events related to the list + */ + _bindListEvents: function(){ + var self = this; + $('.o_wslides_fullscreen_sidebar_slide_tab').each(function () { + $(this).click(self._onListCellClick.bind(self)); + }); + + $('.o_wslides_slide_quiz ').each(function(){ + $(this).click(self._onMiniQuizClick.bind(self)); + }) + }, + _onKeyDown: function(ev){ + var self = this; + switch(ev.key){ + case "ArrowRight": + self._goToNextSlide(); + break; + case "ArrowLeft": + self._goToPreviousSlide(); + break; + } + }, + }) + + sAnimations.registry.websiteSlidesFullscreenPlayer = Widget.extend({ + selector: '.oe_js_course_slide', + xmlDependencies: ['/website_slides/static/src/xml/website_slides_fullscreen.xml'], + init: function(el){ + this._super.apply(this, arguments); + }, + start: function(){ + this._super.apply(this, arguments); + var user_id = this.$el.attr('user_id'); + var course_id = this.$el.attr('course_id'); + var slide_id = this.$el.attr('slide_id'); + var fullscreen = new Fullscreen(this, course_id, slide_id, user_id); + fullscreen.appendTo(".oe_js_course_slide"); + } + }); + + return Fullscreen; +});
\ No newline at end of file diff --git a/addons/website_slides/static/src/js/slides_course_progress_bar.js b/addons/website_slides/static/src/js/slides_course_progress_bar.js new file mode 100644 index 00000000000..2412368018f --- /dev/null +++ b/addons/website_slides/static/src/js/slides_course_progress_bar.js @@ -0,0 +1,47 @@ +odoo.define('website_slides.progress.bar', function (require) { + + var sAnimations = require('website.content.snippets.animation'); + var Widget = require('web.Widget'); + + var ProgressBar = Widget.extend({ + + /** + * @override + * @param {Object} el + * @param {number} channel_id + */ + init: function (el, completion) { + this._super(el); + this.completion = completion; + }, + /** + * @override + */ + start: function () { + this._renderProgressBar(); + return this._super.apply(this, arguments); + }, + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + _renderProgressBar: function (ev) { + var self = this; + $('.oe_slide_js_progress_bar').css('width', (self.completion <= 100 ? self.completion : 100) + '%'); + }, + }); + + sAnimations.registry.websiteSlidesProgressBar = sAnimations.Class.extend({ + selector: '.oe_slide_js_progress_bar', + /** + * @override + */ + start: function () { + var completion = parseInt($('.oe_slide_js_progress_bar').attr('channel_completion')); + var progressBar = new ProgressBar(this, completion); + progressBar.appendTo(".oe_slide_js_progress_bar"); + return this._super.apply(this, arguments); + }, + }); + + return ProgressBar; + }); diff --git a/addons/website_slides/static/src/js/slides_course_quiz.js b/addons/website_slides/static/src/js/slides_course_quiz.js new file mode 100644 index 00000000000..f98d5b3ed8a --- /dev/null +++ b/addons/website_slides/static/src/js/slides_course_quiz.js @@ -0,0 +1,206 @@ +odoo.define('website_slides.quiz', function (require) { + 'use strict'; + var sAnimations = require('website.content.snippets.animation'); + var core = require('web.core'); + var Widget = require('web.Widget'); + + var QWeb = core.qweb; + + var Quiz= Widget.extend({ + /** + * @override + * @param {Object} el + * @param {Object} data holding all the slide elements needed for the quiz + * It will either come from the fullscreen widget or the sAnimation at the end of this file + */ + init: function(el, data){ + this.slide = data; + this.answeredQuestions = []; + this.fullscreen = el; + return this._super.apply(this,arguments); + }, + start: function(){ + var self = this; + self._bindQuizEvents(); + /** + * If the quiz is rendered by the server instead of the fullscreen widget, + * questions and their answers will have to be created manually from attributes + */ + if(self.slide.quiz.questions.length === 0){ + this._setQuestions(); + } + return this._super.apply(this, arguments); + }, + _renderSuccessModal: function(){ + var self =this; + $('.o_wslides_fullscreen_quiz').append(QWeb.render('website.course.quiz.success', { + data: data + })); + $('.submit-quiz').remove(); + $('.next-slide').css('display', 'inline-block'); + $('.next-slide').click(function(){ + self.fullscreen._goToNextSlide(); + }); + $('.o_wslides_quiz_success_modal_close').click(function(){ + $('.o_wslides_quiz_success_modal').remove(); + $('.o_wslides_quiz_modal_background').remove(); + }); + $(".o_wslides_quiz_modal_background").click(function(ev){ + $(ev.currentTarget).remove(); + $('.o_wslides_quiz_success_modal').remove(); + }); + }, + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + /** + * @private + * In case the quiz is rendered by the server and the data don't come from the fullscreen widget, + * questions and their answers will have to be set here by using attributes + */ + _setQuestions: function(){ + var self = this; + $('.o_wslides_quiz_question').each(function(){ + self.slide.quiz.questions.push({ + id: parseInt($(this).attr('id')), + title: $(this).attr('title'), + answers: [] + }); + }) + for(var i = 0; i < self.slide.quiz.questions.length; i++){ + self._setAnswersForQuestion(self.slide.quiz.questions[i]); + } + }, + _setAnswersForQuestion: function (question){ + var self = this; + $('.o_wslides_quiz_answer[question_id='+question.id+']').each(function (){ + question.answers.push({ + id: parseInt($(this).attr('id')), + text: $(this).attr('text_value'), + is_correct: $(this).attr('is_correct') + }); + }); + }, + _updateProgressbar: function (){ + var self = this; + var completion = self.channelCompletion <= 100 ? self.channelCompletion : 100; + $('.o_wslides_fullscreen_sidebar_progress_gauge').css('width', completion + "%" ); + $('.o_wslides_progress_percentage').text(completion); + }, + _bindQuizEvents: function (){ + var self = this; + if (!self.slide.done){ + $('.o_wslides_quiz_answer').each(function(){ + $(this).click(self._onAnswerClick.bind(self)); + }); + } + $('.submit-quiz').click(self._onSubmitQuiz.bind(self)); + }, + _highlightAnswers: function (answers){ + var self = this; + self.answeredQuestions = []; + for (var i = 0; i < answers.goodAnswers.length; i++){ + $('#answer'+ answers.goodAnswers[i] +'').addClass('o_wslides_quiz_good_answer'); + $('#answer'+ answers.goodAnswers[i] +' .o_wslides_quiz_radio_box span').replaceWith($('<i class="fa fa-check-circle"></i>')); + var questionID =$('#answer'+ answers.goodAnswers[i] +' .o_wslides_quiz_radio_box input').attr('question_id'); + self.answeredQuestions.push(questionID); + $('.o_wslides_quiz_answer[question_id='+questionID+']:not(.o_wslides_quiz_good_answer)').addClass('o_wslides_quiz_ignored_answer'); + $('.o_wslides_quiz_answer[question_id='+questionID+']').each(function(){ + $(this).unbind('click') + }); + $('input[question_id='+questionID+']').each(function(){ + $(this).prop('disabled',true); + }); + } + for (i = 0; i < answers.badAnswers.length; i++){ + $('#answer'+ answers.badAnswers[i]).removeClass('o_wslides_quiz_good_answer') + .addClass('o_wslides_quiz_bad_answer') + .unbind('click'); + $('#answer'+ answers.badAnswers[i] +' .o_wslides_quiz_radio_box span').replaceWith($('<i class="fa fa-times "></i>')); + $('#answer'+ answers.badAnswers[i] +' .o_wslides_quiz_radio_box input').prop('checked', false); + } + }, + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + _onAnswerClick: function (ev){ + var self = this; + var target = $(ev.currentTarget); + if ((self.answeredQuestions.indexOf(target.attr('question_id'))) === -1){ + var id = target.attr('id'); + var question_id = target.attr('question_id'); + $('.o_wslides_quiz_answer[question_id='+question_id+']').removeClass('o_wslides_quiz_good_answer'); + $('#'+id+' input[type=radio]').prop('checked', true); + } + }, + _onSubmitQuiz: function (){ + var self = this; + var inputs = $('input[type=radio]:checked'); + var values = []; + for (var i = 0; i < inputs.length; i++){ + values.push(parseInt($(inputs[i]).val())); + } + if (values.length === self.slide.quiz.questions.length){ + $('.quiz-danger').remove(); + self._rpc({ + route: "/slide/quiz/submit", + params: { + slide_id: self.slide.id, + answer_ids: values, + quiz_id: self.slide.quiz_id + } + }).then(function(data){ + self._highlightAnswers(data); + self.slide.quiz.nb_attempts = data.attempts_count; + if (data.passed){ + self.channelCompletion = data.channel_completion; + self._updateProgressbar(); + $('#check-'+self.slide.id).replaceWith($('<i class="check-done o_wslides_slide_completed fa fa-check-circle"></i>')); + self.slide.done = true; + self._renderSuccessModal(); + } + else { + $('#quiz-points').text(self.slide.quiz.nb_attempts < 3 ? self.slide.quiz.possible_rewards[self.slide.quiz.nb_attempts] : self.slide.quiz.possible_rewards[self.slide.quiz.possible_rewards.length-1]); + } + }); + } else { + $('.o_wslides_fullscreen_player').append($('<p class="quiz-danger text-danger mt-1">All questions must be answered !</p>')); + } + }, +}); + + sAnimations.registry.websiteSlidesQuizNoFullscreen = Widget.extend({ + selector: '.o_w_slides_quiz_no_fullscreen', + xmlDependencies: ['/website_slides/static/src/xml/website_slides_fullscreen.xml'], + init: function (el){ + this._super.apply(this, arguments); + }, + start: function (){ + this._super.apply(this, arguments); + var slideID = parseInt(this.$el.attr('slide_id'),10); + var slideDone = this.$el.attr('slide_done'); + var nbAttempts = parseInt(this.$el.attr('nb_attempts'), 10); + var firstAttemptReward = this.$el.attr('first_reward'); + var secondAttemptReward = this.$el.attr('second_reward'); + var thirdAttemptReward = this.$el.attr('third_reward'); + var fourthAttemptReward = this.$el.attr('fourth_reward'); + var possibleRewards = [firstAttemptReward,secondAttemptReward,thirdAttemptReward,fourthAttemptReward] + var data = { + id: slideID, + done: slideDone, + quiz: { + questions: [], + nb_attempts: nbAttempts, + possible_rewards: possibleRewards, + reward: nbAttempts < possibleRewards.length ? possibleRewards[nbAttempts] : possibleRewards[possibleRewards.length-1] + } + } + if(!slideDone){ + var NewQuiz = new Quiz(this, data); + NewQuiz.appendTo(".o_w_slides_quiz_no_fullscreen"); + } + } + }); + + return Quiz; +});
\ No newline at end of file diff --git a/addons/website_slides/static/src/js/slides_course_sidebar_list.js b/addons/website_slides/static/src/js/slides_course_sidebar_list.js new file mode 100644 index 00000000000..11b61d4e5ec --- /dev/null +++ b/addons/website_slides/static/src/js/slides_course_sidebar_list.js @@ -0,0 +1,34 @@ +odoo.define('website_slides.sidebar', function (require) { + 'use strict'; + var sAnimations = require('website.content.snippets.animation'); + var core = require('web.core'); + var Widget = require('web.Widget'); + + var SideBar = Widget.extend({ + init: function (el){ + this._super.apply(this,arguments); + }, + start: function (){ + $('.o_wslides_fullscreen_toggle_sidebar').click(function (ev){ + ev.preventDefault(); + $(ev.currentTarget).toggleClass('active'); + $('.o_wslides_fullscreen_sidebar').toggleClass('o_wslides_fullscreen_sidebar_hidden'); + $('.o_wslides_fullscreen_player').toggleClass('o_wslides_fullscreen_player_no_sidebar') + }) + return this._super.apply(this, arguments); + }, + }); + + sAnimations.registry.websiteSlidesSidebarList = Widget.extend({ + selector: '.o_wslides_fullscreen_toggle_sidebar', + // xmlDependencies: ['/website_slides/static/src/xml/website_slides.xml'], + init: function (el){ + this._super.apply(this, arguments); + }, + start: function (){ + this._super.apply(this, arguments); + var sideBar = new SideBar(this); + sideBar.appendTo(".oe_js_side_bar_list"); + } + }); +});
\ No newline at end of file diff --git a/addons/website_slides/static/src/js/slides_course_slides_list.js b/addons/website_slides/static/src/js/slides_course_slides_list.js new file mode 100644 index 00000000000..bf634f542e3 --- /dev/null +++ b/addons/website_slides/static/src/js/slides_course_slides_list.js @@ -0,0 +1,251 @@ +odoo.define('website_slides.slideslist', function (require) { + 'use strict'; + var sAnimations = require('website.content.snippets.animation'); + var Widget = require('web.Widget'); + + var SlideUpload = require('website_slides.upload_modal'); + + var List = Widget.extend({ + init: function (el){ + this._super.apply(this,arguments); + this.draggedElement = undefined; + this.dropTarget = undefined; + this.slideCount = undefined; + this.slides = []; + this.categories = []; + }, + start: function (){ + this._super.apply(this,arguments); + this.slideCount = $('li.content-slide').length; + //Change links HREF to fullscreen mode for SEO + var links = $(".link-to-slide"); + for (var i = 0; i < links.length; i++){ + $(links[i]).attr('href', $(links[i]).attr('href') + "?fullscreen=1"); + } + this._bindEvents(); + }, + _bindEvents: function (){ + var self = this; + $('.slide-draggable').each(function (){ + self._addSlideDragAndDropHandlers($(this)); + }); + $('.section-draggable').each(function (){ + self._addSectionDragAndDropHandlers($(this)); + }); + $('.course-section').each(function (){ + self._addDropSlideOnSectionHandler($(this)); + }); + }, + _unbind: function (className){ + $("."+className).each(function (){ + $(this).unbind(); + }); + }, + _unbindAll: function (){ + this._unbind('slide-draggable'); + this._unbind('section-draggable'); + this._unbind('course-section'); + }, + _getSlides: function (){ + var self = this; + var slides = $('li.content-slide'); + for(var i = 0; i < slides.length;i++){ + var slide = $(slides[i]); + self.slides.push({ + id: parseInt(slide.attr('slide_id')), + category_id: parseInt(slide.attr('category_id')), + sequence: i + }); + } + }, + _getCategories: function (){ + var self = this; + self.categories = []; + var categories = $('.course-section'); + for (var i = 0; i < categories.length;i++){ + var category = $(categories[i]); + self.categories.push(parseInt(category.attr('category_id'))); + } + }, + _addDropSlideOnSectionHandler: function (target){ + var self = this; + target.on('drop', function (ev){ + if (ev.preventDefault){ + ev.preventDefault(); + } + self.dropTarget = $(ev.currentTarget); + self.draggedElement[0].parentNode.removeChild(self.draggedElement[0]); + $('ul[category_id='+target.attr('category_id')+']').append(self.draggedElement) + self._addSlideDragAndDropHandlers(self.draggedElement); + self._reorderSlides(); + }); + target.on('dragover', function (ev){ + if(ev.preventDefault){ + ev.preventDefault(); + } + }); + }, + _addSlideDragAndDropHandlers: function (target){ + var self = this; + target.on('dragstart', function (ev){ + $('.section-draggable').removeClass('hold') + self._unbind('section-draggable'); + ev.originalEvent.dataTransfer.effectAllowed = 'move'; + ev.originalEvent.dataTransfer.setData('text/html', this.outerHTML); + self.draggedElement = target; + self.draggedElement.addClass('hold'); + }); + target.on('dragover', function (ev){ + if ($(ev.currentTarget) !== self.draggedElement){ + if (ev.preventDefault){ + ev.preventDefault(); + } + target.addClass('slide-hovered'); + } + }); + target.on('dragleave', function (ev){ + if (ev.preventDefault){ + ev.preventDefault(); + } + target.removeClass('slide-hovered'); + }); + target.on('drop', function (ev){ + if (self.draggedElement.hasClass('slide-draggable') && target.hasClass('slide-draggable')){ + if (ev.preventDefault){ + ev.preventDefault(); + } + target.removeClass('slide-hovered'); + target.removeClass('hold'); + if (target !== self.draggedElement){ + self.dropTarget = $(ev.currentTarget); + self.draggedElement[0].parentNode.removeChild(self.draggedElement[0]); + var dropHTML = ev.originalEvent.dataTransfer.getData('text/html'); + target[0].insertAdjacentHTML('beforebegin',dropHTML); + self.draggedElement = $(target[0].previousSibling); + self._reorderSlides(); + } + self._unbindAll(); + self._bindEvents(); + } + }); + target.on('dragend', function (ev){ + if (ev.preventDefault){ + ev.preventDefault(); + } + target.removeClass('slide-hovered'); + target.removeClass('hold'); + }); + }, + _addSectionDragAndDropHandlers: function(target){ + var self = this; + target.on('dragstart', function (ev){ + self._unbind('slide-draggable'); + self._unbind('course-section'); + ev.originalEvent.dataTransfer.effectAllowed = 'move'; + ev.originalEvent.dataTransfer.setData('text/html', this.outerHTML); + self.draggedElement = target; + self.draggedElement.addClass('hold'); + }); + target.on('dragover', function (ev){ + if (target.hasClass('section-draggable') && self.draggedElement.hasClass('section-draggable')){ + if (ev.preventDefault){ + ev.preventDefault(); + } + target.addClass('slide-hovered'); + } + }); + target.on('dragleave', function (ev){ + if (ev.preventDefault){ + ev.preventDefault(); + } + target.removeClass('slide-hovered'); + }); + target.on('drop', function (ev){ + if(ev.preventDefault){ + ev.preventDefault(); + } + if(self.draggedElement.hasClass('section-draggable') && target.hasClass('section-draggable')){ + target.removeClass('slide-hovered'); + target.removeClass('hold'); + self.dropTarget = $(ev.currentTarget); + if(target !== self.draggedElement && $(ev.currentTarget).hasClass('section-draggable')){ + self.draggedElement[0].parentNode.removeChild(self.draggedElement[0]); + var dropHTML = ev.originalEvent.dataTransfer.getData('text/html'); + target[0].insertAdjacentHTML('beforebegin',dropHTML); + self.draggedElement = $(target[0].previousSibling); + self._reorderCategories(); + self._reorderSlides(); + self._rebindUploadButton(self.draggedElement.attr('category_id')); + } + self._unbindAll(); + self._bindEvents(); + } + }); + target.on('dragend', function (ev){ + if (ev.preventDefault){ + ev.preventDefault(); + } + target.removeClass('slide-hovered'); + target.removeClass('hold'); + }); + }, + _reorderCategories: function (){ + var self = this; + self._getCategories(); + self._rpc({ + route: '/web/dataset/resequence', + params: { + model: "slide.category", + ids: self.categories + } + }).then(function (){ + self._resetCategoriesIndex(); + }); + }, + _resetCategoriesIndex: function (){ + var categoriesIndexes = $('.section-index') + for (var i = 0; i < categoriesIndexes.length; i++){ + $(categoriesIndexes[i]).text(i+1) + } + }, + _reorderSlides: function(){ + var self = this; + // In case the slide was transfered to another section + if (self.draggedElement.hasClass('slide-draggable')){ + self.draggedElement.attr('category_id', parseInt(self.dropTarget.attr('category_id'))) + } + self.slides = []; + self._getSlides(); + self._rpc({ + route: "/slides/resequence_slides", + params: { + slides_data: self.slides + } + }).then(function(){ + }); + }, + _rebindUploadButton: function(categoryID){ + var self = this; + this.$('.oe_slide_js_upload[data-category-id='+categoryID+']').click(function(ev){ + ev.preventDefault(); + var data = $(ev.currentTarget).data(); + var dialog = new SlideUpload.SlideUploadDialog(self, data); + dialog.appendTo(document.body); + dialog.open(); + }) + } + }) + + sAnimations.registry.websiteSlidesCourseSlidesList = Widget.extend({ + selector: '.oe_js_course_slides_list', + xmlDependencies: ['/website_slides/static/src/xml/website_slides_upload.xml'], + init: function (el){ + this._super.apply(this, arguments); + }, + start: function (){ + this._super.apply(this, arguments); + var list = new List(this); + list.appendTo(".oe_js_course_slides_list"); + } + }); +});
\ No newline at end of file diff --git a/addons/website_slides/static/src/js/slides_delete_slide.js b/addons/website_slides/static/src/js/slides_delete_slide.js new file mode 100644 index 00000000000..e3eef38502d --- /dev/null +++ b/addons/website_slides/static/src/js/slides_delete_slide.js @@ -0,0 +1,88 @@ +odoo.define('website_slides.delete.slide', function (require) { + + var sAnimations = require('website.content.snippets.animation'); + var core = require('web.core'); + var Widget = require('web.Widget'); + + var _t = core._t; + var QWeb = core.qweb; + + var DeleteSlideDialog = Widget.extend({ + template: 'website.slide.delete.slide', + events: { + 'hidden.bs.modal': 'destroy', + 'click button[data-dismiss="modal"]': '_cancel', + 'click button.delete': '_delete' + }, + + /** + * @override + * @param {Object} el + * @param {number} channel_id + */ + init: function (el, slideID) { + this._super(el, slideID); + this.slide_id = parseInt(slideID, 10); + }, + /** + * @override + */ + start: function () { + this.$el.modal({ + backdrop: 'static' + }); + return this._super.apply(this, arguments); + }, + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + _delete: function (ev) { + var self = this; + // TO FIX: CallBack is not executed + $('[slide_id='+this.slide_id+']').remove(); + this._rpc({ + model: 'slide.slide', + method: 'unlink', + args: [[self.slide_id]], + }).then(function () { + $('[slide='+this.slide_id+']').remove(); + }); + }, + /** + * @override + */ + _cancel: function () { + this.trigger('cancel'); + } + }); + + sAnimations.registry.websiteSlidesDeleteSlide = sAnimations.Class.extend({ + selector: '.oe_slide_js_delete_slide', + xmlDependencies: ['/website_slides/static/src/xml/website_slides_upload.xml'], + read_events: { + 'click': '_onDeleteSlideClick', + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + _openDialog: function (slideID) { + new DeleteSlideDialog(this, slideID).appendTo(document.body); + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * @private + * @param {Event} ev + */ + _onDeleteSlideClick: function (ev) { + var target = $(ev.currentTarget); + this._openDialog(target.attr('slide_id')); + }, + }); + return DeleteSlideDialog; + }); diff --git a/addons/website_slides/static/src/js/slides_upload.js b/addons/website_slides/static/src/js/slides_upload.js index 855b7e52932..7b0a2964bf8 100644 --- a/addons/website_slides/static/src/js/slides_upload.js +++ b/addons/website_slides/static/src/js/slides_upload.js @@ -30,6 +30,7 @@ var SlideUploadDialog = Dialog.extend({ this._setup(); this.channelID = parseInt(options.channelId, 10); + this.defaultCategoryID = parseInt(options.categoryId,10) this.canUpload = options.canUpload === 'True'; this.canPublish = options.canPublish === 'True'; @@ -198,6 +199,7 @@ var SlideUploadDialog = Dialog.extend({ */ _getSelect2DropdownValues: function (){ var result = {}; + var self = this; // tags var tagValues = []; _.each(this.$('#tag_ids').select2('data'), function (val) { @@ -211,11 +213,17 @@ var SlideUploadDialog = Dialog.extend({ result['tag_ids'] = tagValues; } // category - var categoryValue = this.$('#category_id').select2('data'); - if (categoryValue && categoryValue.create) { - result['category_id'] = [0, {'name': categoryValue.text}]; - } else if (categoryValue) { - result['category_id'] = [categoryValue.id]; + if(!self.defaultCategoryID){ + var categoryValue = this.$('#category_id').select2('data'); + if (categoryValue && categoryValue.create) { + result['category_id'] = [0, {'name': categoryValue.text}]; + } else if (categoryValue) { + result['category_id'] = [categoryValue.id]; + this.categoryID = categoryValue.id; + } + } else { + result['category_id'] = [self.defaultCategoryID]; + this.categoryID = self.defaultCategoryID; } return result; }, @@ -303,6 +311,25 @@ var SlideUploadDialog = Dialog.extend({ return values; }, + _reorderSlidesSequence: function(){ + var self = this; + var slidesElement = $('li.content-slide'); + var slides = []; + for(var i = 0; i < slidesElement.length;i++){ + slides.push({ + id: parseInt($(slidesElement[i]).attr('slide_id')), + category_id: parseInt($(slidesElement[i]).attr('category_id')), + sequence: i + }) + } + self._rpc({ + route: '/slides/resequence_slides', + params: { + slides_data: slides + } + }).then(function(){ + }) + }, /** * Init the data relative to the support slide type to upload * @@ -325,6 +352,11 @@ var SlideUploadDialog = Dialog.extend({ label: _t('Video'), template: 'website.slide.upload.modal.video', }, + quiz: { + icon: 'fa-question-circle', + label: _t('Quiz'), + template: 'website.slide.upload.quiz' + } }; }, /** @@ -472,12 +504,11 @@ var SlideUploadDialog = Dialog.extend({ _onChangeSlideUrl: function (ev) { var self = this; var url = $(ev.target).val(); - this._alertRemove(); this.isValidUrl = false; this.set('can_submit_form', false); this._fetchUrlPreview(url).then(function (data) { - this.set('can_submit_form', true); + self.set('can_submit_form', true); if (data.error) { self._alertDisplay(data.error); } else { @@ -508,6 +539,12 @@ var SlideUploadDialog = Dialog.extend({ self.set('state', oldType); self._alertDisplay(data.error); } else { + //Quick and really dirty fix for reordering issues + if(data.channel_type == 'training' && self.categoryID){ + var categoryElement = $('ul[category_id='+self.categoryID+']'); + $('<li hidden class="content-slide" slide_id="'+data.slide_id+'" category_id="'+self.categoryID+'">temp</li>').appendTo(categoryElement) + self._reorderSlidesSequence(); + } window.location = data.url; } }); diff --git a/addons/website_slides/static/src/js/website_slides.editor.js b/addons/website_slides/static/src/js/website_slides.editor.js index 40513e785e2..72a18bb93ee 100644 --- a/addons/website_slides/static/src/js/website_slides.editor.js +++ b/addons/website_slides/static/src/js/website_slides.editor.js @@ -5,6 +5,7 @@ var core = require('web.core'); var Dialog = require('web.Dialog'); var QWeb = core.qweb; var WebsiteNewMenu = require('website.newMenu'); +var wUtils = require('website.utils'); var _t = core._t; |
