summaryrefslogtreecommitdiff
path: root/addons
diff options
context:
space:
mode:
Diffstat (limited to 'addons')
-rw-r--r--addons/website_slides/controllers/main.py4
-rw-r--r--addons/website_slides/models/slide_slide.py2
-rw-r--r--addons/website_slides/static/src/js/slides_course_fullscreen_player.js765
-rw-r--r--addons/website_slides/static/src/js/slides_course_quiz.js18
-rw-r--r--addons/website_slides/static/src/scss/slides_slide_fullscreen.scss265
-rw-r--r--addons/website_slides/static/src/xml/slide_quiz.xml2
-rw-r--r--addons/website_slides/static/src/xml/website_slides_fullscreen.xml59
-rw-r--r--addons/website_slides/views/website_slides_templates_lesson_fullscreen.xml178
-rw-r--r--addons/website_slides_forum/views/website_slides_templates.xml6
-rw-r--r--addons/website_slides_survey/static/src/xml/website_slides_fullscreen.xml14
-rw-r--r--addons/website_slides_survey/views/website_slides_templates_lesson_fullscreen.xml6
11 files changed, 634 insertions, 685 deletions
diff --git a/addons/website_slides/controllers/main.py b/addons/website_slides/controllers/main.py
index f8ebb91a70b..70137487c0c 100644
--- a/addons/website_slides/controllers/main.py
+++ b/addons/website_slides/controllers/main.py
@@ -622,8 +622,10 @@ class WebsiteSlides(WebsiteProfile):
'quizKarmaWon': quiz_info['quiz_karma_won'],
}
- @http.route('/slides/slide/quiz/submit', type="json", auth="user", website=True)
+ @http.route('/slides/slide/quiz/submit', type="json", auth="public", website=True)
def slide_quiz_submit(self, slide_id, answer_ids):
+ if request.website.is_public_user():
+ return {'error': 'public_user'}
fetch_res = self._fetch_slide(slide_id)
if fetch_res.get('error'):
return fetch_res
diff --git a/addons/website_slides/models/slide_slide.py b/addons/website_slides/models/slide_slide.py
index 53378df947c..c98d0b9a69f 100644
--- a/addons/website_slides/models/slide_slide.py
+++ b/addons/website_slides/models/slide_slide.py
@@ -512,7 +512,7 @@ class Slide(models.Model):
slide_partner = slide_partners_map.get(slide.id)
if slide.question_ids and slide_partner:
if slide_partner.quiz_attempts_count:
- result[slide.id]['quiz_karma_gain'] = gains[slide_partner.quiz_attempts_count] if slide_partner.quiz_attempts_count <= len(gains) else gains[-1]
+ result[slide.id]['quiz_karma_gain'] = gains[slide_partner.quiz_attempts_count] if slide_partner.quiz_attempts_count < len(gains) else gains[-1]
result[slide.id]['quiz_attempts_count'] = slide_partner.quiz_attempts_count
if quiz_done or slide_partner.completed:
result[slide.id]['quiz_karma_won'] = gains[slide_partner.quiz_attempts_count-1] if slide_partner.quiz_attempts_count < len(gains) else gains[-1]
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 b7165e92d4d..f8c34ee222f 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
@@ -7,414 +7,561 @@ odoo.define('website_slides.fullscreen', function (require) {
var core = require('web.core');
var QWeb = core.qweb;
- var tag = document.createElement('script');
+ var Quiz = require('website_slides.quiz');
- 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);
+ /**
+ * Helper: Get the slide dict matching the given criteria
+ *
+ * @private
+ * @param {Array<Object>} slideList List of dict reprensenting a slide
+ * @param {Object} matcher (see https://underscorejs.org/#matcher)
+ */
+ var findSlide = function (slideList, matcher) {
+ var slideMatch = _.matcher(matcher);
+ return _.find(slideList, slideMatch);
+ };
+ /**
+ * This widget is responsible of display Youtube Player
+ *
+ * The widget will trigger an event `change_slide` when the video is at
+ * its end, and `slide_completed` when the player is at 30 sec before the
+ * end of the video (30 sec before is considered as completed).
+ */
+ var VideoPlayer = Widget.extend({
+ template: 'website.slides.fullscreen.video',
+ youtubeUrl: 'https://www.youtube.com/iframe_api',
- var Fullscreen = Widget.extend({
- custom_events: {
- quiz_next_slide: '_goToNextSlide',
- quiz_completed: '_onQuizCompleted'
- },
- /**
- * @override
- * @param {Object} el
- * @param {Object} data holding channelId and optionally upload and publish control parameters
- */
- init: function (el, courseId, slideId, userId) {
- this.courseID = parseInt(courseId, 10);
- this.slideID = parseInt(slideId, 10);
- this.userID = parseInt(userId, 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.slideTitle = undefined;
- return this._super.apply(this,arguments);
+ init: function (parent, slide) {
+ this.slide = slide;
+ return this._super.apply(this, arguments);
},
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);
+ return $.when(this._super.apply(this, arguments), this._loadYoutubeAPI()).then(function() {
+ self._setupYoutubePlayer();
+ });
},
- //--------------------------------------------------------------------------
- // Private
- //--------------------------------------------------------------------------
- /**
- * @private
- * Renders the player accordingly to the current slide
- */
- _renderPlayer: function (){
- var self = this;
-
+ _loadYoutubeAPI: function () {
var def = $.Deferred();
- if (this.slide.slide_type === 'webpage') {
- this._fetchHtmlContent().then(function () {
- self._renderWebpage();
- def.resolve();
- });
- } else {
- if (this.slide.slide_type !== 'quiz') {
- // no RPC, get slide data from existing DOM
- var embed_url = $(this.slide.embed_code).attr('src');
- if (this.slide.slide_type === "video"){
- embed_url = "https://" + embed_url + "&rel=0&autoplay=1&enablejsapi=1&origin=" + window.location.origin;
- }
- $('.o_wslides_fs_player').html(QWeb.render('website.slides.fullscreen', {
- slide: self.slide,
- nextSlide: self.nextSlide,
- questions: self.slide.quiz ? self.slide.quiz.questions: '',
- embed_url: embed_url,
- question_count: self.slide.quiz ? self.slide.quiz.questions.length : '',
- letters: self.slide.quiz ? self.letters : '',
- showMiniQuiz: self.goToQuiz
- }));
+ if ($(document).find('script[src="' + this.youtubeUrl + '"]').length === 0) {
+ var $youtubeElement = $('<script/>', {src: this.youtubeUrl});
+ $(document.head).append($youtubeElement);
- if(this.slide.slide_type === "video"){
- this._renderYoutubeIframe();
- }
+ // function called when the Youtube asset is loaded
+ // see https://developers.google.com/youtube/iframe_api_reference#Requirements
+ onYouTubeIframeAPIReady = function () {
def.resolve();
- }
- }
-
- if (this.slide.slide_type === "quiz" || this.goToQuiz) {
- this._renderQuiz().then(function() {
- def.resolve();
- });
- }
+ };
- return def.then(function() {
- 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 (error) {
- onYouTubeIframeAPIReady = function (){
- var self = this;
- self._setupYoutubePlayer();
- }.bind(this);
+ } else {
+ def.resolve();
}
- },
- _renderWebpage: function (){
- var self = this;
- $(self.slide.htmlContent).appendTo('.o_wslides_fs_webpage_content');
- },
-
- _renderQuiz: function (){
- var quizSlideData = _.extend(this.slide, {
- completed: this.slide.done,
- });
- var Quiz = new QuizWidget(this, quizSlideData);
- return Quiz.appendTo('.o_wslides_fs_player');
- },
-
- _renderTitle: function (){
- var self = this;
- $('.o_wslides_fs_slide_title').empty().html(QWeb.render('website.course.fullscreen.title', {
- slide: self.slide,
- miniQuiz: self.goToQuiz
- }));
+ return def;
},
/**
- * @private
* Links the youtube api to the iframe present in the template
+ *
+ * @private
*/
_setupYoutubePlayer: function (){
- var self = this;
- self.player = new YT.Player('youtube-player', {
+ this.player = new YT.Player('youtube-player' + this.slide.id, {
host: 'https://www.youtube.com',
- playerVars: {'autoplay': 1, 'origin': window.location.origin},
- autoplay: 1,
+ playerVars: {
+ 'autoplay': 1,
+ 'origin': window.location.origin
+ },
events: {
- 'onReady': self._onPlayerReady,
- 'onStateChange': this._onPlayerStateChange.bind(self)
+ 'onStateChange': this._onPlayerStateChange.bind(this)
}
});
},
/**
- * @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
+ * 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
+ *
+ * @private
+ * @param {*} event
*/
_onPlayerStateChange: function (event){
var self = this;
- var tid;
clearInterval(self.tid);
- if (event.data === YT.PlayerState.PLAYING && !self.slide.done) {
+ if (event.data === YT.PlayerState.PLAYING && !self.slide.completed) {
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();
+ if (!self.slide.hasQuestion && !self.slide.completed){
+ self.trigger_up('slide_completed', self.slide);
}
}
}
}, 1000);
}
if (event.data === YT.PlayerState.ENDED){
- self.player = undefined;
- self._goToNextSlide();
+ this.player = undefined;
+ if (this.slide.hasNext) {
+ this.trigger_up('slide_go_next');
+ }
}
},
+ });
+
+
+ /**
+ * This widget is responsible of navigation for one slide to another:
+ * - by clicking on any slide list entry
+ * - by mouse click (next / prev)
+ * - by recieving the order to go to prev/next slide (`goPrevious` and `goNext` public methods)
+ *
+ * The widget will trigger an event `change_slide` with
+ * the `slideId` and `isMiniQuiz` as data.
+ */
+ var Sidebar = Widget.extend({
+ events: {
+ "click .o_wslides_fs_sidebar_list_item": '_onClickTab',
+ },
+ init: function (parent, slideList, defaultSlide) {
+ var result = this._super.apply(this, arguments);
+ this.slideEntries = slideList;
+ this.set('slideEntry', defaultSlide);
+ return result;
+ },
+ start: function (){
+ var self = this;
+ this.on('change:slideEntry', this, this._onChangeCurrentSlide);
+ return this._super.apply(this, arguments).then(function (){
+ $(document).keydown(self._onKeyDown.bind(self));
+ });
+ },
+ destroy: function () {
+ $(document).unbind('keydown', this._onKeyDown.bind(this));
+ return this._super.apply(this, arguments);
+ },
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
/**
- * @private
- * Creates slides objects from every slide-list-cells attributes
+ * Change the current slide with the next one (if there is one).
+ *
+ * @public
*/
- _getSlides: function (){
- var self = this;
- var slides = $('.o_wslides_fs_sidebar_slide_tab');
- for (var i = 0; i < slides.length;i++){
- var slide = $(slides[i]);
- self.slides.push(slide.data());
- this._getActiveSlide();
+ goNext: function () {
+ var currentIndex = this._getCurrentIndex();
+ if (currentIndex < this.slideEntries.length-1) {
+ this.set('slideEntry', this.slideEntries[currentIndex+1]);
+ }
+ },
+ /**
+ * Change the current slide with the previous one (if there is one).
+ *
+ * @public
+ */
+ goPrevious: function () {
+ var currentIndex = this._getCurrentIndex();
+ if (currentIndex >= 1) {
+ this.set('slideEntry', this.slideEntries[currentIndex-1]);
}
},
+ /**
+ * Greens up the bullet when the slide is completed
+ *
+ * @public
+ * @param {*} slide_id
+ */
+ setSlideCompleted: function (slideId) {
+ var $elem = this.$('.fa-circle-thin[data-slide-id="'+slideId+'"]');
+ $elem.removeClass('fa-circle-thin').addClass('fa-check-circle text-success');
+ },
+ /**
+ * Updates the progressbar whenever a lesson is completed
+ *
+ * @public
+ * @param {*} channelCompletion
+ */
+ updateProgressbar: function (channelCompletion) {
+ var completion = Math.min(100, channelCompletion);
+ this.$('.progress-bar').css('width', completion + "%" );
+ this.$('.o_wslides_progress_percentage').text(completion);
+ },
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
/**
- * @private
+ * Get the index of the current slide entry (slide and/or quiz)
*/
- _fetchHtmlContent: function(){
- var self = this;
- return self._rpc({
- route: 'slides/slide/get_html_content',
- params: {
- 'slide_id': self.slide.id
- }
- }).then(function (data){
- if (data.html_content) {
- self.slide.htmlContent = data.html_content;
- }
+ _getCurrentIndex: function () {
+ var slide = this.get('slideEntry');
+ var currentIndex = _.findIndex(this.slideEntries, function (entry) {
+ return entry.id === slide.id && entry.isQuiz === slide.isQuiz;
});
+ return currentIndex;
},
+ //--------------------------------------------------------------------------
+ // Handler
+ //--------------------------------------------------------------------------
/**
+ * Handler called whenever the user clicks on a sub-quiz which is linked to a slide.
+ * This does NOT handle the case of a slide of type "quiz".
+ * By going through this handler, the widget will be able to determine that it has to render
+ * the associated quiz and not the main content.
+ *
* @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
+ * @param {*} ev
*/
- _setSlideStateAsDone: function (){
- var self = this;
- return self._rpc({
- route: '/slides/slide/set_completed',
- params: {
- slide_id: self.slide.id,
- }
- }).then(function (data) {
- if (! data.error) {
- $('#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();
- }
+ _onClickMiniQuiz: function (ev){
+ var slideID = parseInt($(ev.currentTarget).data().slide_id);
+ this.set('slideEntry',{
+ slideID: slideID,
+ isMiniQuiz: true
});
+ this.trigger_up('change_slide', this.get('slideEntry'));
},
- _updateProgressbar: function () {
- var completion = _.min([this.channelCompletion, 100]);
- this.$('.o_wslides_fs_sidebar_progressbar .progress-bar').css('width', completion + "%" );
- this.$('.o_wslides_progress_percentage').text(completion);
+ /**
+ * Handler called when the user clicks on a normal slide tab
+ *
+ * @private
+ * @param {*} ev
+ */
+ _onClickTab: function (ev) {
+ var $elem = $(ev.currentTarget);
+ var isQuiz = $elem.data('isQuiz');
+ var slideID = parseInt($elem.data('id'));
+ var slide = findSlide(this.slideEntries, {id: slideID, isQuiz: isQuiz});
+ this.set('slideEntry', slide);
},
/**
+ * Actively changes the active tab in the sidebar so that it corresponds
+ * the slide currently displayed
+ *
* @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));
+ _onChangeCurrentSlide: function () {
+ var slide = this.get('slideEntry');
+ this.$('.o_wslides_fs_sidebar_list_item.active').removeClass('active');
+ var selector = '.o_wslides_fs_sidebar_list_item[data-id='+slide.id+']';
+ if (slide.isQuiz) {
+ selector += '[data-is-quiz="1"]';
+ } else {
+ selector += '[data-is-quiz!="1"]';
}
- return letters;
+ this.$(selector).addClass('active');
+ this.trigger_up('change_slide', this.get('slideEntry'));
},
- _goToNextSlide: function (){
- var self = this;
- clearInterval(self.tid);
- self.player = undefined;
- self.goToQuiz = self.slide.hasQuiz && !self.goToQuiz && self.slide.slide_type !== 'quiz';
- if (self.nextSlide && !self.goToQuiz){
- self.slide = self.nextSlide;
- self.index++;
- self._setActiveTab();
- self._renderPlayer();
- self._setPreviousAndNextSlides();
- self._updateUrl();
- history.pushState(null,'',self.url);
+
+ /**
+ * Binds left and right arrow to allow the user to navigate between slides
+ *
+ * @param {*} ev
+ * @private
+ */
+ _onKeyDown: function (ev){
+ switch (ev.key){
+ case "ArrowLeft":
+ this.goPrevious();
+ break;
+ case "ArrowRight":
+ this.goNext();
+ break;
}
- else if (self.nextSlide){
- self._renderPlayer();
+ },
+ });
+
+
+ /**
+ * This widget's purpose is to show content of a course, naviguating through contents
+ * and correclty display it. It also handle slide completion, course progress, ...
+ *
+ * This widget is rendered sever side, and attached to the existing DOM.
+ */
+ var Fullscreen = Widget.extend({
+ events: {
+ "click .o_wslides_fs_toggle_sidebar": '_onClickToggleSidebar',
+ },
+ custom_events: {
+ 'change_slide': '_onChangeSlideRequest',
+ 'toggle_sidebar': '_onToggleSidebar',
+ 'slide_completed': '_onSlideCompleted',
+ 'quiz_completed': '_onSlideCompleted',
+ 'slide_go_next': '_onSlideGoToNext',
+ },
+ /**
+ * @override
+ * @param {Object} el
+ * @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){
+ var result = this._super.apply(this,arguments);
+ this.initialSlideID = defaultSlideId;
+ this.slides = this._preprocessSlideData(slides);
+
+ var slide;
+ if (defaultSlideId) {
+ slide = findSlide(this.slides, {id: defaultSlideId});
+ } else {
+ slide = this.slides[0];
}
+
+ this.set('slide', slide);
+
+ this.sidebar = new Sidebar(this, this.slides, slide);
+ return result;
},
- _goToPreviousSlide: function (){
+ /**
+ * @override
+ */
+ start: 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);
- }
+ this.on('change:slide', this, this._onChangeSlide);
+ return this._super.apply(this, arguments).then(function () {
+ self._onChangeSlide(); // trigger manually once DOM ready, since slide content is not rendered server side
+ });
+ },
+ /**
+ * Extended to attach sub widget to sub DOM. This might be experimental but
+ * seems working fine.
+ *
+ * @override
+ */
+ attachTo: function (){
+ var defs = [this._super.apply(this, arguments)];
+ defs.push(this.sidebar.attachTo(this.$('.o_wslides_fs_sidebar')));
+ return $.when.apply($, defs);
},
- _setPreviousAndNextSlides: function (){
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+ /**
+ * Fetches content with an rpc call for slides of type "webpage"
+ *
+ * @private
+ */
+ _fetchHtmlContent: 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;
+ var currentSlide = this.get('slide');
+ return self._rpc({
+ route:"/slides/slide/get_html_content",
+ params: {
+ 'slide_id': currentSlide.id
+ }
+ }).then(function (data){
+ if (data.html_content) {
+ currentSlide.htmlContent = data.html_content;
+ }
+ });
+ },
+ /**
+ * Fetches slide content depending on its type.
+ * If the slide doesn't need to fetch any content, return a resolved deferred
+ *
+ * @private
+ */
+ _fetchSlideContent: function (){
+ var slide = this.get('slide');
+ if (slide.type === 'webpage') {
+ return this._fetchHtmlContent();
+ }
+ return $.when();
+ },
+ /**
+ * Extend the slide data list to add informations about rendering method, and other
+ * specific values according to their slide_type.
+ */
+ _preprocessSlideData: function (slidesDataList) {
+ slidesDataList.forEach(function (slideData, index) {
+ // compute hasNext slide
+ slideData.hasNext = index < slidesDataList.length-1;
+ // compute embed url
+ if (slideData.type === 'video') {
+ slideData.embedCode = $(slideData.embedCode).attr('src'); // embedCode containts an iframe tag, where src attribute is the url (youtube or embed document from odoo)
+ slideData.embedUrl = "https://" + slideData.embedCode + "&rel=0&autoplay=1&enablejsapi=1&origin=" + window.location.origin;
+ } else if (slideData.type === 'infographic') {
+ slideData.embedUrl = _.str.sprintf('/web/image/slide.slide/%s/image', slideData.id);
+ } else if (_.contains(['document', 'presentation'], slideData.type)) {
+ slideData.embedUrl = $(slideData.embedCode).attr('src');
+ }
+ // fill empty property to allow searching on it with _.filter(list, matcher)
+ slideData.isQuiz = !!slideData.isQuiz;
+ slideData.hasQuestion = !!slideData.hasQuestion;
+ // technical settings for the Fullscreen to work
+ slideData._autoSetDone = _.contains(['infographic', 'presentation', 'document', 'webpage'], slideData.type) && !slideData.hasQuestion;
+ });
+ return slidesDataList;
},
/**
* Changes the url whenever the user changes slides.
- * This allows the user to refresh the page and stay at the right video
+ * This allows the user to refresh the page and stay on the right slide
+ *
+ * @private
*/
- _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);
+ _pushUrlState: function (){
+ var urlParts = window.location.pathname.split('/');
+ urlParts[urlParts.length-1] = this.get('slide').slug;
+ var url = _.str.sprintf('%s?fullscreen=1', urlParts.join('/'));
+ history.pushState(null, '', url);
},
/**
- * Whenever the user changes slide, change the active tab
+ * Render the current slide content using specific mecanism according to slide type:
+ * - simply append content (for webpage)
+ * - template rendering (for image, document, ....)
+ * - using a sub widget (quiz and video)
+ *
+ * @private
+ * @returns Deferred
*/
- _setActiveTab: function (){
- var self = this;
- self.activeTab.removeClass('active');
- $('li.active').removeClass('active');
- $('li[data-slide-id='+self.slide.id+']').addClass('active');
- self.activeTab = $('.o_wslides_fs_sidebar_slide_tab[index="'+self.index+'"]');
- self.activeTab.addClass('active');
+ _renderSlide: function () {
+ var slide = this.get('slide');
+ var $content = this.$('.o_wslides_fs_content');
+ $content.empty();
+ $content.removeClass('bg-white'); // webpage case
+
+ // display quiz slide, or quiz attached to a slide
+ if (slide.type === 'quiz' || slide.isQuiz) {
+ var QuizWidget = new Quiz(this, slide);
+ return QuizWidget.appendTo($content);
+ }
+
+ // render slide content
+ if (_.contains(['document', 'presentation', 'infographic'], slide.type)) {
+ $content.html(QWeb.render('website.slides.fullscreen.content', {widget: this}));
+ } else if (slide.type === 'video') {
+ this.videoPlayer = new VideoPlayer(this, slide);
+ return this.videoPlayer.appendTo($content);
+ } else if (slide.type === 'webpage'){
+ $(slide.htmlContent).appendTo($content);
+ $content.addClass('bg-white');
+ }
+ return $.when();
},
/**
- * The first time the user gets on the player,
- * get the slide that is represented by the active tab in the sidebar
+ * Once the completion conditions are filled,
+ * rpc call to set the the relation between the slide and the user as "completed"
+ *
+ * @private
+ * @param {Integer} slideId: the id of slide to set as completed
*/
- _getActiveSlide: function (){
+ _setCompleted: function (slideId){
var self = this;
- self.activeTab = $('.o_wslides_fs_sidebar_slide_tab.active');
- self.index = parseInt(self.activeTab.attr('index'), 10);
- self.slide = self.slides[self.index];
- self._setPreviousAndNextSlides();
+ var slide = findSlide(this.slides, {id: slideId});
+ if (!slide.completed) { // no useless RPC call
+ return this._rpc({
+ route: '/slides/slide/set_completed',
+ params: {
+ slide_id: slide.id,
+ }
+ }).then(function (data){
+ slide.completed = true;
+ self.sidebar.setSlideCompleted(slide.id);
+ self.sidebar.updateProgressbar(data.channel_completion);
+ });
+ }
+ return $.when();
},
//--------------------------------------------------------------------------
// 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[data-slide-id='+self.slide.id+']').addClass('active');
- self._setPreviousAndNextSlides();
- self._updateUrl();
- history.pushState(null,'',self.url);
- }
- },
- _sidebarToggle: function (ev){
- ev.preventDefault();
- $(ev.currentTarget).toggleClass('active');
- $('.o_wslides_fs_sidebar').toggleClass('o_wslides_fs_sidebar_hidden');
- $('.o_wslides_fs_player').toggleClass('o_wslides_fs_player_no_sidebar');
- },
- _onMiniQuizClick: function (ev){
- var self = this;
- self.index = parseInt($(ev.currentTarget).attr('index')) || 0;
- self.slide = self.slides[self.index];
- self.goToQuiz = true;
- self._setPreviousAndNextSlides();
- self._renderPlayer();
- self._setActiveTab();
- self._updateUrl();
- history.pushState(null,'' ,self.url);
- },
- _onQuizCompleted: function (ev) {
- this._setSlideStateAsDone();
- },
- /**
- * @private
- * Binds events related to the list
- */
- _bindListEvents: function (){
+ /**
+ * Method called whenever the user changes slides.
+ * When the current slide is changed, it will autoimatically update the widget
+ * and allow it to fetch the content if needs be, render it, update the url and set the
+ * slide as "completed" if its type makes it necessary
+ *
+ * @private
+ */
+ _onChangeSlide: function (){
var self = this;
- $('.o_wslides_fs_sidebar_slide_tab').each(function () {
- $(this).click(self._onListCellClick.bind(self));
- });
- $('.o_wslides_fs_slide_quiz ').each(function (){
- $(this).click(self._onMiniQuizClick.bind(self));
+ var slide = this.get('slide');
+ return this._fetchSlideContent().then(function() { // update title and render content
+ self.$('.o_wslides_fs_slide_title').html(QWeb.render('website.slides.fullscreen.title', {widget: self}));
+ return self._renderSlide();
+ }).then(function() {
+ if (slide._autoSetDone) {
+ self._setCompleted(slide.id);
+ }
+ self._pushUrlState();
});
- $('.o_wslides_fs_toggle_sidebar').click(function (ev){
- self._sidebarToggle(ev);
+ },
+ /**
+ * Changes current slide when receiving custom event `change_slide` with
+ * its id and if it's its quizz or not we need to display.
+ *
+ * @private
+ */
+ _onChangeSlideRequest: function (ev){
+ var slideData = ev.data;
+ var newSlide = findSlide(this.slides, {
+ id: slideData.id,
+ isQuiz: slideData.isQuiz || false,
});
+ this.set('slide', newSlide);
},
- _onKeyDown: function (ev){
- var self = this;
- switch (ev.key){
- case "ArrowRight":
- self._goToNextSlide();
- break;
- case "ArrowLeft":
- self._goToPreviousSlide();
- break;
- }
+ /**
+ * Triggered when sub widget business is done and that slide
+ * can now be marked as done.
+ *
+ * @private
+ */
+ _onSlideCompleted: function (ev) {
+ var slideId = ev.data.id;
+ this._setCompleted(slideId);
+ },
+ /**
+ * Go the next slide
+ *
+ * @private
+ */
+ _onSlideGoToNext: function (ev) {
+ this.sidebar.goNext();
+ },
+ /**
+ * Show or Hide the sidebar
+ *
+ * @private
+ */
+ _onClickToggleSidebar: function (ev){
+ ev.preventDefault();
+ this.$('.o_wslides_fs_sidebar').toggleClass('d-none');
+ this.$('.o_wslides_fs_content').toggleClass('col-10').toggleClass('col-12');
+ this.$('.o_wslides_fs_toggle_sidebar').toggleClass('active');
},
});
sAnimations.registry.websiteSlidesFullscreenPlayer = Widget.extend({
selector: '.o_wslides_fs_main',
xmlDependencies: ['/website_slides/static/src/xml/website_slides_fullscreen.xml'],
- init: function (el){
- this._super.apply(this, arguments);
- },
start: function (){
var defs = [this._super.apply(this, arguments)];
- var userId = this.$el.data('userId');
- var courseId = this.$el.data('courseId');
- var slideId = this.$el.data('slideId');
- var fullscreen = new Fullscreen(this, courseId, slideId, userId);
- defs.push(fullscreen.attachTo(this.$el));
+ var fullscreen = new Fullscreen(this, this._getSlides(), this._getCurrentSlideID());
+ defs.push(fullscreen.attachTo(".o_wslides_fs_main"));
return $.when.apply($, defs);
- }
+ },
+ _getCurrentSlideID: function (){
+ return parseInt(this.$('.o_wslides_fs_sidebar_list_item.active').data('id'));
+ },
+ /**
+ * @private
+ * Creates slides objects from every slide-list-cells attributes
+ */
+ _getSlides: function (){
+ var $slides = this.$('.o_wslides_fs_sidebar_list_item');
+ var slideList = [];
+ $slides.each(function () {
+ var slideData = $(this).data();
+ slideList.push(slideData);
+ });
+ return slideList;
+ },
});
return Fullscreen;
-}); \ No newline at end of file
+});
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 319ca629bf2..c969e38623b 100644
--- a/addons/website_slides/static/src/js/slides_course_quiz.js
+++ b/addons/website_slides/static/src/js/slides_course_quiz.js
@@ -15,9 +15,8 @@ odoo.define('website_slides.quiz', function (require) {
* used client side (Fullscreen).
*
* Triggered events are :
- * - quiz_next_slide: need to go to the next slide, when quiz is done. Event data contains the current slide id.
- * - quiz_completed: when the quiz is passed and completed by the user. Event data contains completion
- * percentage and current slide id.
+ * - slide_go_next: need to go to the next slide, when quiz is done. Event data contains the current slide id.
+ * - quiz_completed: when the quiz is passed and completed by the user. Event data contains current slide data.
*/
var Quiz = Widget.extend({
template: 'slide.slide.quiz',
@@ -88,6 +87,8 @@ odoo.define('website_slides.quiz', function (require) {
}
else if (alert_code === 'slide_quiz_done') {
message = _t('This quiz is already done. Retaking it is not possible.');
+ } else if (alert_code === 'public_user') {
+ message = _t("You must be logged to submit the quiz.");
}
this.$('.o_wslides_js_lesson_quiz_error span').html(message);
this.$('.o_wslides_js_lesson_quiz_error').removeClass('d-none');
@@ -116,7 +117,6 @@ odoo.define('website_slides.quiz', function (require) {
_renderAnswers: function () {
var self = this;
this.$('input[type=radio]').each(function () {
- console.log($(this));
$(this).prop('disabled', self.slide.readonly);
});
},
@@ -184,9 +184,7 @@ odoo.define('website_slides.quiz', function (require) {
* @param {Integer} completion
*/
_setCompleted: function () {
- this.trigger_up('quiz_completed', {
- 'slideId': this.slide.id,
- });
+ this.trigger_up('quiz_completed', this.slide);
},
/*
* Submit the given answer, and display the result
@@ -241,9 +239,7 @@ odoo.define('website_slides.quiz', function (require) {
*/
_onClickNext: function (ev) {
if (this.slide.hasNext) {
- this.trigger_up('quiz_next_slide', {
- 'slideId': this.slide.id,
- });
+ this.trigger_up('slide_go_next');
}
},
/**
@@ -272,7 +268,7 @@ odoo.define('website_slides.quiz', function (require) {
sAnimations.registry.websiteSlidesQuizNoFullscreen = sAnimations.Class.extend({
selector: '.o_wslides_js_lesson_quiz',
custom_events: {
- quiz_next_slide: '_onQuizNextSlide',
+ slide_go_next: '_onQuizNextSlide',
quiz_completed: '_onQuizCompleted',
},
diff --git a/addons/website_slides/static/src/scss/slides_slide_fullscreen.scss b/addons/website_slides/static/src/scss/slides_slide_fullscreen.scss
index 08ef8bc299b..3868ad873cb 100644
--- a/addons/website_slides/static/src/scss/slides_slide_fullscreen.scss
+++ b/addons/website_slides/static/src/scss/slides_slide_fullscreen.scss
@@ -1,3 +1,9 @@
+$slides-bg-fullscreen: #313A44;
+$slides-bg-fullscreen-dark: darken($slides-bg-fullscreen,5%);
+$slides-bg-fullscreen-darker: darken($slides-bg-fullscreen,10%);
+
+$slides-text: #8A8F93;
+
.o_wslides_fs_main {
position: absolute;
top: 0;
@@ -5,267 +11,60 @@
width: 100%;
height: 100%;
z-index: 2000;
+ color: $slides-text;
+
.o_wslides_slide_fs_header {
- align-items: center;
- background-color: #313A44;
- color: #AEB0B2;
- display: flex;
+ background-color: $slides-bg-fullscreen;
height: 50px;
- justify-content: space-between;
- width: 100%;
-
- .o_wslides_fs_slide_title {
- height: 100%;
- display: flex;
- align-items: center;
- padding: 0 20px;
- color: #D5D7D9;
- font-weight: bold;
-
- i.fa-flag-checkered{
- color: #D39B4B;
- margin-right: 10px;
- }
- }
-
- div {
- display: flex;
- height: 100%;
-
- a {
- align-items: center;
- background-color: #1F242C;
- color: inherit;
- display: flex;
- font-size: 0.8rem;
- font-weight: bold;
- height: 100%;
- justify-content: center;
- padding: 0 20px;
- text-decoration: none !important;
- text-transform: uppercase;
-
- i {
- align-items: center;
- display: flex;
- height: 100%;
- margin: 0;
- margin-right: 5px;
- }
- }
-
- a:hover,
- a.active{
- background-color: #313A44;
- color: #fff;
- }
- }
}
.o_wslides_fs_container {
- display: flex;
- align-items: stretch;
height: calc(100% - 50px);
- position: relative;
- background-color: #313A44;
- }
-
- .o_wslides_fs_sidebar_hidden{
- transform: translateX(-100%);
+ background-color: $slides-bg-fullscreen;
}
- .o_wslides_fs_sidebar{
- width: 400px;
- min-width: 400px;
- background-color: #313A44;
- color: #fff;
+ .o_wslides_fs_sidebar {
transition: all .2s ease-in;
overflow: auto;
- a {
- text-decoration: none !important;
- color: inherit;
- }
-
- ul {
- margin: 0;
- padding: 0;
- list-style: none;
- }
-
- li {
- padding-left: 20px;
- }
-
.o_wslides_fs_sidebar_header {
- height: 100px;
- padding: 20px 0;
- display: flex;
- flex-direction: column;
-
- .o_wslides_slide_fs_progress_box{
- display: flex;
- align-items: center;
- font-size: .9rem;
- .o_wslides_fs_sidebar_progressbar{
- height: 5px;
- border-radius: 10px;
- width: 70%;
- background-color: rgba(0,0,0,.3);
- margin-right: 40px;
- overflow: hidden;
- }
- .progress-bar {
- background-color: $primary;
- height: 100%;
- width: 0;
- transition: all 1.5s ease-in;
- }
- span {
- color: #BABDC0;
- }
- }
- a {
- font-size: 1.5rem;
- font-weight: bold;
+ color: $slides-text;
+ .progress {
+ height: 5px;
}
}
+
.o_wslides_fs_sidebar_section {
- margin-bottom: 20px;
- background-color: #283038;
- padding: 10px 0;
- > a > li {
- color: #656B72;
- font-size: .8rem;
- text-transform: uppercase;
- font-weight: bold;
- padding: 5px 0 5px 15px;
+ background-color: $slides-bg-fullscreen-dark;
+ border: none;
+ border-radius: 0;
+
+ .card-body {
+ color: $slides-text;
}
}
- .o_wslides_fs_sidebar_section_slides{
- padding: 0;
- margin-bottom: 15px;
- li {
- margin: 0;
- padding: 0;
- border-left: 2px solid transparent;
- display: flex;
- a {
- flex: 1;
- }
- > span{
- width: 15px;
- padding: 0;
- margin: 0;
- margin-left: 10px;
- display: flex;
- flex-direction: column;
- align-items: center;
- i {
- z-index: 100;
- padding: 0;
- margin: 0;
- }
- i.o_wslides_slide_completed{
- color: #00A04A;
- border-radius: 50%;
- overflow: hidden;
- background-color: #fff;
- height: 12px;
- width: 12px;
- display: flex;
- justify-content: center;
- align-items: center;
- border: 1px solid #00A04A;
- }
- .o_wslides_fs_full_line{
- width: 1px;
- height: 100%;
- background-color: #787D82;
- }
- }
- }
- .o_wslides_top_line,
- .o_wslides_bottom_line{
- width: 1px;
- display: flex;
- flex-grow: 1;
- background-color: #787D82;
- }
- li:first-child .o_wslides_top_line{
- background-color: transparent;
- }
- li:last-child .o_wslides_bottom_line{
- background-color: transparent;
- }
+
+ .o_wslides_fs_sidebar_list{
li:hover,
li.active {
- border-left: 2px solid $primary;
cursor: pointer;
- background-color: #1D2228;
+ background-color: $slides-bg-fullscreen-darker;
}
- .o_wslides_fs_sidebar_slide_tab {
- padding:5px 0 5px 15px;
- font-size: .8rem;
- color: #8A8F93;
- font-weight: bold;
- flex: 1;
+ li {
+ color: $slides-text;
}
- .o_wslides_fs_sidebar_slide_tab.active{
+ li.active{
+ border-left: 2px solid $primary;
color: #fff;
}
- .o_wslides_fs_slide_link {
- color: #098586;
- font-size: .8rem;
- margin-left: 15px;
- flex: 1;
- i {
- margin-right: 10px;
- }
- }
- .o_wslides_fs_slide_quiz {
- font-size: .8rem;
- margin-left: 15px;
- color: #8A8F93;
- flex: 1;
- i {
- color: #D39B4B;
- margin-right: 10px;
- }
- }
}
-
-
}
- .o_wslides_fs_player {
- display: flex;
- align-items: stretch;
- max-width: calc(100% - 400px);
- height: 100%;
- background-color: #283038;
- transition: all .2s ease-in;
- position: absolute;
- top: 0;
- right: 0;
- height: 100%;
- width: 80%;
-
- iframe {
- height: 100%;
- width: 100%;
- }
+ .o_wslides_fs_content {
+ background-color: $slides-bg-fullscreen-dark;
}
- .o_wslides_fs_webpage {
- background-color: #fff;
- width: 100%;
- height: 100%;
- overflow-y: scroll;
- padding: 20px 0;
- }
-
- .o_wslides_fs_player_no_sidebar {
+ .o_wslides_fs_content_no_sidebar {
max-width: 100%;
width: 100%;
}
@@ -273,10 +72,8 @@
// quizz
.o_wslides_fs_quiz_container {
background-color: $gray-200;
- height: 100%;
overflow: scroll;
overflow-x: hidden;
- width: 100%;
.o_wslides_js_lesson_quiz_question {
li {
diff --git a/addons/website_slides/static/src/xml/slide_quiz.xml b/addons/website_slides/static/src/xml/slide_quiz.xml
index f512985a3af..0af500914c5 100644
--- a/addons/website_slides/static/src/xml/slide_quiz.xml
+++ b/addons/website_slides/static/src/xml/slide_quiz.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="slide.slide.quiz">
- <div class="o_wslides_fs_quiz_container">
+ <div class="o_wslides_fs_quiz_container h-100 w-100">
<div class="mx-5">
<div t-foreach="widget.quiz.questions" t-as="question"
class="o_wslides_js_lesson_quiz_question mt-3" t-att-data-question-id="question.id" t-att-data-title="question.question">
diff --git a/addons/website_slides/static/src/xml/website_slides_fullscreen.xml b/addons/website_slides/static/src/xml/website_slides_fullscreen.xml
index b364f486420..683dc45752d 100644
--- a/addons/website_slides/static/src/xml/website_slides_fullscreen.xml
+++ b/addons/website_slides/static/src/xml/website_slides_fullscreen.xml
@@ -1,41 +1,36 @@
<templates id="template" xml:space="preserve">
- <t t-name="website.slides.fullscreen">
- <div t-if="slide.slide_type == 'video' &amp;&amp; !showMiniQuiz" class="player embed-responsive embed-responsive-16by9 embed-responsive-item">
- <iframe id="youtube-player" t-att-src="embed_url" allowFullScreen="true" frameborder="0" enablejsapi="1" autoplay="1" allow="autoplay"></iframe>
- </div>
- <div t-if="['presentation', 'document'].indexOf(slide.slide_type) !== -1 &amp;&amp; !showMiniQuiz" class="embed-responsive embed-responsive-4by3 embed-responsive-item ">
- <iframe t-att-src="embed_url" class="o_wslides_iframe_viewer" allowFullScreen="true" height="315" width="420" frameborder="0"></iframe>
- </div>
- <div t-if="slide.slide_type == 'infographic' &amp;&amp; !showMiniQuiz" style="width:100%;height:100%;text-align:center;">
- <img t-attf-src="/web/image/slide.slide/#{slide.id}/image" class="img-fluid" style="height: 100%" alt="Slide image"/>
- </div>
- <div class="o_wslides_fs_content"/>
- </t>
- <t t-name="website.course.player.infos">
- <div class="text-box">
- <h1><t t-esc="slide.name"/></h1>
- <a t-att-href="url" class="oe_js_show_comments">Show comments</a>
- </div>
- <div>
- <button class="oe_js_slide_full_screen btn btn-secondary">Full Screen</button>
- <button t-att-current_slide_id="slide.id" class="btn btn-primary oe_js_slide_completed">Mark as completed<i class="fa fa-check-circle ml-1"></i></button>
- </div>
+ <t t-name="website.slides.fullscreen.title">
+ <t t-if="!widget.get('slide').isQuiz">
+ <i t-if="widget.get('slide').type == 'document'" class="fa fa-file-pdf-o mr-2"></i>
+ <i t-if="widget.get('slide').type == 'infographic'" class="fa fa-file-picture-o mr-2"></i>
+ <i t-if="widget.get('slide').type == 'video'" class="fa fa-play-circle mr-2"></i>
+ <i t-if="widget.get('slide').type == 'webpage'" class="fa fa-file-text mr-2"></i>
+ <i t-if="widget.get('slide').type == 'quiz'" class="fa fa-question-circle mr-2 text"></i>
+ <span class="o_wslides_fs_slide_title_span" t-esc="widget.get('slide').name"/>
+ </t>
+ <t t-else="">
+ <span><i class="fa fa-flag-checkered"></i>Quiz: <span t-esc="widget.get('slide').name"/></span>
+ </t>
</t>
- <t t-name="website.course.fullscreen.title">
- <t t-if="!miniQuiz">
- <i t-if="slide.slide_type == 'document'" class="fa fa-file-pdf-o mr-2"></i>
- <i t-if="slide.slide_type == 'infographic'" class="fa fa-file-picture-o mr-2"></i>
- <i t-if="slide.slide_type == 'video'" class="fa fa-play-circle mr-2"></i>
- <i t-if="slide.slide_type == 'link'" class="fa fa-file-code-o mr-2"></i>
- <i t-if="slide.slide_type == 'webpage'" class="fa fa-file-text mr-2"></i>
- <i t-if="slide.slide_type == 'quiz'" class="fa fa-question-circle mr-2 text"></i>
- <span class="o_wslides_fs_slide_title_span" t-esc="slide.name"/>
+ <t t-name="website.slides.fullscreen.content">
+ <t t-if="_.contains(['document', 'presentation'], widget.get('slide').type)">
+ <div class="embed-responsive embed-responsive-4by3 embed-responsive-item h-100">
+ <iframe t-att-src="widget.get('slide').embedUrl" class="o_wslides_iframe_viewer h-100 w-100" allowFullScreen="true" frameborder="0"></iframe>
+ </div>
</t>
- <t t-else="">
- <span><i class="fa fa-flag-checkered"></i>Quiz: <span t-esc="slide.name"/></span>
+ <t t-if="widget.get('slide').type === 'infographic'">
+ <div class="w-100 h-100 text-center">
+ <img t-att-src="'/web/image/slide.slide/'+ widget.get('slide').id +'/image'" class="img-fluid" style="height: 100%" alt="Slide image"/>
+ </div>
</t>
</t>
+ <t t-name="website.slides.fullscreen.video">
+ <div class="player embed-responsive embed-responsive-16by9 embed-responsive-item h-100">
+ <iframe t-att-id="'youtube-player' + widget.slide.id" t-att-src="widget.slide.embedUrl" allowFullScreen="true" frameborder="0" enablejsapi="1" autoplay="1" allow="autoplay"></iframe>
+ </div>
+ </t>
+
</templates>
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 1361559c23f..8285a76bacd 100644
--- a/addons/website_slides/views/website_slides_templates_lesson_fullscreen.xml
+++ b/addons/website_slides/views/website_slides_templates_lesson_fullscreen.xml
@@ -4,115 +4,125 @@
<!-- Slide template for the fullscreen mode -->
<template id="slide_fullscreen" name="Fullscreen">
<t t-call="website.layout">
- <div class="o_wslides_fs_main"
+ <div class="o_wslides_fs_main d-flex flex-column"
t-att-data-user-id="user.id"
t-att-data-course-name="slide.channel_id.name"
t-att-data-course-id="slide.channel_id.id"
t-att-data-course-slug="slug(slide.channel_id)"
t-att-data-slide-id="slide.id">
- <div class="o_wslides_slide_fs_header">
- <div>
- <a id="fullscreen_sidebar_button" href="#" class="active o_wslides_fs_toggle_sidebar"><i class="fa fa-bars"></i>Lessons</a>
- <a class="o_wslides_small_screen" t-attf-href="/slides/slide/#{slug(slide)}" target="new"><i class="fa fa-pencil"></i>write a review</a>
- <div class="o_wslides_fs_slide_title"/>
+ <div class="o_wslides_slide_fs_header d-flex w-100">
+ <a class="p-3 h-100 d-inline-block o_wslides_fs_toggle_sidebar active text-uppercase" href="#">
+ <i class="fa fa-bars"/> Lessons
+ </a>
+ <a class="p-3 h-100 d-inline-block text-uppercase" t-attf-href="/slides/slide/#{slug(slide)}" target="new">
+ <i class="fa fa-pencil"/> Write a review
+ </a>
+ <div class="p-3 h-100 d-inline-block o_wslides_fs_slide_title font-weight-bold">
+ <t>
+ <t t-call="website_slides.slide_icon">
+ <t t-set="icon_class" t-value="'mr-2 text'"/>
+ </t>
+ <span class="o_wslides_fs_slide_title_span" t-esc="slide.name"/>
+ </t>
</div>
- <div>
- <a t-attf-href="/slides/">back to courses</a>
+ <div class="ml-auto p-3 text-uppercase">
+ <a href="/slides/">Back to courses</a>
</div>
</div>
- <div class="o_wslides_fs_container">
- <div class="o_wslides_fs_sidebar">
- <ul>
- <li>
- <div class="o_wslides_fs_sidebar_header">
- <a t-attf-href="/slides/#{slug(slide.channel_id)}">
- <span t-field="slide.channel_id.name"/>
- </a>
- <div class="o_wslides_slide_fs_progress_box">
- <div class="progress o_wslides_fs_sidebar_progressbar">
- <div class="progress-bar" role="progressbar"
- t-att-aria-valuenow="slide.channel_id.completion" aria-valuemin="0" aria-valuemax="100"
- t-att-style="'width: %s' % (slide.channel_id.completion)"/>
- </div>
- <div><span class="o_wslides_progress_percentage" t-esc="slide.channel_id.completion"/>%</div>
- </div>
+ <div class="o_wslides_fs_container d-flex flex-wrap">
+ <div class="col-2 o_wslides_fs_sidebar p-0">
+ <div class="o_wslides_fs_sidebar_header mt-2 p-2">
+ <a t-attf-href="/slides/#{slug(slide.channel_id)}">
+ <span class="h3" t-field="slide.channel_id.name"/>
+ </a>
+ <div class="mt-2">
+ <div class="progress">
+ <div class="progress-bar" role="progressbar" t-attf-style="width: #{slide.channel_id.completion}%" t-att-aria-valuenow="slide.channel_id.completion" aria-valuemin="0" aria-valuemax="100"></div>
</div>
- </li>
+ <span class="o_wslides_progress_percentage" t-esc="slide.channel_id.completion"/> %
+ </div>
+ </div>
+ <div class="o_wslides_fs_category mt-4">
<t t-if="uncategorized_slides" t-call="website_slides.slide_fullscreen_sidebar_category">
- <t t-set="category_slide_ids" t-value="uncategorized_slides" />
+ <t t-set="slides" t-value="uncategorized_slides"/>
+ <t t-set="current_slide" t-value="slide"/>
</t>
<t t-foreach="slide.channel_id.category_ids" t-as="category">
- <t t-call="website_slides.slide_fullscreen_sidebar_category">
- <t t-set="category_slide_ids" t-value="category.slide_ids" />
+ <t t-if="category.slide_ids">
+ <t t-call="website_slides.slide_fullscreen_sidebar_category">
+ <t t-set="slides" t-value="category.slide_ids"/>
+ <t t-set="current_slide" t-value="slide"/>
+ </t>
</t>
</t>
- </ul>
+ </div>
</div>
- <div class="o_wslides_fs_player"/>
+ <div class="col-10 o_wslides_fs_content p-0"></div>
</div>
</div>
</t>
</template>
<template id="slide_fullscreen_sidebar_category" name="Slides category template for fullscreen view side bar">
- <div class="o_wslides_fs_sidebar_section">
- <a data-toggle="collapse" t-att-href="('#collapse-%s') % (category.id if category else 0)" role="button" aria-expanded="true" t-att-aria-controls="('collapse-%s') % (category.id if category else 0)">
- <li>
- <t t-if="category">
- <span t-field="category.name"/>
- </t>
- <t t-else="">
- <span>Uncategorized</span>
- </t>
- </li>
- </a>
- <ul class="o_wslides_fs_sidebar_section_slides collapse show p-0 m-0" t-att-id="('collapse-%s') % (category.id if category else 0)" >
- <t t-foreach="category_slide_ids" t-as="course_slide">
- <!-- AWA FIXME: remove that index crap but right now fullscreen JS uses it too much -->
- <t t-set="i" t-value="list(slide.channel_id.slide_ids).index(course_slide)"/>
- <li t-att-index="i" t-att-data-slide-id="course_slide.id" t-attf-class="#{'active' if slide.id == course_slide.id else ''}">
- <span>
- <span class="o_wslides_top_line"></span>
- <i t-attf-id="check-#{course_slide.id}" t-if="not channel_progress[course_slide.id].get('completed')" class="check-done fa fa-circle-thin"></i>
- <i t-attf-id="check-#{course_slide.id}" t-if="channel_progress[course_slide.id].get('completed')" class="check-done o_wslides_slide_completed fa fa-check-circle"></i>
- <span class="o_wslides_bottom_line"></span>
- </span>
- <a t-att-index="i" t-att-data-slide-id="course_slide.id">
- <div t-attf-index="#{i}"
- t-att-data-slug="slug(course_slide)"
- t-att-data-done="channel_progress[course_slide.id].get('completed')"
- t-att-data-id="course_slide.id"
- t-att-data-readonly="not course_slide.channel_id.is_member"
- t-att-data-name="course_slide.name"
- t-att-data-has-quiz="True if course_slide.question_ids else False"
- t-att-data-slide_type="course_slide.slide_type"
- t-att-data-embed_code="course_slide.embed_code"
- t-attf-class="o_wslides_fs_sidebar_slide_tab #{'active' if slide.id == course_slide.id else ''} d-flex justify-content-between">
- <div>
- <t t-call="website_slides.slide_icon">
- <t t-set="slide" t-value="course_slide" />
- </t>
- <t t-esc="course_slide.name"/>
- </div>
+ <div class="card o_wslides_fs_sidebar_section mb-3" t-attf-id="category-collapse-#{category.id if category else 0}">
+ <div class="card-header" t-attf-id="#category-title-#{category.id if category else 0}" data-toggle="collapse" t-attf-data-target="#category-#{category.id if category else 0}" aria-expanded="true" t-attf-aria-controls="category-#{category.id if category else 0}">
+ <t t-if="category">
+ <span class="h6 text-uppercase" t-field="category.name"/>
+ </t>
+ <t t-else="">
+ <span class="h6 text-uppercase">Uncategorized</span>
+ </t>
+ </div>
+ <div t-attf-id="category-#{category.id if category else 0}" class="collapse show" t-attf-aria-labelledby="#category-title-#{category.id if category else 0}" t-attf-data-parent="#category-collapse-#{category.id if category else 0}">
+ <div class="card-body p-0" style="background-color: transparent !important;"><!-- inline style as boostrap used !important property to force color on .card-body -->
+ <ul class="o_wslides_fs_sidebar_list list-group list-unstyled collapse show p-0 m-0" t-att-id="('collapse-%s') % (category.id if category else 0)" >
+ <t t-foreach="slides" t-as="slide">
+ <t t-set="slide_completed" t-value="channel_progress[slide.id].get('completed')"/>
+ <li t-att-class="'o_wslides_fs_sidebar_list_item d-flex p-1 font-weight-bold %s' % ('active' if slide.id == current_slide.id else '')"
+ t-att-data-id="slide.id"
+ t-att-data-name="slide.name"
+ t-att-data-type="slide.slide_type"
+ t-att-data-slug="slug(slide)"
+ t-att-data-has-question="1 if slide.question_ids else 0"
+ t-att-data-is-quiz="0"
+ t-att-data-completed="1 if slide_completed else 0"
+ t-att-data-embed-code="slide.embed_code if slide.slide_type in ['video', 'document', 'presentation', 'infographic'] else False"
+ t-att-data-readonly="not channel_progress[slide.id]"
+ >
+ <div class="mr-2 ml-2 flex-grow">
+ <i t-if="not slide_completed" class="fa fa-circle-thin" t-att-data-slide-id="slide.id"/>
+ <i t-if="slide_completed" class="fa fa-check-circle text-success" t-att-data-slide-id="slide.id"/>
+ </div>
+ <div class="">
+ <t t-call="website_slides.slide_icon">
+ <t t-set="icon_class" t-value="'mr-2'"/>
+ <t t-set="slide" t-value="slide"/>
+ </t>
+ <span t-field="slide.name"/>
+
+ <ul t-if="slide.link_ids" class="list-group list-unstyled">
+ <li t-foreach="slide.link_ids" t-as="link">
+ <a class="btn btn-link" t-att-href="link.link" target="new"><i class="fa fa-link"/> <span t-esc="link.name"/></a>
+ </li>
+ </ul>
</div>
- </a>
- </li>
- <t t-if="course_slide.link_ids" t-foreach="course_slide.link_ids" t-as="link">
- <li>
- <span>
- <span class="o_wslides_fs_full_line"/>
+ </li>
+ <li t-if="slide.question_ids and slide.slide_type != 'quiz'" class="o_wslides_fs_sidebar_list_item"
+ t-att-data-id="slide.id"
+ t-att-data-name="slide.name"
+ t-att-data-type="slide.slide_type"
+ t-att-data-slug="slug(slide)"
+ t-att-data-has-question="1 if slide.question_ids else 0"
+ t-att-data-is-quiz="1">
+ <span class="ml-4">
+ <i class="fa fa-flag-checkered"/> Quiz: <span t-field="slide.name"/>
</span>
- <a class="o_wslides_fs_slide_link" t-att-href="link.link" target="new"><i class="fa fa-link"></i><span t-esc="link.name"/></a>
</li>
</t>
- <li t-att-index="i" t-if="course_slide.question_ids and not course_slide.slide_type =='quiz'">
- <span>
- <span class="o_wslides_fs_full_line"/>
- </span>
- <span class="o_wslides_fs_slide_quiz" t-att-index="i"><i class="fa fa-flag-checkered"></i>Quiz: <span t-esc="course_slide.name"/></span>
- </li>
- </t>
- </ul>
+ </ul>
+
+ </div>
+ </div>
</div>
</template>
diff --git a/addons/website_slides_forum/views/website_slides_templates.xml b/addons/website_slides_forum/views/website_slides_templates.xml
index 554d5b18978..dd9717a8bd7 100644
--- a/addons/website_slides_forum/views/website_slides_templates.xml
+++ b/addons/website_slides_forum/views/website_slides_templates.xml
@@ -12,8 +12,10 @@
<template id="slide_fullscreen" inherit_id="website_slides.slide_fullscreen">
<!-- Fullscreen template: add link to forum -->
- <xpath expr="//a[@id='fullscreen_sidebar_button']" position="after">
- <a t-if="slide.channel_id.forum_id" id="fullscreen_forum_button" t-attf-href="/forum/#{slug(slide.channel_id.forum_id)}" target="new"><i class="fa fa-comments"></i>Ask a question</a>
+ <xpath expr="//div[hasclass('o_wslides_fs_slide_title')]" position="before">
+ <a t-if="slide.channel_id.forum_id" class="p-3 h-100 d-inline-block text-uppercase" t-att-href="'/forum/%s' % (slug(slide.channel_id.forum_id))" target="new">
+ <i class="fa fa-comments"></i>Ask a question
+ </a>
</xpath>
</template>
diff --git a/addons/website_slides_survey/static/src/xml/website_slides_fullscreen.xml b/addons/website_slides_survey/static/src/xml/website_slides_fullscreen.xml
index c5d22bfc46f..6daa08ffe65 100644
--- a/addons/website_slides_survey/static/src/xml/website_slides_fullscreen.xml
+++ b/addons/website_slides_survey/static/src/xml/website_slides_fullscreen.xml
@@ -1,21 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates>
<t t-extend="website.slides.fullscreen">
- <t t-jquery=".o_wslides_fs_content" t-operation="append">
- <div class="o_wslides_fs_certification" t-if="slide.slide_type == 'certification' &amp;&amp; !slide.certificationDone">
- <a class="btn btn-primary" t-att-href="'/slides/slide/' + slide.slug" target="new"><i class="fa fa-trophy"/> Pass Certification</a>
+ <t t-jquery="t:last" t-operation="after">
+ <div t-if="widget.get('slide').type === 'certification' &amp;&amp; !widget.get('slide').certificationDone" class="o_wslides_fs_certification">
+ <a class="btn btn-primary" t-att-href="'/slides/slide/' + widget.get('slide').id" target="new"><i class="fa fa-trophy"/> Pass Certification</a>
</div>
- <div class="o_wslides_fs_certification" t-if="slide.slide_type == 'certification' &amp;&amp; slide.certificationDone">
- <a role="button" t-att-data-survey_id="slide.certificationId" class="btn btn-primary o_wslides_survey_download_certification" href="#">
+ <div class="o_wslides_fs_certification" t-if="widget.get('slide').type === 'certification' &amp;&amp; widget.get('slide').certificationDone">
+ <a role="button" t-att-data-survey_id="widget.get('slide').certificationId" class="btn btn-primary o_wslides_survey_download_certification" href="#">
<i class="fa fa-fw fa-trophy" role="img" aria-label="Download certification" title="Download certification"/> Download certification
</a>
</div>
</t>
</t>
- <t t-extend="website.course.fullscreen.title">
+ <t t-extend="website.slides.fullscreen.title">
<t t-jquery=".o_wslides_fs_slide_title_span" t-operation="before">
- <i t-if="slide.slide_type == 'certification'" class="fa fa-trophy mr-2 text"></i>
+ <i t-if="widget.get('slide').type === 'certification'" class="fa fa-trophy mr-2 text"></i>
</t>
</t>
</templates>
diff --git a/addons/website_slides_survey/views/website_slides_templates_lesson_fullscreen.xml b/addons/website_slides_survey/views/website_slides_templates_lesson_fullscreen.xml
index d42674336ac..03baa27ef70 100644
--- a/addons/website_slides_survey/views/website_slides_templates_lesson_fullscreen.xml
+++ b/addons/website_slides_survey/views/website_slides_templates_lesson_fullscreen.xml
@@ -2,9 +2,9 @@
<odoo><data>
<template id="slide_fullscreen_sidebar_category" inherit_id="website_slides.slide_fullscreen_sidebar_category">
- <xpath expr="//div[@t-attf-index='#{i}']" position="attributes">
- <attribute name="t-att-data-certification-done">channel_progress[course_slide.id].get('survey_quizz_passed')</attribute>
- <attribute name="t-att-data-certification-id">course_slide.survey_id.id</attribute>
+ <xpath expr="//li[hasclass('o_wslides_fs_sidebar_list_item')]" position="attributes">
+ <attribute name="t-att-data-certification-done">channel_progress[slide.id].get('survey_quizz_passed')</attribute>
+ <attribute name="t-att-data-certification-id">slide.survey_id.id</attribute>
</xpath>
</template>