From 5fbfd2a7a4b9c4f787939c88461958ba6bddcac8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Warnon?= Date: Fri, 22 Feb 2019 14:07:19 +0000 Subject: [PATCH] [REF] website_slides: refactor the course reordering of slides and categories Purpose ======= This commit refactors the way slides and categories are reordered in the frontend course view (training courses). The use of jqueryui's "sortable" widget makes it easier and more user friendly to drag and drop both sections and slides to reorganize your course. Commit linked to task ID 1942852 and PR #31394. --- addons/website_slides/controllers/main.py | 33 +- .../src/js/slides_course_slides_list.js | 368 ++++++------------ .../static/src/js/slides_slide_archive.js | 2 +- .../static/src/js/slides_upload.js | 25 -- .../website_slides_drag_and_drop_list.scss | 69 ++-- .../views/website_slides_templates_course.xml | 18 +- .../views/website_slides_templates_lesson.xml | 2 +- 7 files changed, 177 insertions(+), 340 deletions(-) diff --git a/addons/website_slides/controllers/main.py b/addons/website_slides/controllers/main.py index e7fd8c238b8..e314965f52b 100644 --- a/addons/website_slides/controllers/main.py +++ b/addons/website_slides/controllers/main.py @@ -644,6 +644,24 @@ class WebsiteSlides(WebsiteProfile): return {'error': 'join_done'} return success + @http.route('/slides/channel/resequence', type="json", website=True, auth="user") + def resequence_slides(self, channel_id, slides_data): + """" Reorder the slides within the channel by reassigning their 'sequence' field. + This method also handles slides that are put in a new category. """ + channel = request.env['slide.channel'].browse(int(channel_id)) + if not channel.can_publish: + return {'error': 'Only the publishers of the channel can edit it'} + + slides = request.env['slide.slide'].search([ + ('id', 'in', [int(key) for key in slides_data.keys()]), + ('channel_id', '=', channel.id) + ]) + + for slide in slides: + slide_key = str(slide.id) + slide.sequence = slides_data[slide_key]['sequence'] + slide.category_id = slides_data[slide_key]['category_id'] + @http.route(['/slides/prepare_preview'], type='json', auth='user', methods=['POST'], website=True) def prepare_preview(self, **data): Slide = request.env['slide.slide'] @@ -741,21 +759,6 @@ class WebsiteSlides(WebsiteProfile): 'can_create': can_create, } - #Not using the /web/dataset/resequence route as a slide can be dragged into another category at the same time - @http.route('/slides/resequence_slides', type="json", website=True, auth="user") - def resequence_slides(self, slides_data=None, **kw): - channel_user = None - for slide in slides_data: - s = request.env['slide.slide'].browse(slide['id']) - if not channel_user: - channel_user = s.channel_id.user_id - if request.env.user != channel_user: - return {'error': 'Only the responsible of the channel can edit it'} - s.write({ - 'sequence': slide['sequence'], - 'category_id': slide['category_id'] - }) - # -------------------------------------------------- # EMBED IN THIRD PARTY WEBSITES # -------------------------------------------------- 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 index d5fb61bbc5c..17f0d0893aa 100644 --- a/addons/website_slides/static/src/js/slides_course_slides_list.js +++ b/addons/website_slides/static/src/js/slides_course_slides_list.js @@ -1,251 +1,127 @@ odoo.define('website_slides.slideslist', function (require) { - 'use strict'; - var sAnimations = require('website.content.snippets.animation'); - var Widget = require('web.Widget'); +'use strict'; - var SlideUpload = require('website_slides.upload_modal'); +var sAnimations = require('website.content.snippets.animation'); - 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.o_wslides_content_slide').length; - //Change links HREF to fullscreen mode for SEO - var links = $('.o_wslides_course_slide_link'); - 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.o_wsldies_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 = sAnimations.Class.extend({ + selector: '.o_wslides_slides_list', + xmlDependencies: ['/website_slides/static/src/xml/website_slides_upload.xml'], + + start: function () { + this._super.apply(this,arguments); + + this.channelId = this.$el.data('channelId'); + + this._updateHref(); + this._bindSortable(); + }, + + //-------------------------------------------------------------------------- + // Private + //--------------------------------------------------------------------------, + + /** + * Bind the sortable jQuery widget to both + * - course sections + * - course slides + * + * @private + */ + _bindSortable: function () { + this.$('ul.o_wslides_slides_list_container').sortable({ + handle: '.fa-arrows', + stop: this._reorderCategories.bind(this) + }); + + this.$('.o_wslides_slides_list_container ul').sortable({ + handle: '.fa-arrows', + connectWith: '.o_wslides_slides_list_container ul', + stop: this._reorderSlides.bind(this), + items: '.o_wslides_slides_list_slide' + }); + }, + + _getCategories: function (){ + var categories = []; + this.$('.o_wslides_slide_list_category_container').each(function (){ + categories.push(parseInt($(this).attr('category_id'))); + }); + + return categories; + }, + + /** + * Returns a slides dict in the form: + * {slide_id: {'sequence': slide_sequence, 'category_id': slide.category_id.id}} + * + * @private + */ + _getSlides: function (){ + var slides = {}; + this.$('li.o_wslides_slides_list_slide').each(function (index){ + var $slide = $(this); + slides[$slide.attr('slide_id')] = { + category_id: parseInt( + $slide.closest('.o_wslides_slide_list_category').attr('category_id') + ), + sequence: index + }; + }); + + return slides; + }, + + _reorderCategories: function (){ + var self = this; + self._rpc({ + route: '/web/dataset/resequence', + params: { + model: "slide.category", + ids: self._getCategories() + } + }).then(function (){ + self._resetCategoriesIndex(); + }); + }, + + _reorderSlides: function (){ + this._rpc({ + route: "/slides/channel/resequence", + params: { + channel_id: this.channelId, + slides_data: this._getSlides() + } + }); + }, + + /** + * Used to reset the categories numbering (1, 2, 3, ...) in the UI + */ + _resetCategoriesIndex: function (){ + this.$('.section-index').each(function (index){ + $(this).text(index + 1); + }); + }, + + /** + * Change links href to fullscreen mode for SEO. + * + * Specifications demand that links are generated (xml) without the "fullscreen" + * parameter for SEO purposes. + * + * This method then adds the parameter as soon as the page is loaded. + * + * @private + */ + _updateHref: function () { + this.$(".o_wslides_slides_list_slide_link").each(function (){ + var href = $(this).attr('href'); + var operator = href.indexOf('?') !== -1 ? '&' : '?'; + $(this).attr('href', href + operator + "fullscreen=1"); + }); + } +}); + +return sAnimations.registry.websiteSlidesCourseSlidesList; - 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_slide_archive.js b/addons/website_slides/static/src/js/slides_slide_archive.js index 5be5a1b384e..a5d8747fdbc 100644 --- a/addons/website_slides/static/src/js/slides_slide_archive.js +++ b/addons/website_slides/static/src/js/slides_slide_archive.js @@ -47,7 +47,7 @@ var SlideArchiveDialog = Dialog.extend({ slide_id: this.slideId }, }).then(function () { - self.$slideTarget.closest('.o_wslides_content_slide').remove(); + self.$slideTarget.closest('.o_wslides_slides_list_slide').remove(); self.close(); }); } diff --git a/addons/website_slides/static/src/js/slides_upload.js b/addons/website_slides/static/src/js/slides_upload.js index d5abc73017b..9dca5c09774 100644 --- a/addons/website_slides/static/src/js/slides_upload.js +++ b/addons/website_slides/static/src/js/slides_upload.js @@ -311,25 +311,6 @@ var SlideUploadDialog = Dialog.extend({ return values; }, - _reorderSlidesSequence: function (){ - var self = this; - var slidesElement = $('li.o_wslides_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 * @@ -539,12 +520,6 @@ 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+']'); - $('').appendTo(categoryElement); - self._reorderSlidesSequence(); - } window.location = data.url; } }); diff --git a/addons/website_slides/static/src/scss/website_slides_drag_and_drop_list.scss b/addons/website_slides/static/src/scss/website_slides_drag_and_drop_list.scss index e0bf31c284f..bbe24c89664 100644 --- a/addons/website_slides/static/src/scss/website_slides_drag_and_drop_list.scss +++ b/addons/website_slides/static/src/scss/website_slides_drag_and_drop_list.scss @@ -1,49 +1,32 @@ -.o_wslides_course_content { +.o_wslides_slides_list { padding: 0; ul { list-style: none; - padding: 0 + padding: 0; + } + + .o_wslides_slide_list_category_container { + margin: 0; + background-color: #ddd; + display: flex; + list-style: none; + font-size: 1.05rem; + border-bottom: 1px solid #ccc; + } + + .o_wslides_slides_list_slide:nth-child(odd) { + background-color: #f6f6f6; + } + + .o_wslides_slides_list_slide:nth-child(even) { + background-color: #f9f9f9; + } + + .o_wslides_slides_list_slide_controls { + i:hover { + color: $primary !important; + cursor: pointer; + } } } - -.o_wslides_course_section { - margin: 0; - background-color: #ddd; - display: flex; - list-style: none; - font-size: 1.05rem; - border-bottom: 1px solid #ccc -} - -.o_wslides_content_tab{ - .active { - - border: none; - border-bottom: 1px solid $secondary; - } -} - -.o_wslides_content_tab a:hover{ - border:none; - border-bottom: 1px solid $secondary; -} - -.o_wslides_content_container{ - border:none; -} - -.o_wslides_content_slide:nth-child(odd){ - background-color: #f6f6f6; -} - -.o_wslides_content_slide:nth-child(even){ - background-color: #f9f9f9; -} - -.o_wslides_content_slide_controls { - i:hover{ - color: $primary !important; - cursor: pointer; - } -} \ No newline at end of file diff --git a/addons/website_slides/views/website_slides_templates_course.xml b/addons/website_slides/views/website_slides_templates_course.xml index 4f799876e75..352aaf1633c 100644 --- a/addons/website_slides/views/website_slides_templates_course.xml +++ b/addons/website_slides/views/website_slides_templates_course.xml @@ -245,15 +245,15 @@