From 3751379f1e9a4c215fb6eb898b4ccc67659b9ace Mon Sep 17 00:00:00 2001 From: stephanchrst Date: Tue, 10 May 2022 21:51:50 +0700 Subject: initial commit 2 --- addons/website_slides/static/src/js/activity.js | 87 +++ .../static/src/js/rating_field_backend.js | 42 ++ .../static/src/js/slide_category_one2many.js | 182 +++++ addons/website_slides/static/src/js/slides.js | 124 ++++ .../static/src/js/slides_category_add.js | 84 +++ .../static/src/js/slides_course_enroll_email.js | 83 +++ .../src/js/slides_course_fullscreen_player.js | 762 ++++++++++++++++++++ .../static/src/js/slides_course_join.js | 161 +++++ .../static/src/js/slides_course_quiz.js | 775 +++++++++++++++++++++ .../static/src/js/slides_course_quiz_finish.js | 157 +++++ .../src/js/slides_course_quiz_question_form.js | 228 ++++++ .../static/src/js/slides_course_slides_list.js | 114 +++ .../static/src/js/slides_course_tag_add.js | 377 ++++++++++ .../static/src/js/slides_course_unsubscribe.js | 168 +++++ .../website_slides/static/src/js/slides_embed.js | 250 +++++++ .../website_slides/static/src/js/slides_share.js | 105 +++ .../static/src/js/slides_slide_archive.js | 108 +++ .../static/src/js/slides_slide_like.js | 114 +++ .../src/js/slides_slide_toggle_is_preview.js | 40 ++ .../website_slides/static/src/js/slides_upload.js | 678 ++++++++++++++++++ .../static/src/js/tours/slides_tour.js | 117 ++++ .../static/src/js/website_slides.editor.js | 188 +++++ 22 files changed, 4944 insertions(+) create mode 100644 addons/website_slides/static/src/js/activity.js create mode 100644 addons/website_slides/static/src/js/rating_field_backend.js create mode 100644 addons/website_slides/static/src/js/slide_category_one2many.js create mode 100644 addons/website_slides/static/src/js/slides.js create mode 100644 addons/website_slides/static/src/js/slides_category_add.js create mode 100644 addons/website_slides/static/src/js/slides_course_enroll_email.js create mode 100644 addons/website_slides/static/src/js/slides_course_fullscreen_player.js create mode 100644 addons/website_slides/static/src/js/slides_course_join.js create mode 100644 addons/website_slides/static/src/js/slides_course_quiz.js create mode 100644 addons/website_slides/static/src/js/slides_course_quiz_finish.js create mode 100644 addons/website_slides/static/src/js/slides_course_quiz_question_form.js create mode 100644 addons/website_slides/static/src/js/slides_course_slides_list.js create mode 100644 addons/website_slides/static/src/js/slides_course_tag_add.js create mode 100644 addons/website_slides/static/src/js/slides_course_unsubscribe.js create mode 100644 addons/website_slides/static/src/js/slides_embed.js create mode 100644 addons/website_slides/static/src/js/slides_share.js create mode 100644 addons/website_slides/static/src/js/slides_slide_archive.js create mode 100644 addons/website_slides/static/src/js/slides_slide_like.js create mode 100644 addons/website_slides/static/src/js/slides_slide_toggle_is_preview.js create mode 100644 addons/website_slides/static/src/js/slides_upload.js create mode 100644 addons/website_slides/static/src/js/tours/slides_tour.js create mode 100644 addons/website_slides/static/src/js/website_slides.editor.js (limited to 'addons/website_slides/static/src/js') diff --git a/addons/website_slides/static/src/js/activity.js b/addons/website_slides/static/src/js/activity.js new file mode 100644 index 00000000..ac0ae3cd --- /dev/null +++ b/addons/website_slides/static/src/js/activity.js @@ -0,0 +1,87 @@ +odoo.define('website_slides.Activity', function (require) { +"use strict"; + +var field_registry = require('web.field_registry'); + +require('mail.Activity'); + +var KanbanActivity = field_registry.get('kanban_activity'); + +function applyInclude(Activity) { + Activity.include({ + events: _.extend({}, Activity.prototype.events, { + 'click .o_activity_action_grant_access': '_onGrantAccess', + 'click .o_activity_action_refuse_access': '_onRefuseAccess', + }), + + _onGrantAccess: function (event) { + var self = this; + var partnerId = $(event.currentTarget).data('partner-id'); + this._rpc({ + model: 'slide.channel', + method: 'action_grant_access', + args: [this.res_id, partnerId], + }).then(function (result) { + self.trigger_up('reload'); + }); + }, + + _onRefuseAccess: function (event) { + var self = this; + var partnerId = $(event.currentTarget).data('partner-id'); + this._rpc({ + model: 'slide.channel', + method: 'action_refuse_access', + args: [this.res_id, partnerId], + }).then(function () { + self.trigger_up('reload'); + }); + }, + }); +} + +applyInclude(KanbanActivity); + +}); + +odoo.define('website_slides/static/src/components/activity/activity.js', function (require) { +'use strict'; + +const components = { + Activity: require('mail/static/src/components/activity/activity.js'), +}; +const { patch } = require('web.utils'); + +patch(components.Activity, 'website_slides/static/src/components/activity/activity.js', { + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * @private + */ + async _onGrantAccess(ev) { + await this.env.services.rpc({ + model: 'slide.channel', + method: 'action_grant_access', + args: [[this.activity.thread.id]], + kwargs: { partner_id: this.activity.requestingPartner.id }, + }); + this.trigger('reload'); + }, + /** + * @private + */ + async _onRefuseAccess(ev) { + await this.env.services.rpc({ + model: 'slide.channel', + method: 'action_refuse_access', + args: [[this.activity.thread.id]], + kwargs: { partner_id: this.activity.requestingPartner.id }, + }); + this.trigger('reload'); + }, +}); + +}); diff --git a/addons/website_slides/static/src/js/rating_field_backend.js b/addons/website_slides/static/src/js/rating_field_backend.js new file mode 100644 index 00000000..12a6d8b6 --- /dev/null +++ b/addons/website_slides/static/src/js/rating_field_backend.js @@ -0,0 +1,42 @@ +odoo.define('website_slides.ratingField', function (require) { +"use strict"; + +var basicFields = require('web.basic_fields'); +var fieldRegistry = require('web.field_registry'); + +var core = require('web.core'); + +var QWeb = core.qweb; + +var FieldFloatRating = basicFields.FieldFloat.extend({ + xmlDependencies: !basicFields.FieldFloat.prototype.xmlDependencies ? + ['/portal_rating/static/src/xml/portal_tools.xml'] : basicFields.FieldFloat.prototype.xmlDependencies.concat( + ['/portal_rating/static/src/xml/portal_tools.xml'] + ), + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * @override + * @private + */ + _render: function () { + var self = this; + + return Promise.resolve(this._super()).then(function () { + self.$el.html(QWeb.render('portal_rating.rating_stars_static', { + 'val': self.value / 2, + 'inline_mode': true + })); + }); + }, +}); + +fieldRegistry.add('field_float_rating', FieldFloatRating); + +return { + FieldFloatRating: FieldFloatRating, +}; + +}); diff --git a/addons/website_slides/static/src/js/slide_category_one2many.js b/addons/website_slides/static/src/js/slide_category_one2many.js new file mode 100644 index 00000000..adb84d60 --- /dev/null +++ b/addons/website_slides/static/src/js/slide_category_one2many.js @@ -0,0 +1,182 @@ +odoo.define('survey.slide_category_one2many', function (require){ +"use strict"; + +var Context = require('web.Context'); +var FieldOne2Many = require('web.relational_fields').FieldOne2Many; +var FieldRegistry = require('web.field_registry'); +var ListRenderer = require('web.ListRenderer'); +var config = require('web.config'); + +var SectionListRenderer = ListRenderer.extend({ + init: function (parent, state, params) { + this.sectionFieldName = "is_category"; + this._super.apply(this, arguments); + }, + _checkIfRecordIsSection: function (id){ + var record = this._findRecordById(id); + return record && record.data[this.sectionFieldName]; + }, + _findRecordById: function (id){ + return _.find(this.state.data, function (record){ + return record.id === id; + }); + }, + /** + * Allows to hide specific field in case the record is a section + * and, in this case, makes the 'title' field take the space of all the other + * fields + * @private + * @override + * @param {*} record + * @param {*} node + * @param {*} index + * @param {*} options + */ + _renderBodyCell: function (record, node, index, options){ + var $cell = this._super.apply(this, arguments); + + var isSection = record.data[this.sectionFieldName]; + + if (isSection){ + if (node.attrs.widget === "handle"){ + return $cell; + } else if (node.attrs.name === "name"){ + var nbrColumns = this._getNumberOfCols(); + if (this.handleField){ + nbrColumns--; + } + if (this.addTrashIcon){ + nbrColumns--; + } + $cell.attr('colspan', nbrColumns); + } else { + $cell.removeClass('o_invisible_modifier'); + return $cell.addClass('o_hidden'); + } + } + return $cell; + }, + /** + * Adds specific classes to rows that are sections + * to apply custom css on them + * @private + * @override + * @param {*} record + * @param {*} index + */ + _renderRow: function (record, index){ + var $row = this._super.apply(this, arguments); + if (record.data[this.sectionFieldName]) { + $row.addClass("o_is_section"); + } + return $row; + }, + /** + * Adding this class after the view is rendered allows + * us to limit the custom css scope to this particular case + * and no other + * @private + * @override + */ + _renderView: function (){ + var def = this._super.apply(this, arguments); + var self = this; + return def.then(function () { + self.$('table.o_list_table').addClass('o_section_list_view'); + }); + }, + // Handlers + /** + * Overriden to allow different behaviours depending on + * the row the user clicked on. + * If the row is a section: edit inline + * else use a normal modal + * @private + * @override + * @param {*} ev + */ + _onRowClicked: function (ev){ + var parent = this.getParent(); + var recordId = $(ev.currentTarget).data('id'); + var is_section = this._checkIfRecordIsSection(recordId); + if (is_section && parent.mode === "edit"){ + this.editable = "bottom"; + } else { + this.editable = null; + } + this._super.apply(this, arguments); + }, + /** + * Overriden to allow different behaviours depending on + * the cell the user clicked on. + * If the cell is part of a section: edit inline + * else use a normal edit modal + * @private + * @override + * @param {*} ev + */ + _onCellClick: function (ev){ + var parent = this.getParent(); + var recordId = $(ev.currentTarget.parentElement).data('id'); + var is_section = this._checkIfRecordIsSection(recordId); + if (is_section && parent.mode === "edit"){ + this.editable = "bottom"; + } else { + this.editable = null; + this.unselectRow(); + } + this._super.apply(this, arguments); + }, + /** + * In this case, navigating in the list caused issues. + * For example, editing a section then pressing enter would trigger + * the inline edition of the next element in the list. Which is not desired + * if the next element ends up being a question and not a section + * @override + * @param {*} ev + */ + _onNavigationMove: function (ev){ + this.unselectRow(); + }, +}); + +var SectionFieldOne2Many = FieldOne2Many.extend({ + init: function (parent, name, record, options){ + this._super.apply(this, arguments); + this.sectionFieldName = "is_category"; + this.rendered = false; + }, + /** + * Overriden to use our custom renderer + * @private + * @override + */ + _getRenderer: function (){ + if (this.view.arch.tag === 'tree'){ + return SectionListRenderer; + } + return this._super.apply(this, arguments); + }, + /** + * Overriden to allow different behaviours depending on + * the object we want to add. Adding a section would be done inline + * while adding a question would render a modal. + * @private + * @override + * @param {*} ev + */ + _onAddRecord: function (ev) { + this.editable = null; + if (!config.device.isMobile){ + var context_str = ev.data.context && ev.data.context[0]; + var context = new Context(context_str).eval(); + if (context['default_' + this.sectionFieldName]){ + this.editable = "bottom"; + } + } + this._super.apply(this, arguments); + }, +}); + +FieldRegistry.add('slide_category_one2many', SectionFieldOne2Many); +}); \ No newline at end of file diff --git a/addons/website_slides/static/src/js/slides.js b/addons/website_slides/static/src/js/slides.js new file mode 100644 index 00000000..4f081013 --- /dev/null +++ b/addons/website_slides/static/src/js/slides.js @@ -0,0 +1,124 @@ +odoo.define('website_slides.slides', function (require) { +'use strict'; + +var publicWidget = require('web.public.widget'); +var time = require('web.time'); + +publicWidget.registry.websiteSlides = publicWidget.Widget.extend({ + selector: '#wrapwrap', + + /** + * @override + * @param {Object} parent + */ + start: function (parent) { + var defs = [this._super.apply(this, arguments)]; + + _.each($("timeago.timeago"), function (el) { + var datetime = $(el).attr('datetime'); + var datetimeObj = time.str_to_datetime(datetime); + // if presentation 7 days, 24 hours, 60 min, 60 second, 1000 millis old(one week) + // then return fix formate string else timeago + var displayStr = ''; + if (datetimeObj && new Date().getTime() - datetimeObj.getTime() > 7 * 24 * 60 * 60 * 1000) { + displayStr = moment(datetimeObj).format('ll'); + } else { + displayStr = moment(datetimeObj).fromNow(); + } + $(el).text(displayStr); + }); + + return Promise.all(defs); + }, +}); + +return publicWidget.registry.websiteSlides; + +}); + +//============================================================================== + +odoo.define('website_slides.slides_embed', function (require) { +'use strict'; + +var publicWidget = require('web.public.widget'); +require('website_slides.slides'); + +var SlideSocialEmbed = publicWidget.Widget.extend({ + events: { + 'change input': '_onChangePage', + }, + /** + * @constructor + * @param {Object} parent + * @param {Number} maxPage + */ + init: function (parent, maxPage) { + this._super.apply(this, arguments); + this.max_page = maxPage || false; + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * @private + * @param {Number} page + */ + _updateEmbeddedCode: function (page) { + var $embedInput = this.$('.slide_embed_code'); + var newCode = $embedInput.val().replace(/(page=).*?([^\d]+)/, '$1' + page + '$2'); + $embedInput.val(newCode); + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * @private + * @param {Object} ev + */ + _onChangePage: function (ev) { + ev.preventDefault(); + var input = this.$('input'); + var page = parseInt(input.val()); + if (this.max_page && !(page > 0 && page <= this.max_page)) { + page = 1; + } + this._updateEmbeddedCode(page); + }, +}); + +publicWidget.registry.websiteSlidesEmbed = publicWidget.Widget.extend({ + selector: '#wrapwrap', + + /** + * @override + * @param {Object} parent + */ + start: function (parent) { + var defs = [this._super.apply(this, arguments)]; + $('iframe.o_wslides_iframe_viewer').on('ready', this._onIframeViewerReady.bind(this)); + return Promise.all(defs); + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * @private + * @param {Event} ev + */ + _onIframeViewerReady: function (ev) { + // TODO : make it work. For now, once the iframe is loaded, the value of #page_count is + // still now set (the pdf is still loading) + var $iframe = $(ev.currentTarget); + var maxPage = $iframe.contents().find('#page_count').val(); + new SlideSocialEmbed(this, maxPage).attachTo($('.oe_slide_js_embed_code_widget')); + }, +}); + +}); diff --git a/addons/website_slides/static/src/js/slides_category_add.js b/addons/website_slides/static/src/js/slides_category_add.js new file mode 100644 index 00000000..e11ba2a4 --- /dev/null +++ b/addons/website_slides/static/src/js/slides_category_add.js @@ -0,0 +1,84 @@ +odoo.define('website_slides.category.add', function (require) { +'use strict'; + +var publicWidget = require('web.public.widget'); +var Dialog = require('web.Dialog'); +var core = require('web.core'); +var _t = core._t; + +var CategoryAddDialog = Dialog.extend({ + template: 'slides.category.add', + + /** + * @override + */ + init: function (parent, options) { + options = _.defaults(options || {}, { + title: _t('Add a section'), + size: 'medium', + buttons: [{ + text: _t('Save'), + classes: 'btn-primary', + click: this._onClickFormSubmit.bind(this) + }, { + text: _t('Discard'), + close: true + }] + }); + + this.channelId = options.channelId; + this._super(parent, options); + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + _formValidate: function ($form) { + $form.addClass('was-validated'); + return $form[0].checkValidity(); + }, + + _onClickFormSubmit: function (ev) { + var $form = this.$('#slide_category_add_form'); + if (this._formValidate($form)) { + $form.submit(); + } + }, +}); + +publicWidget.registry.websiteSlidesCategoryAdd = publicWidget.Widget.extend({ + selector: '.o_wslides_js_slide_section_add', + xmlDependencies: ['/website_slides/static/src/xml/slide_management.xml'], + events: { + 'click': '_onAddSectionClick', + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + _openDialog: function (channelId) { + new CategoryAddDialog(this, {channelId: channelId}).open(); + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * @private + * @param {Event} ev + */ + _onAddSectionClick: function (ev) { + ev.preventDefault(); + this._openDialog($(ev.currentTarget).attr('channel_id')); + }, +}); + +return { + categoryAddDialog: CategoryAddDialog, + websiteSlidesCategoryAdd: publicWidget.registry.websiteSlidesCategoryAdd +}; + +}); diff --git a/addons/website_slides/static/src/js/slides_course_enroll_email.js b/addons/website_slides/static/src/js/slides_course_enroll_email.js new file mode 100644 index 00000000..a9f5f799 --- /dev/null +++ b/addons/website_slides/static/src/js/slides_course_enroll_email.js @@ -0,0 +1,83 @@ +odoo.define('website_slides.course.enroll', function (require) { +'use strict'; + +var core = require('web.core'); +var Dialog = require('web.Dialog'); +var publicWidget = require('web.public.widget'); +var _t = core._t; + +var SlideEnrollDialog = Dialog.extend({ + template: 'slide.course.join.request', + + init: function (parent, options, modalOptions) { + modalOptions = _.defaults(modalOptions || {}, { + title: _t('Request Access.'), + size: 'medium', + buttons: [{ + text: _t('Yes'), + classes: 'btn-primary', + click: this._onSendRequest.bind(this) + }, { + text: _t('Cancel'), + close: true + }] + }); + this.$element = options.$element; + this.channelId = options.channelId; + this._super(parent, modalOptions); + }, + + _onSendRequest: function () { + var self = this; + this._rpc({ + model: 'slide.channel', + method: 'action_request_access', + args: [self.channelId] + }).then(function (result) { + if (result.error) { + self.$element.replaceWith(''); + } else if (result.done) { + self.$element.replaceWith(''); + } else { + self.$element.replaceWith(''); + } + self.close(); + }); + } + +}); + +publicWidget.registry.websiteSlidesEnroll = publicWidget.Widget.extend({ + selector: '.o_wslides_js_channel_enroll', + xmlDependencies: ['/website_slides/static/src/xml/slide_course_join.xml'], + events: { + 'click': '_onSendRequestClick', + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + _openDialog: function (channelId) { + new SlideEnrollDialog(this, { + channelId: channelId, + $element: this.$el + }).open(); + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + _onSendRequestClick: function (ev) { + ev.preventDefault(); + this._openDialog($(ev.currentTarget).data('channelId')); + } +}); + +return { + slideEnrollDialog: SlideEnrollDialog, + websiteSlidesEnroll: publicWidget.registry.websiteSlidesEnroll +}; + +}); 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 00000000..d9aa707c --- /dev/null +++ b/addons/website_slides/static/src/js/slides_course_fullscreen_player.js @@ -0,0 +1,762 @@ +var onYouTubeIframeAPIReady = undefined; + +odoo.define('website_slides.fullscreen', function (require) { + 'use strict'; + + var publicWidget = require('web.public.widget'); + var core = require('web.core'); + var config = require('web.config'); + var QWeb = core.qweb; + var _t = core._t; + + var session = require('web.session'); + + var Quiz = require('website_slides.quiz').Quiz; + + var Dialog = require('web.Dialog'); + + require('website_slides.course.join.widget'); + + /** + * Helper: Get the slide dict matching the given criteria + * + * @private + * @param {Array} 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 = publicWidget.Widget.extend({ + template: 'website.slides.fullscreen.video', + youtubeUrl: 'https://www.youtube.com/iframe_api', + + init: function (parent, slide) { + this.slide = slide; + return this._super.apply(this, arguments); + }, + start: function (){ + var self = this; + return Promise.all([this._super.apply(this, arguments), this._loadYoutubeAPI()]).then(function() { + self._setupYoutubePlayer(); + }); + }, + _loadYoutubeAPI: function () { + var self = this; + var prom = new Promise(function (resolve, reject) { + if ($(document).find('script[src="' + self.youtubeUrl + '"]').length === 0) { + var $youtubeElement = $('