summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--addons/website/views/website_navbar_templates.xml4
-rw-r--r--addons/website_slides/controllers/main.py168
-rw-r--r--addons/website_slides/data/website_data.xml2
-rw-r--r--addons/website_slides/models/__init__.py1
-rw-r--r--addons/website_slides/models/slide_channel.py9
-rw-r--r--addons/website_slides/models/slide_question.py51
-rw-r--r--addons/website_slides/models/slide_slide.py22
-rw-r--r--addons/website_slides/security/ir.model.access.csv4
-rw-r--r--addons/website_slides/static/src/js/slides_category.js148
-rw-r--r--addons/website_slides/static/src/js/slides_course_fullscreen_player.js427
-rw-r--r--addons/website_slides/static/src/js/slides_course_progress_bar.js47
-rw-r--r--addons/website_slides/static/src/js/slides_course_quiz.js206
-rw-r--r--addons/website_slides/static/src/js/slides_course_sidebar_list.js34
-rw-r--r--addons/website_slides/static/src/js/slides_course_slides_list.js251
-rw-r--r--addons/website_slides/static/src/js/slides_delete_slide.js88
-rw-r--r--addons/website_slides/static/src/js/slides_upload.js51
-rw-r--r--addons/website_slides/static/src/js/website_slides.editor.js1
-rw-r--r--addons/website_slides/static/src/scss/slide_course.scss536
-rw-r--r--addons/website_slides/static/src/scss/slide_slide.scss156
-rw-r--r--addons/website_slides/static/src/scss/slides_slide_fullscreen.scss303
-rw-r--r--addons/website_slides/static/src/scss/website_slides_quiz.scss237
-rw-r--r--addons/website_slides/static/src/xml/website_slides_fullscreen.xml111
-rw-r--r--addons/website_slides/static/src/xml/website_slides_upload.xml105
-rw-r--r--addons/website_slides/views/assets.xml11
-rw-r--r--addons/website_slides/views/slide_channel_views.xml6
-rw-r--r--addons/website_slides/views/slide_slide_views.xml46
-rw-r--r--addons/website_slides/views/website_slides_templates.xml350
-rw-r--r--addons/website_slides_forum/controllers/main.py7
-rw-r--r--addons/website_slides_forum/views/website_slides_templates.xml25
-rw-r--r--addons/website_slides_survey/static/src/js/slides_course_fullscreen_player.js15
-rw-r--r--addons/website_slides_survey/static/src/scss/website_slides.scss12
-rw-r--r--addons/website_slides_survey/static/src/xml/website_slides_fullscreen.xml16
-rw-r--r--addons/website_slides_survey/views/assets.xml4
33 files changed, 3399 insertions, 55 deletions
diff --git a/addons/website/views/website_navbar_templates.xml b/addons/website/views/website_navbar_templates.xml
index 1e2c6fa7dfb..08ef7b54ab4 100644
--- a/addons/website/views/website_navbar_templates.xml
+++ b/addons/website/views/website_navbar_templates.xml
@@ -148,8 +148,8 @@
</div>
<div groups="base.group_system" name="module_website_slides" t-att-data-module-id="env.ref('base.module_website_slides').id" t-att-data-module-shortdesc="env.ref('base.module_website_slides').shortdesc" class="col-md-4 mb8 o_new_content_element">
<a href="#" data-action="new_slide_channel">
- <i class="fa fa-youtube-play"/>
- <p>New Slide Channel</p>
+ <i class="fa fa-graduation-cap"></i>
+ <p>New Course</p>
</a>
</div>
<div groups="base.group_system" name="module_website_livechat" t-att-data-module-id="env.ref('base.module_website_livechat').id" t-att-data-module-shortdesc="env.ref('base.module_website_livechat').shortdesc" class="col-md-4 mb8 o_new_content_element">
diff --git a/addons/website_slides/controllers/main.py b/addons/website_slides/controllers/main.py
index e8ebae00b7b..e5751893282 100644
--- a/addons/website_slides/controllers/main.py
+++ b/addons/website_slides/controllers/main.py
@@ -50,13 +50,34 @@ class WebsiteSlides(WebsiteProfile):
def _get_slide_detail(self, slide):
most_viewed_slides = slide.get_most_viewed_slides(self._slides_per_list)
related_slides = slide.get_related_slides(self._slides_per_list)
- return {
+ values = {
'slide': slide,
'most_viewed_slides': most_viewed_slides,
'related_slides': related_slides,
'user': request.env.user,
'is_public_user': request.website.is_public_user(),
'comments': slide.website_message_ids or [],
+ 'user_progress': {}
+ }
+ if slide.channel_id.channel_type == "training":
+ channel_slides = slide.channel_id.slide_ids.ids
+ slide_index = channel_slides.index(slide.id)
+ previous_slide = None
+ next_slide = None
+ if slide_index > 0:
+ previous_slide = slide.channel_id.slide_ids[slide_index-1]
+ if slide_index < len(channel_slides) - 1:
+ next_slide = slide.channel_id.slide_ids[slide_index+1]
+ values.update({
+ 'previous_slide': slug(previous_slide) if previous_slide else "",
+ 'next_slide': slug(next_slide) if next_slide else ""
+ })
+ return values
+
+ def _get_user_progress(self, channel):
+ user_progress = { slide_partner.slide_id.id: slide_partner for slide_partner in request.env['slide.slide.partner'].sudo().search([('channel_id', '=', channel.id),('partner_id', '=', request.env.user.partner_id.id)])}
+ return {
+ 'user_progress': user_progress
}
def _extract_channel_tag_search(self, **post):
@@ -198,7 +219,6 @@ class WebsiteSlides(WebsiteProfile):
def channel(self, channel, category=None, tag=None, page=1, slide_type=None, sorting=None, search=None, **kw):
if not channel.can_access_from_current_website():
raise werkzeug.exceptions.NotFound()
-
domain = [('channel_id', '=', channel.id)]
if not request.env.user.has_group('website.group_website_publisher'):
domain = expression.AND([
@@ -299,6 +319,9 @@ class WebsiteSlides(WebsiteProfile):
values = self._prepare_additional_channel_values(values, **kw)
+ if channel.channel_type == "training":
+ values.update(self._get_user_progress(channel))
+
return request.render('website_slides.course_main', values)
@http.route(['/slides/channel/add'], type='http', auth='user', methods=['POST'], website=True)
@@ -339,7 +362,13 @@ class WebsiteSlides(WebsiteProfile):
'message_post_hash': slide._generate_signed_token(request.env.user.partner_id.id),
'message_post_pid': request.env.user.partner_id.id,
})
- return request.render('website_slides.slide_detail_view', values)
+ self._set_viewed_slide(slide)
+ if slide.channel_id.channel_type == "training":
+ values.update(self._get_user_progress(slide.channel_id))
+ if 'fullscreen' in kwargs:
+ return request.render("website_slides.slide_fullscreen", values)
+ return request.render("website_slides.slide_detail_view", values)
+
@http.route('''/slides/slide/<model("slide.slide"):slide>/pdf_content''',
type='http', auth="public", website=True, sitemap=False)
@@ -394,6 +423,94 @@ class WebsiteSlides(WebsiteProfile):
response.status_code = status
return response
+ @http.route('/slide/html_content/get', type="json", auth="public", website=True)
+ def get_html_content(self, slide_id):
+ slide = request.env['slide.slide'].browse(slide_id)
+ return {
+ 'html_content': slide.html_content
+ }
+
+ #SLIDE QUIZ CONTROLLERS
+
+ @http.route('/slide/quiz/get', type="json", auth="public", website=True)
+ def get_quiz(self, **kw):
+ if 'slide_id' in kw:
+ slide = request.env['slide.slide'].browse(kw['slide_id'])
+ slide_partner = request.env['slide.slide.partner'].search([('slide_id', '=', slide.id), ('partner_id', '=', request.env.user.partner_id.id)])
+ possible_points = [slide.quiz_first_attempt_reward,slide.quiz_second_attempt_reward,slide.quiz_third_attempt_reward, slide.quiz_fourth_attempt_reward]
+ points = 0
+ if slide_partner.quiz_attempts_count < len(possible_points):
+ points = possible_points[slide_partner.quiz_attempts_count]
+ else:
+ points = possible_points[len(possible_points)-1]
+ res = {
+ 'questions':[
+ {'title': question.question,
+ 'id': question.id,
+ 'answers': [{'text': answer.text_value, 'correct':answer.is_correct,'id': answer.id} for answer in question.answer_ids]
+ } for question in slide.question_ids
+ ],
+ 'nb_attempts': slide_partner.quiz_attempts_count if slide_partner else 0,
+ 'possible_rewards': possible_points,
+ 'reward': points
+ }
+ return res
+
+ @http.route('/slide/quiz/submit', type="json", auth="user", website=True)
+ def submit_quiz(self, slide_id, answer_ids,**kw):
+ slide = request.env['slide.slide'].browse(slide_id)
+ good_answers = request.env['slide.answer'].search([('id', 'in', answer_ids), ('is_correct', '=', True)])
+ bad_answers = request.env['slide.answer'].browse(answer_ids) - good_answers
+ slide_partner = request.env['slide.slide.partner'].search([('slide_id', '=', slide_id), ('partner_id', '=', request.env.user.partner_id.id)])
+ possible_points = [slide.quiz_first_attempt_reward, slide.quiz_second_attempt_reward, slide.quiz_third_attempt_reward, slide.quiz_fourth_attempt_reward]
+ points = 0
+ if not slide_partner:
+ slide_partner = request.env['slide.slide.partner'].sudo().create({
+ "slide_id": slide.id,
+ "partner_id": request.env.user.partner_id.id,
+ "channel_id": slide.channel_id.id,
+ "completed": False
+ })
+ if not slide_partner.completed:
+ if slide_partner.quiz_attempts_count < len(possible_points):
+ points = possible_points[slide_partner.quiz_attempts_count]
+ else:
+ points = possible_points[-1]
+ slide_partner.sudo().write({
+ 'quiz_attempts_count': slide_partner.quiz_attempts_count if not bad_answers else slide_partner.quiz_attempts_count + 1,
+ 'points_won': points if not bad_answers else 0,
+ 'completed': not bad_answers
+ })
+ return {
+ 'goodAnswers': [good_answer.id for good_answer in good_answers],
+ 'badAnswers': [bad_answer.id for bad_answer in bad_answers],
+ 'passed': not bad_answers,
+ 'points': points if not bad_answers else 0,
+ 'attempts_count': slide_partner.quiz_attempts_count if slide_partner else 0,
+ 'channel_completion': slide.channel_id.completion
+ }
+ return {
+ 'error': "You already passed this quiz"
+ }
+
+ #SLIDE STATE CONTROLLERS
+
+ @http.route('/slide/completed/<int:slide_id>', website=True, type="http", auth="user")
+ def mark_as_completed(self, slide_id, next_slide=None, **kw):
+ slide = request.env['slide.slide'].browse(slide_id)
+ slide.action_set_completed()
+ return werkzeug.utils.redirect("/slides/slide/%s" %(next_slide))
+
+
+ @http.route('/slides/set_completed', website=True, type="json", auth="user")
+ def set_status_as_done(self, slide_id, **kw):
+ slide = request.env['slide.slide'].browse(slide_id)
+ slide.channel_id.invalidate_cache()
+ slide.action_set_completed()
+ return {
+ 'channel_completion': slide.channel_id.completion
+ }
+
# JSONRPC
@http.route('/slides/slide/like', type='json', auth="user", website=True)
def slide_like(self, slide_id, upvote):
@@ -423,6 +540,13 @@ class WebsiteSlides(WebsiteProfile):
# TOOLS
# --------------------------------------------------
+ @http.route(['/slides/channel/enroll'], type='http', auth='public', website=True)
+ def slide_channel_join_http(self, channel_id):
+ if not request.website.is_public_user():
+ channel = request.env['slide.channel'].browse(int(channel_id))
+ channel.action_add_member()
+ return werkzeug.utils.redirect("/slides/%s" % (slug(channel)))
+
@http.route(['/slides/channel/join'], type='json', auth='public', website=True)
def slide_channel_join(self, channel_id):
if request.website.is_public_user():
@@ -495,9 +619,19 @@ class WebsiteSlides(WebsiteProfile):
return {'error': _('Internal server error, please try again later or contact administrator.\nHere is the error message: %s') % e}
redirect_url = "/slides/slide/%s" % (slide.id)
+ if channel.channel_type == "training" and not slide.slide_type == "webpage":
+ redirect_url = "/slides/%s" % (slug(channel))
if slide.slide_type == 'webpage':
redirect_url += "?enable_editor=1"
- return {'url': redirect_url}
+ if slide.slide_type == "quiz":
+ action_id = request.env.ref('website_slides.action_slides_slides').id
+ redirect_url = '/web#id=%s&action=%s&model=slide.slide&view_type=form' %(slide.id,action_id)
+ return {
+ 'url': redirect_url,
+ 'channel_type': channel.channel_type,
+ 'slide_id': slide.id,
+ 'category_id': slide.category_id
+ }
def _get_valid_slide_post_values(self):
return ['name', 'url', 'tag_ids', 'slide_type', 'channel_id',
@@ -527,6 +661,32 @@ class WebsiteSlides(WebsiteProfile):
'can_create': can_create,
}
+ @http.route('/slides/add_category', type="json", website=True, auth="user")
+ def add_category(self, channel_id, name, **kw):
+ channel = request.env['slide.channel'].browse(channel_id)
+ request.env['slide.category'].create({
+ 'name': name,
+ 'channel_id': channel.id
+ })
+
+ return {'url': "/slides/%s" %(slug(channel))}
+
+ #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/data/website_data.xml b/addons/website_slides/data/website_data.xml
index 0d2f0582deb..e2e8b1c4a78 100644
--- a/addons/website_slides/data/website_data.xml
+++ b/addons/website_slides/data/website_data.xml
@@ -6,7 +6,7 @@
</record>
<record id="website_menu_slides" model="website.menu">
- <field name="name">Presentations</field>
+ <field name="name">Courses</field>
<field name="url">/slides</field>
<field name="parent_id" ref="website.main_menu"/>
<field name="sequence" type="int">50</field>
diff --git a/addons/website_slides/models/__init__.py b/addons/website_slides/models/__init__.py
index 4b8210c6caa..0fcfe6373ab 100644
--- a/addons/website_slides/models/__init__.py
+++ b/addons/website_slides/models/__init__.py
@@ -2,6 +2,7 @@
from . import gamification_challenge
from . import slide_slide
+from . import slide_question
from . import slide_channel
from . import slide_channel_tag
from . import slide_channel_invite
diff --git a/addons/website_slides/models/slide_channel.py b/addons/website_slides/models/slide_channel.py
index dd772a8ec7b..6bbd0e3b297 100644
--- a/addons/website_slides/models/slide_channel.py
+++ b/addons/website_slides/models/slide_channel.py
@@ -101,6 +101,7 @@ class Channel(models.Model):
nbr_videos = fields.Integer('Number of Videos', compute='_compute_slides_statistics', store=True)
nbr_infographics = fields.Integer('Number of Infographics', compute='_compute_slides_statistics', store=True)
nbr_webpages = fields.Integer("Number of Webpages", compute='_compute_slides_statistics', store=True)
+ nbr_quizs = fields.Integer("Number of quizzes", compute="_compute_slides_statistics", store=True)
total_slides = fields.Integer('# Slides', compute='_compute_slides_statistics', store=True, oldname='total')
total_views = fields.Integer('# Views', compute='_compute_slides_statistics', store=True)
total_votes = fields.Integer('# Votes', compute='_compute_slides_statistics', store=True)
@@ -174,7 +175,7 @@ class Channel(models.Model):
def _compute_slides_statistics(self):
result = dict((cid, dict(total_slides=0, total_views=0, total_votes=0, total_time=0)) for cid in self.ids)
read_group_res = self.env['slide.slide'].read_group(
- [('is_published', '=', True), ('channel_id', 'in', self.ids)],
+ [('is_published', '=', True),('channel_id', 'in', self.ids)],
['channel_id', 'slide_type', 'likes', 'dislikes', 'total_views', 'completion_time'],
groupby=['channel_id', 'slide_type'],
lazy=False)
@@ -195,7 +196,7 @@ class Channel(models.Model):
def _compute_slides_statistics_type(self, read_group_res):
""" Can be overridden to compute stats on added slide_types """
- result = dict((cid, dict(nbr_presentations=0, nbr_documents=0, nbr_videos=0, nbr_infographics=0, nbr_webpages=0)) for cid in self.ids)
+ result = dict((cid, dict(nbr_presentations=0, nbr_documents=0, nbr_videos=0, nbr_infographics=0, nbr_webpages=0, nbr_quizs=0)) for cid in self.ids)
for res_group in read_group_res:
cid = res_group['channel_id'][0]
result[cid]['nbr_presentations'] += res_group.get('slide_type', '') == 'presentation' and res_group['__count'] or 0
@@ -203,6 +204,7 @@ class Channel(models.Model):
result[cid]['nbr_videos'] += res_group.get('slide_type', '') == 'video' and res_group['__count'] or 0
result[cid]['nbr_infographics'] += res_group.get('slide_type', '') == 'infographic' and res_group['__count'] or 0
result[cid]['nbr_webpages'] += res_group.get('slide_type', '') == 'webpage' and res_group['__count'] or 0
+ result[cid]['nbr_quizs'] += res_group.get('slide_type', '') == 'quiz' and res_group['__count'] or 0
return result
@api.depends('slide_partner_ids')
@@ -409,6 +411,7 @@ class Category(models.Model):
nbr_videos = fields.Integer("Number of Videos", compute='_count_presentations', store=True)
nbr_infographics = fields.Integer("Number of Infographics", compute='_count_presentations', store=True)
nbr_webpages = fields.Integer("Number of Webpages", compute='_count_presentations', store=True)
+ nbr_quizs = fields.Integer("Number of quizzes", compute="_count_presentations", store=True)
total_slides = fields.Integer(compute='_count_presentations', store=True, oldname='total')
@api.depends('slide_ids.slide_type', 'slide_ids.is_published')
@@ -432,6 +435,7 @@ class Category(models.Model):
'nbr_videos': result[record_id].get('video', 0),
'nbr_infographics': result[record_id].get('infographic', 0),
'nbr_webpages': result[record_id].get('webpage', 0),
+ 'nbr_quizs': result[record_id].get('quiz', 0),
'total_slides': 0
}
@@ -440,5 +444,6 @@ class Category(models.Model):
statistics['total_slides'] += statistics['nbr_videos']
statistics['total_slides'] += statistics['nbr_infographics']
statistics['total_slides'] += statistics['nbr_webpages']
+ statistics['total_slides'] += statistics['nbr_quizs']
return statistics
diff --git a/addons/website_slides/models/slide_question.py b/addons/website_slides/models/slide_question.py
new file mode 100644
index 00000000000..0193558ba46
--- /dev/null
+++ b/addons/website_slides/models/slide_question.py
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import api, fields, models, _
+from odoo.exceptions import ValidationError
+
+
+class SlideQuestion(models.Model):
+ _name = "slide.question"
+ _rec_name = "question"
+ _description = """
+ Question for a slide. A slide can have multiple questions and each question
+ must have at least 2 possible answers and only one good answer.
+ """
+
+ sequence = fields.Integer("Sequence", default=10)
+ question = fields.Char("Question Name", required=True)
+ slide_id = fields.Many2one('slide.slide', string="Slide")
+ answer_ids = fields.One2many('slide.answer', 'question_id', string="Answer")
+
+ @api.constrains('answer_ids')
+ def _check_only_one_good_answer(self):
+ for question in self:
+ good_answer_count = 0
+ for answer in question.answer_ids:
+ if answer.is_correct:
+ good_answer_count += 1
+ if good_answer_count > 1:
+ raise ValidationError(_('A question can only have one good answer'))
+
+ @api.constrains('answer_ids')
+ def _check_correct_answer(self):
+ for question in self:
+ if not any([answer.is_correct for answer in question.answer_ids]):
+ raise ValidationError(_("A question must at least have one good answer"))
+
+ @api.constrains('answer_ids')
+ def _check_at_least_2_answers(self):
+ for question in self:
+ if len(question.answer_ids) < 2:
+ raise ValidationError(_("A question must at least have two possible answers"))
+
+
+class SlideAnswer(models.Model):
+ _name = "slide.answer"
+ _rec_name = "text_value"
+ _description = """Answer for a slide question"""
+
+ question_id = fields.Many2one('slide.question', string="Question")
+ text_value = fields.Char("Answer", required=True,)
+ is_correct = fields.Boolean("Is correct answer", default=False)
diff --git a/addons/website_slides/models/slide_slide.py b/addons/website_slides/models/slide_slide.py
index 35b9fefc4e5..a71446b82fa 100644
--- a/addons/website_slides/models/slide_slide.py
+++ b/addons/website_slides/models/slide_slide.py
@@ -23,12 +23,14 @@ class SlidePartnerRelation(models.Model):
_description = 'Slide / Partner decorated m2m'
_table = 'slide_slide_partner'
- slide_id = fields.Many2one('slide.slide', index=True, required=True)
+ slide_id = fields.Many2one('slide.slide', ondelete="cascade", index=True, required=True)
channel_id = fields.Many2one('slide.channel', string="Channel", related="slide_id.channel_id", store=True, index=True)
partner_id = fields.Many2one('res.partner', index=True, required=True)
vote = fields.Integer('Vote', default=0)
completed = fields.Boolean('Completed')
+ quiz_attempts_count = fields.Integer(string="Number of times this user attempted the quiz")
+
class SlideLink(models.Model):
_name = 'slide.slide.link'
@@ -95,6 +97,7 @@ class Slide(models.Model):
'most_voted': 'likes desc',
'latest': 'date_published desc',
}
+ _order = 'category_sequence asc, sequence asc'
_sql_constraints = [
('exclusion_html_content_and_url', "CHECK(html_content IS NULL OR url IS NULL)", "A slide is either filled with a document url or HTML content. Not both.")
@@ -106,6 +109,8 @@ class Slide(models.Model):
# description
name = fields.Char('Title', required=True, translate=True)
active = fields.Boolean(default=True)
+ sequence = fields.Integer('Sequence', default=10)
+ category_sequence = fields.Integer('Category sequence', related="category_id.sequence", store=True)
user_id = fields.Many2one('res.users', string='Uploaded by', default=lambda self: self.env.uid)
description = fields.Text('Description', translate=True)
channel_id = fields.Many2one('slide.channel', string="Channel", required=True)
@@ -126,15 +131,24 @@ class Slide(models.Model):
partner_ids = fields.Many2many('res.partner', 'slide_slide_partner', 'slide_id', 'partner_id',
string='Subscribers', groups='base.group_website_publisher')
slide_partner_ids = fields.One2many('slide.slide.partner', 'slide_id', string='Subscribers information', groups='base.group_website_publisher')
- user_membership_id = fields.Many2one('slide.slide.partner', string="Subscriber information", compute='_compute_user_membership_id',
+ user_membership_id = fields.Many2one(
+ 'slide.slide.partner', string="Subscriber information", compute='_compute_user_membership_id',
help="Subscriber information for the current logged in user")
+ # Quiz related fields
+ question_ids = fields.One2many("slide.question","slide_id", string="Questions")
+ quiz_first_attempt_reward = fields.Integer("First attempt reward", default=10)
+ quiz_second_attempt_reward = fields.Integer("Second attempt reward", default=7)
+ quiz_third_attempt_reward = fields.Integer("Third attempt reward", default=5,)
+ quiz_fourth_attempt_reward = fields.Integer("Reward for every attempt after the third try", default=2)
+
# content
slide_type = fields.Selection([
('infographic', 'Infographic'),
+ ('webpage', 'Web Page'),
('presentation', 'Presentation'),
('document', 'Document'),
- ('webpage', 'Web Page'),
- ('video', 'Video')],
+ ('video', 'Video'),
+ ('quiz', "Quiz")],
string='Type', required=True,
default='document', readonly=True,
help="The document type will be set automatically based on the document URL and properties (e.g. height and width for presentation and document).")
diff --git a/addons/website_slides/security/ir.model.access.csv b/addons/website_slides/security/ir.model.access.csv
index 9dbfa916ff6..ce9bcf2593d 100644
--- a/addons/website_slides/security/ir.model.access.csv
+++ b/addons/website_slides/security/ir.model.access.csv
@@ -3,6 +3,10 @@ access_slide_slide_all,slide.slide.all,model_slide_slide,,1,0,0,0
access_slide_slide_publisher,slide.slide.publisher,model_slide_slide,website.group_website_publisher,1,1,1,1
access_slide_slide_partner_all,slide.slide.partner.all,model_slide_slide_partner,,0,0,0,0
access_slide_slide_partner_system,slide.slide.partner.system,model_slide_slide_partner,website.group_website_publisher,1,1,1,1
+access_slide_question_all,slide.question.all,model_slide_question,,1,0,0,0
+access_slide_question_publisher,slide.question.publisher,model_slide_question,website.group_website_publisher,1,1,1,1
+access_slide_answer_all,slide.answer.all,model_slide_answer,,1,0,0,0
+access_slide_answer_publisher,slide.answer.publisher,model_slide_answer,website.group_website_publisher,1,1,1,1
access_slide_tag_all,slide.tag.all,model_slide_tag,,1,0,0,0
access_slide_tag_publisher,slide.tag.publisher,model_slide_tag,website.group_website_publisher,1,1,1,1
access_slide_channel_tag_all,slide.channel.tag.all,model_slide_channel_tag,,1,0,0,0
diff --git a/addons/website_slides/static/src/js/slides_category.js b/addons/website_slides/static/src/js/slides_category.js
new file mode 100644
index 00000000000..d1d7efb7747
--- /dev/null
+++ b/addons/website_slides/static/src/js/slides_category.js
@@ -0,0 +1,148 @@
+odoo.define('website_slides.add.section', function (require) {
+ 'use strict';
+
+ var sAnimations = require('website.content.snippets.animation');
+ var core = require('web.core');
+ var Widget = require('web.Widget');
+
+ var _t = core._t;
+
+ var SectionDialog = Widget.extend({
+ template: 'website.slide.add.section',
+ events: {
+ 'hidden.bs.modal': 'destroy',
+ 'click button.save': '_save',
+ 'click button[data-dismiss="modal"]': '_cancel',
+ 'change input#upload': '_slideUpload',
+ 'change input#url': '_slideUrl',
+ 'click .list-group-item': function (ev) {
+ this.$('.list-group-item').removeClass('active');
+ $(ev.target).closest('li').addClass('active');
+ }
+ },
+
+ /**
+ * @override
+ * @param {Object} el
+ * @param {number} channel_id
+ */
+ init: function (el, channelID) {
+ this._super(el, channelID);
+ this.channel_id = parseInt(channelID, 10);
+ this.index_content = '';
+ },
+ /**
+ * @override
+ */
+ start: function () {
+ this.$el.modal({
+ backdrop: 'static'
+ });
+
+ return this._super.apply(this, arguments);
+ },
+ _getValue: function () {
+ var canvas = this.$('#data_canvas')[0],
+ values = {
+ 'channel_id': this.channel_id || '',
+ 'url': this.$('#url').val(),
+ 'name': this.$('#section_name').val()
+ };
+ return values;
+ },
+ /**
+ * @private
+ */
+ _validate: function () {
+ this.$('.form-group').removeClass('o_has_error').find('.form-control, .custom-select').removeClass('is-invalid');
+ if (!this.$('#name').val()) {
+ this.$('#name').closest('.form-group').addClass('o_has_error').find('.form-control, .custom-select').addClass('is-invalid');
+ return false;
+ }
+ var url = this.$('#url').val() ? this.is_valid_url : false;
+ if (!(this.file.name || url)) {
+ this.$('#url').closest('.form-group').addClass('o_has_error').find('.form-control, .custom-select').addClass('is-invalid');
+ return false;
+ }
+ return true;
+ },
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * @override
+ * @param {Object} ev
+ */
+ _save: function (ev) {
+ var self = this;
+
+ var values = this._getValue();
+ if ($(ev.target).data('published')) {
+ values.website_published = true;
+ }
+ this.$('.oe_slides_upload_loading').show();
+ this.$('.modal-footer, .modal-body').hide();
+ this._rpc({
+ route: '/slides/add_category',
+ params: values,
+ }).then(function (data) {
+ if (data.error) {
+ self._displayAlert(data.error);
+ self.$('.oe_slides_upload_loading').hide();
+ self.$('.modal-footer, .modal-body').show();
+
+ } else {
+ window.location = data.url;
+ }
+ });
+ },
+ /**
+ * @override
+ */
+ _cancel: function () {
+ this.trigger('cancel');
+ },
+
+ });
+
+ sAnimations.registry.websiteSlidesSection = sAnimations.Class.extend({
+ selector: '.oe_slide_js_add_section',
+ xmlDependencies: ['/website_slides/static/src/xml/website_slides_upload.xml'],
+ read_events: {
+ 'click': '_onAddSectionClick',
+ },
+
+ /**
+ * @override
+ */
+ start: function () {
+ // Automatically open the upload dialog if requested from query string
+ if ($.deparam.querystring().enable_slide_upload !== undefined) {
+ this._openDialog(this.$el.attr('channel_id'));
+ }
+ return this._super.apply(this, arguments);
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ _openDialog: function (channelID) {
+ new SectionDialog(this, channelID).appendTo(document.body);
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * @private
+ * @param {Event} ev
+ */
+ _onAddSectionClick: function (ev) {
+ console.log('test');
+ this._openDialog($(ev.currentTarget).attr('channel_id'));
+ },
+ });
+ }); \ No newline at end of file
diff --git a/addons/website_slides/static/src/js/slides_course_fullscreen_player.js b/addons/website_slides/static/src/js/slides_course_fullscreen_player.js
new file mode 100644
index 00000000000..1da173a2f46
--- /dev/null
+++ b/addons/website_slides/static/src/js/slides_course_fullscreen_player.js
@@ -0,0 +1,427 @@
+var onYouTubeIframeAPIReady;
+
+odoo.define('website_slides.fullscreen', function (require) {
+ 'use strict';
+ var sAnimations = require('website.content.snippets.animation');
+ var Widget = require('web.Widget');
+ var core = require('web.core');
+ var QWeb = core.qweb;
+
+ var tag = document.createElement('script');
+
+ var QuizWidget = require('website_slides.quiz');
+
+ tag.src = "https://www.youtube.com/iframe_api";
+ var firstScriptTag = document.getElementsByTagName('script')[0];
+ firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
+
+
+ var Fullscreen = Widget.extend({
+ /**
+ * @override
+ * @param {Object} el
+ * @param {Object} data holding channelId and optionally upload and publish control parameters
+ */
+ init: function(el, course_id,slide_id, user_id){
+ this._super.apply(this,arguments);
+ this.courseID = parseInt(course_id, 10);
+ this.slideID = parseInt(slide_id, 10);
+ this.userID = parseInt(user_id, 10);
+ this.course = undefined;
+ this.slide = undefined;
+ this.slides = [];
+ this.nextSlide = undefined;
+ this.previousSlide = undefined;
+ this.url = undefined;
+ this.urlToSmallScreen = undefined;
+ this.activetab = undefined;
+ this.player = undefined;
+ this.goToQuiz = false;
+ this.answeredQuestions = [];
+ this.slideTitle = undefined;
+ },
+ start: function(){
+ var self = this;
+ this.url = window.location.pathname;
+ this.urlToSmallScreen = this.url.replace('/fullscreen','');
+ this._getSlides();
+ this._renderPlayer();
+ this._bindListEvents();
+
+ $(document).keydown(this._onKeyDown.bind(this));
+ return this._super.apply(this, arguments);
+ },
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+ /**
+ * @private
+ * Renders the player accordingly to the current slide
+ */
+ _renderPlayer: function(){
+ var self = this;
+ var embed_url;
+ if(self.slide.slide_type !== 'webpage' || self.slide.htmlContent){
+ if((self.slide.slide_type == "quiz" || self.slide.hasQuiz) && !self.slide.quiz){
+ self._fetchQuiz();
+ } else {
+ embed_url = $(this.slide.embed_code).attr('src');
+ if (self.slide.slide_type === "video"){
+ embed_url = "https://" + embed_url + "&rel=0&autoplay=1&enablejsapi=1&origin=" + window.location.origin;
+ }
+ $('.o_wslides_fullscreen_player').html(QWeb.render('website.slides.fullscreen', {
+ slide: self.slide,
+ nextSlide: self.nextSlide,
+ questions: self.slide.quiz ? self.slide.quiz.questions: '',
+ reward: self.slide.quiz ? self.slide.quiz.nb_attempts < 3 ? self.slide.quiz.possible_rewards[self.slide.quiz.nb_attempts] : self.slide.quiz.possible_rewards[3]: self.slide.maxPoints,
+ embed_url: embed_url,
+ question_count: self.slide.quiz ? self.slide.quiz.questions.length : '',
+ letters: self.slide.quiz ? self.letters : '',
+ showMiniQuiz: self.goToQuiz
+ }));
+ if(self.slide.slide_type === "video"){
+ self._renderYoutubeIframe();
+ }
+ if(self.slide.slide_type == 'webpage'){
+ self._renderWebpage();
+ }
+ if((self.slide.slide_type == "presentation" || self.slide.slide_type == "document" || self.slide.slide_type == "infographic" || self.slide.slide_type == "webpage") && !self.slide.quiz){
+ self._setSlideStateAsDone();
+ }
+ if ((self.slide.quiz && self.slide.slide_type === "quiz") || self.goToQuiz){
+ self._renderQuiz();
+ }
+ }
+ } else {
+ self._fetchHtmlContent();
+ }
+ self._renderTitle();
+ },
+ _renderYoutubeIframe: function(){
+ var self = this;
+ /**
+ * Due to issues of synchronization between the youtube api script and the widget's instanciation.
+ */
+ try{
+ self._setupYoutubePlayer();
+ }
+ catch {
+ onYouTubeIframeAPIReady = function(){
+ var self = this;
+ self._setupYoutubePlayer();
+ }.bind(this)
+ }
+ },
+ _renderWebpage: function(){
+ var self = this;
+ $(self.slide.htmlContent).appendTo('.o_wslides_fullscreen_webpage_content');
+ },
+ _renderQuiz: function(){
+ var self = this;
+ var Quiz = new QuizWidget(this, self.slide);
+ Quiz.appendTo('.o_wslides_fullscreen_player');
+ $('.next-slide').click(function(){
+ self._goToNextSlide();
+ })
+ $('.back-to-video').click(function(){
+ self.goToQuiz = false;
+ self._renderPlayer();
+ });
+ },
+ _renderTitle: function(){
+ var self = this;
+ $('.o_wslides_fullscreen_slide_title').empty().html(QWeb.render('website.course.fullscreen.title', {
+ slide: self.slide,
+ miniQuiz: self.goToQuiz
+ }));
+ },
+ /**
+ * @private
+ * Links the youtube api to the iframe present in the template
+ */
+ _setupYoutubePlayer: function(){
+ var self = this;
+ self.player = new YT.Player('youtube-player', {
+ host: 'https://www.youtube.com',
+ playerVars: {'autoplay': 1, 'origin': window.location.origin},
+ autoplay: 1,
+ events: {
+ 'onReady': self._onPlayerReady,
+ 'onStateChange': this._onPlayerStateChange.bind(self)
+ }
+ });
+ },
+ /**
+ * @param {*} event
+ * Specific method of the youtube api.
+ * Whenever the player starts playing, a setinterval is created.
+ * This setinterval is used to check te user's progress in the video.
+ * Once the user reaches a particular time in the video, the slide will be considered as completed if the video doesn't have a mini-quiz.
+ * This method also allows to automatically go to the next slide (or the quiz associated to the current video) once the video is over
+ */
+ _onPlayerStateChange: function(event){
+ var self = this;
+ var tid;
+ clearInterval(self.tid);
+ if (event.data == YT.PlayerState.PLAYING && !self.slide.done) {
+ self.tid = setInterval(function(){
+ if(event.target.getCurrentTime){
+ var currentTime = event.target.getCurrentTime();
+ var totalTime = event.target.getDuration();
+ if(totalTime && currentTime > totalTime - 30){
+ clearInterval(self.tid);
+ if(!self.slide.hasQuiz && !self.slide.done){
+ self.slide.done = true;
+ self._setSlideStateAsDone();
+ }
+ }
+ }
+ }, 1000);
+ }
+ if(event.data == YT.PlayerState.ENDED){
+ self.player = undefined;
+ self._goToNextSlide();
+ }
+ },
+ /**
+ * @private
+ * Creates slides objects from every slide-list-cells attributes
+ */
+ _getSlides: function(){
+ var self = this;
+ var slides = $('.o_wslides_fullscreen_sidebar_slide_tab');
+ for(var i = 0; i < slides.length;i++){
+ var slide = $(slides[i]);
+ self.slides.push({
+ id: parseInt(slide.attr('slide_id'), 10),
+ name: slide.attr('slide_name'),
+ embed_code: slide.attr('slide_embed_code'),
+ slide_type: slide.attr('slide_type'),
+ done: slide.attr('done'),
+ hasQuiz: slide.attr('quiz'),
+ slug: slide.attr('slide_slug'),
+ htmlContent: undefined
+ });
+ this._getActiveSlide();
+ }
+ },
+ /**
+ * @private
+ * @param {object} slide
+ * Fetch the quiz for a particular slide
+ */
+ _fetchQuiz: function(){
+ var self = this;
+ self._rpc({
+ route:"/slide/quiz/get",
+ params: {
+ 'slide_id': self.slide.id
+ }
+ }).then(function(data){
+ if(data){
+ self.slide.quiz = data;
+ self._renderPlayer();
+ }
+ })
+ },
+ _fetchHtmlContent: function(){
+ var self = this;
+ self._rpc({
+ route:"/slide/html_content/get",
+ params: {
+ 'slide_id': self.slide.id
+ }
+ }).then(function(data){
+ self.slide.htmlContent = data.html_content;
+ self._renderPlayer();
+ })
+ },
+ /**
+ * @private
+ * Once the completion conditions are filled,
+ * sends a json request to the backend to set the relation between the slide and the user as being completed
+ */
+ _setSlideStateAsDone: function(){
+ var self = this;
+ self._rpc({
+ route: '/slides/set_completed',
+ params: {
+ slide_id: self.slide.id,
+ }
+ }).then(function(data){
+ $('#check-'+self.slide.id).replaceWith($('<i class="check-done o_wslides_slide_completed fa fa-check-circle"></i>'))
+ self.slide.done = true;
+ clearInterval(self.tid);
+ self.channelCompletion = data.channel_completion;
+ self._updateProgressbar();
+ });
+ },
+ _updateProgressbar: function(){
+ var self = this;
+ var completion = self.channelCompletion <= 100 ? self.channelCompletion : 100;
+ $('.o_wslides_fullscreen_sidebar_progress_gauge').css('width', completion + "%" );
+ $('.o_wslides_progress_percentage').text(completion);
+ },
+ /**
+ * @private
+ * Creates an array of letters to be used in the quiz with a foreach
+ */
+ _generateQuizLetters: function(){
+ var letters = [];
+ for(var i = 65; i < 91; i++){
+ letters.push(String.fromCharCode(i));
+ }
+ return letters;
+ },
+ _goToNextSlide: function(){
+ var self = this;
+ clearInterval(self.tid);
+ self.player = undefined;
+ self.goToQuiz = self.slide.hasQuiz && !self.goToQuiz;
+ if(self.nextSlide && !self.goToQuiz){
+ self.slide = self.nextSlide;
+ self.index++;
+ self._setActiveTab();
+ self._renderPlayer();
+ self._setPreviousAndNextSlides();
+ self._updateUrl();
+ history.pushState(null,'',self.url);
+ }
+ else if(self.nextSlide){
+ self._renderPlayer();
+ }
+ },
+ _goToPreviousSlide: function(){
+ var self = this;
+ clearInterval(self.tid);
+ self.goToQuiz = false;
+ self.player = undefined;
+ if(self.previousSlide){
+ self.slide = self.previousSlide;
+ self.index--;
+ self._setActiveTab();
+ self._renderPlayer();
+ self._setPreviousAndNextSlides();
+ self._updateUrl();
+ history.pushState(null,'',self.url);
+ }
+ },
+ _setPreviousAndNextSlides: function(){
+ var self = this;
+ self.previousSlide = self.index > 0 ? self.slides[self.index-1] : undefined;
+ self.nextSlide = self.index < (self.slides.length - 1) ? self.slides[self.index+1] : undefined;
+ },
+ /**
+ * Changes the url whenever the user changes slides.
+ * This allows the user to refresh the page and stay at the right video
+ */
+ _updateUrl: function(){
+ var self = this;
+ var url = window.location.pathname.split('/');
+ url[url.length-1] = self.slide.slug;
+ url = url.join('/');
+ self.url = url;
+ self.urlToSmallScreen = self.url;
+ self.url += "?fullscreen=1";
+ $('.o_wslides_small_screen').attr('href', self.urlToSmallScreen);
+ },
+ /**
+ * Whenever the user changes slide, change the active tab
+ */
+ _setActiveTab: function(){
+ var self = this;
+ self.activeTab.removeClass('active');
+ $('li.active').removeClass('active');
+ $('li[slide_id='+self.slide.id+']').addClass('active');
+ self.activeTab = $('.o_wslides_fullscreen_sidebar_slide_tab[index="'+self.index+'"]')
+ self.activeTab.addClass('active');
+ },
+ /**
+ * The first time the user gets on the player,
+ * get the slide that is represented by the active tab in the sidebar
+ */
+ _getActiveSlide: function(){
+ var self = this;
+ self.activeTab = $('.o_wslides_fullscreen_sidebar_slide_tab.active');
+ self.index = parseInt(self.activeTab.attr('index'), 10);
+ self.slide = self.slides[self.index];
+ self._setPreviousAndNextSlides();
+ },
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+ _onListCellClick: function(ev){
+ var self = this;
+ clearInterval(self.tid);
+ self.player = undefined;
+ var target = $(ev.currentTarget);
+ self.goToQuiz = false;
+ if(target[0] !== self.activeTab[0]){
+ self.activeTab.removeClass('active');
+ target.addClass('active');
+ self.index = parseInt(target.attr('index'));
+ self._getActiveSlide();
+ self._renderPlayer();
+ $('li.active').removeClass('active');
+ $('li[slide_id='+self.slide.id+']').addClass('active');
+ self._setPreviousAndNextSlides();
+ self._updateUrl();
+ history.pushState(null,'',self.url);
+ }
+ },
+ _onMiniQuizClick: function(ev){
+ var self = this;
+ self.index = parseInt($(ev.currentTarget).attr('index'));
+ self.slide = self.slides[self.index];
+ self.goToQuiz = true;
+ self._setPreviousAndNextSlides();
+ self._renderPlayer();
+ self._setActiveTab();
+ self._updateUrl();
+ history.pushState(null,'' ,self.url);
+ },
+ /**
+ * @private
+ * Binds events related to the list
+ */
+ _bindListEvents: function(){
+ var self = this;
+ $('.o_wslides_fullscreen_sidebar_slide_tab').each(function () {
+ $(this).click(self._onListCellClick.bind(self));
+ });
+
+ $('.o_wslides_slide_quiz ').each(function(){
+ $(this).click(self._onMiniQuizClick.bind(self));
+ })
+ },
+ _onKeyDown: function(ev){
+ var self = this;
+ switch(ev.key){
+ case "ArrowRight":
+ self._goToNextSlide();
+ break;
+ case "ArrowLeft":
+ self._goToPreviousSlide();
+ break;
+ }
+ },
+ })
+
+ sAnimations.registry.websiteSlidesFullscreenPlayer = Widget.extend({
+ selector: '.oe_js_course_slide',
+ xmlDependencies: ['/website_slides/static/src/xml/website_slides_fullscreen.xml'],
+ init: function(el){
+ this._super.apply(this, arguments);
+ },
+ start: function(){
+ this._super.apply(this, arguments);
+ var user_id = this.$el.attr('user_id');
+ var course_id = this.$el.attr('course_id');
+ var slide_id = this.$el.attr('slide_id');
+ var fullscreen = new Fullscreen(this, course_id, slide_id, user_id);
+ fullscreen.appendTo(".oe_js_course_slide");
+ }
+ });
+
+ return Fullscreen;
+}); \ No newline at end of file
diff --git a/addons/website_slides/static/src/js/slides_course_progress_bar.js b/addons/website_slides/static/src/js/slides_course_progress_bar.js
new file mode 100644
index 00000000000..2412368018f
--- /dev/null
+++ b/addons/website_slides/static/src/js/slides_course_progress_bar.js
@@ -0,0 +1,47 @@
+odoo.define('website_slides.progress.bar', function (require) {
+
+ var sAnimations = require('website.content.snippets.animation');
+ var Widget = require('web.Widget');
+
+ var ProgressBar = Widget.extend({
+
+ /**
+ * @override
+ * @param {Object} el
+ * @param {number} channel_id
+ */
+ init: function (el, completion) {
+ this._super(el);
+ this.completion = completion;
+ },
+ /**
+ * @override
+ */
+ start: function () {
+ this._renderProgressBar();
+ return this._super.apply(this, arguments);
+ },
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+ _renderProgressBar: function (ev) {
+ var self = this;
+ $('.oe_slide_js_progress_bar').css('width', (self.completion <= 100 ? self.completion : 100) + '%');
+ },
+ });
+
+ sAnimations.registry.websiteSlidesProgressBar = sAnimations.Class.extend({
+ selector: '.oe_slide_js_progress_bar',
+ /**
+ * @override
+ */
+ start: function () {
+ var completion = parseInt($('.oe_slide_js_progress_bar').attr('channel_completion'));
+ var progressBar = new ProgressBar(this, completion);
+ progressBar.appendTo(".oe_slide_js_progress_bar");
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ return ProgressBar;
+ });
diff --git a/addons/website_slides/static/src/js/slides_course_quiz.js b/addons/website_slides/static/src/js/slides_course_quiz.js
new file mode 100644
index 00000000000..f98d5b3ed8a
--- /dev/null
+++ b/addons/website_slides/static/src/js/slides_course_quiz.js
@@ -0,0 +1,206 @@
+odoo.define('website_slides.quiz', function (require) {
+ 'use strict';
+ var sAnimations = require('website.content.snippets.animation');
+ var core = require('web.core');
+ var Widget = require('web.Widget');
+
+ var QWeb = core.qweb;
+
+ var Quiz= Widget.extend({
+ /**
+ * @override
+ * @param {Object} el
+ * @param {Object} data holding all the slide elements needed for the quiz
+ * It will either come from the fullscreen widget or the sAnimation at the end of this file
+ */
+ init: function(el, data){
+ this.slide = data;
+ this.answeredQuestions = [];
+ this.fullscreen = el;
+ return this._super.apply(this,arguments);
+ },
+ start: function(){
+ var self = this;
+ self._bindQuizEvents();
+ /**
+ * If the quiz is rendered by the server instead of the fullscreen widget,
+ * questions and their answers will have to be created manually from attributes
+ */
+ if(self.slide.quiz.questions.length === 0){
+ this._setQuestions();
+ }
+ return this._super.apply(this, arguments);
+ },
+ _renderSuccessModal: function(){
+ var self =this;
+ $('.o_wslides_fullscreen_quiz').append(QWeb.render('website.course.quiz.success', {
+ data: data
+ }));
+ $('.submit-quiz').remove();
+ $('.next-slide').css('display', 'inline-block');
+ $('.next-slide').click(function(){
+ self.fullscreen._goToNextSlide();
+ });
+ $('.o_wslides_quiz_success_modal_close').click(function(){
+ $('.o_wslides_quiz_success_modal').remove();
+ $('.o_wslides_quiz_modal_background').remove();
+ });
+ $(".o_wslides_quiz_modal_background").click(function(ev){
+ $(ev.currentTarget).remove();
+ $('.o_wslides_quiz_success_modal').remove();
+ });
+ },
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+ /**
+ * @private
+ * In case the quiz is rendered by the server and the data don't come from the fullscreen widget,
+ * questions and their answers will have to be set here by using attributes
+ */
+ _setQuestions: function(){
+ var self = this;
+ $('.o_wslides_quiz_question').each(function(){
+ self.slide.quiz.questions.push({
+ id: parseInt($(this).attr('id')),
+ title: $(this).attr('title'),
+ answers: []
+ });
+ })
+ for(var i = 0; i < self.slide.quiz.questions.length; i++){
+ self._setAnswersForQuestion(self.slide.quiz.questions[i]);
+ }
+ },
+ _setAnswersForQuestion: function (question){
+ var self = this;
+ $('.o_wslides_quiz_answer[question_id='+question.id+']').each(function (){
+ question.answers.push({
+ id: parseInt($(this).attr('id')),
+ text: $(this).attr('text_value'),
+ is_correct: $(this).attr('is_correct')
+ });
+ });
+ },
+ _updateProgressbar: function (){
+ var self = this;
+ var completion = self.channelCompletion <= 100 ? self.channelCompletion : 100;
+ $('.o_wslides_fullscreen_sidebar_progress_gauge').css('width', completion + "%" );
+ $('.o_wslides_progress_percentage').text(completion);
+ },
+ _bindQuizEvents: function (){
+ var self = this;
+ if (!self.slide.done){
+ $('.o_wslides_quiz_answer').each(function(){
+ $(this).click(self._onAnswerClick.bind(self));
+ });
+ }
+ $('.submit-quiz').click(self._onSubmitQuiz.bind(self));
+ },
+ _highlightAnswers: function (answers){
+ var self = this;
+ self.answeredQuestions = [];
+ for (var i = 0; i < answers.goodAnswers.length; i++){
+ $('#answer'+ answers.goodAnswers[i] +'').addClass('o_wslides_quiz_good_answer');
+ $('#answer'+ answers.goodAnswers[i] +' .o_wslides_quiz_radio_box span').replaceWith($('<i class="fa fa-check-circle"></i>'));
+ var questionID =$('#answer'+ answers.goodAnswers[i] +' .o_wslides_quiz_radio_box input').attr('question_id');
+ self.answeredQuestions.push(questionID);
+ $('.o_wslides_quiz_answer[question_id='+questionID+']:not(.o_wslides_quiz_good_answer)').addClass('o_wslides_quiz_ignored_answer');
+ $('.o_wslides_quiz_answer[question_id='+questionID+']').each(function(){
+ $(this).unbind('click')
+ });
+ $('input[question_id='+questionID+']').each(function(){
+ $(this).prop('disabled',true);
+ });
+ }
+ for (i = 0; i < answers.badAnswers.length; i++){
+ $('#answer'+ answers.badAnswers[i]).removeClass('o_wslides_quiz_good_answer')
+ .addClass('o_wslides_quiz_bad_answer')
+ .unbind('click');
+ $('#answer'+ answers.badAnswers[i] +' .o_wslides_quiz_radio_box span').replaceWith($('<i class="fa fa-times "></i>'));
+ $('#answer'+ answers.badAnswers[i] +' .o_wslides_quiz_radio_box input').prop('checked', false);
+ }
+ },
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+ _onAnswerClick: function (ev){
+ var self = this;
+ var target = $(ev.currentTarget);
+ if ((self.answeredQuestions.indexOf(target.attr('question_id'))) === -1){
+ var id = target.attr('id');
+ var question_id = target.attr('question_id');
+ $('.o_wslides_quiz_answer[question_id='+question_id+']').removeClass('o_wslides_quiz_good_answer');
+ $('#'+id+' input[type=radio]').prop('checked', true);
+ }
+ },
+ _onSubmitQuiz: function (){
+ var self = this;
+ var inputs = $('input[type=radio]:checked');
+ var values = [];
+ for (var i = 0; i < inputs.length; i++){
+ values.push(parseInt($(inputs[i]).val()));
+ }
+ if (values.length === self.slide.quiz.questions.length){
+ $('.quiz-danger').remove();
+ self._rpc({
+ route: "/slide/quiz/submit",
+ params: {
+ slide_id: self.slide.id,
+ answer_ids: values,
+ quiz_id: self.slide.quiz_id
+ }
+ }).then(function(data){
+ self._highlightAnswers(data);
+ self.slide.quiz.nb_attempts = data.attempts_count;
+ if (data.passed){
+ self.channelCompletion = data.channel_completion;
+ self._updateProgressbar();
+ $('#check-'+self.slide.id).replaceWith($('<i class="check-done o_wslides_slide_completed fa fa-check-circle"></i>'));
+ self.slide.done = true;
+ self._renderSuccessModal();
+ }
+ else {
+ $('#quiz-points').text(self.slide.quiz.nb_attempts < 3 ? self.slide.quiz.possible_rewards[self.slide.quiz.nb_attempts] : self.slide.quiz.possible_rewards[self.slide.quiz.possible_rewards.length-1]);
+ }
+ });
+ } else {
+ $('.o_wslides_fullscreen_player').append($('<p class="quiz-danger text-danger mt-1">All questions must be answered !</p>'));
+ }
+ },
+});
+
+ sAnimations.registry.websiteSlidesQuizNoFullscreen = Widget.extend({
+ selector: '.o_w_slides_quiz_no_fullscreen',
+ xmlDependencies: ['/website_slides/static/src/xml/website_slides_fullscreen.xml'],
+ init: function (el){
+ this._super.apply(this, arguments);
+ },
+ start: function (){
+ this._super.apply(this, arguments);
+ var slideID = parseInt(this.$el.attr('slide_id'),10);
+ var slideDone = this.$el.attr('slide_done');
+ var nbAttempts = parseInt(this.$el.attr('nb_attempts'), 10);
+ var firstAttemptReward = this.$el.attr('first_reward');
+ var secondAttemptReward = this.$el.attr('second_reward');
+ var thirdAttemptReward = this.$el.attr('third_reward');
+ var fourthAttemptReward = this.$el.attr('fourth_reward');
+ var possibleRewards = [firstAttemptReward,secondAttemptReward,thirdAttemptReward,fourthAttemptReward]
+ var data = {
+ id: slideID,
+ done: slideDone,
+ quiz: {
+ questions: [],
+ nb_attempts: nbAttempts,
+ possible_rewards: possibleRewards,
+ reward: nbAttempts < possibleRewards.length ? possibleRewards[nbAttempts] : possibleRewards[possibleRewards.length-1]
+ }
+ }
+ if(!slideDone){
+ var NewQuiz = new Quiz(this, data);
+ NewQuiz.appendTo(".o_w_slides_quiz_no_fullscreen");
+ }
+ }
+ });
+
+ return Quiz;
+}); \ No newline at end of file
diff --git a/addons/website_slides/static/src/js/slides_course_sidebar_list.js b/addons/website_slides/static/src/js/slides_course_sidebar_list.js
new file mode 100644
index 00000000000..11b61d4e5ec
--- /dev/null
+++ b/addons/website_slides/static/src/js/slides_course_sidebar_list.js
@@ -0,0 +1,34 @@
+odoo.define('website_slides.sidebar', function (require) {
+ 'use strict';
+ var sAnimations = require('website.content.snippets.animation');
+ var core = require('web.core');
+ var Widget = require('web.Widget');
+
+ var SideBar = Widget.extend({
+ init: function (el){
+ this._super.apply(this,arguments);
+ },
+ start: function (){
+ $('.o_wslides_fullscreen_toggle_sidebar').click(function (ev){
+ ev.preventDefault();
+ $(ev.currentTarget).toggleClass('active');
+ $('.o_wslides_fullscreen_sidebar').toggleClass('o_wslides_fullscreen_sidebar_hidden');
+ $('.o_wslides_fullscreen_player').toggleClass('o_wslides_fullscreen_player_no_sidebar')
+ })
+ return this._super.apply(this, arguments);
+ },
+ });
+
+ sAnimations.registry.websiteSlidesSidebarList = Widget.extend({
+ selector: '.o_wslides_fullscreen_toggle_sidebar',
+ // xmlDependencies: ['/website_slides/static/src/xml/website_slides.xml'],
+ init: function (el){
+ this._super.apply(this, arguments);
+ },
+ start: function (){
+ this._super.apply(this, arguments);
+ var sideBar = new SideBar(this);
+ sideBar.appendTo(".oe_js_side_bar_list");
+ }
+ });
+}); \ No newline at end of file
diff --git a/addons/website_slides/static/src/js/slides_course_slides_list.js b/addons/website_slides/static/src/js/slides_course_slides_list.js
new file mode 100644
index 00000000000..bf634f542e3
--- /dev/null
+++ b/addons/website_slides/static/src/js/slides_course_slides_list.js
@@ -0,0 +1,251 @@
+odoo.define('website_slides.slideslist', function (require) {
+ 'use strict';
+ var sAnimations = require('website.content.snippets.animation');
+ var Widget = require('web.Widget');
+
+ var SlideUpload = require('website_slides.upload_modal');
+
+ var List = Widget.extend({
+ init: function (el){
+ this._super.apply(this,arguments);
+ this.draggedElement = undefined;
+ this.dropTarget = undefined;
+ this.slideCount = undefined;
+ this.slides = [];
+ this.categories = [];
+ },
+ start: function (){
+ this._super.apply(this,arguments);
+ this.slideCount = $('li.content-slide').length;
+ //Change links HREF to fullscreen mode for SEO
+ var links = $(".link-to-slide");
+ for (var i = 0; i < links.length; i++){
+ $(links[i]).attr('href', $(links[i]).attr('href') + "?fullscreen=1");
+ }
+ this._bindEvents();
+ },
+ _bindEvents: function (){
+ var self = this;
+ $('.slide-draggable').each(function (){
+ self._addSlideDragAndDropHandlers($(this));
+ });
+ $('.section-draggable').each(function (){
+ self._addSectionDragAndDropHandlers($(this));
+ });
+ $('.course-section').each(function (){
+ self._addDropSlideOnSectionHandler($(this));
+ });
+ },
+ _unbind: function (className){
+ $("."+className).each(function (){
+ $(this).unbind();
+ });
+ },
+ _unbindAll: function (){
+ this._unbind('slide-draggable');
+ this._unbind('section-draggable');
+ this._unbind('course-section');
+ },
+ _getSlides: function (){
+ var self = this;
+ var slides = $('li.content-slide');
+ for(var i = 0; i < slides.length;i++){
+ var slide = $(slides[i]);
+ self.slides.push({
+ id: parseInt(slide.attr('slide_id')),
+ category_id: parseInt(slide.attr('category_id')),
+ sequence: i
+ });
+ }
+ },
+ _getCategories: function (){
+ var self = this;
+ self.categories = [];
+ var categories = $('.course-section');
+ for (var i = 0; i < categories.length;i++){
+ var category = $(categories[i]);
+ self.categories.push(parseInt(category.attr('category_id')));
+ }
+ },
+ _addDropSlideOnSectionHandler: function (target){
+ var self = this;
+ target.on('drop', function (ev){
+ if (ev.preventDefault){
+ ev.preventDefault();
+ }
+ self.dropTarget = $(ev.currentTarget);
+ self.draggedElement[0].parentNode.removeChild(self.draggedElement[0]);
+ $('ul[category_id='+target.attr('category_id')+']').append(self.draggedElement)
+ self._addSlideDragAndDropHandlers(self.draggedElement);
+ self._reorderSlides();
+ });
+ target.on('dragover', function (ev){
+ if(ev.preventDefault){
+ ev.preventDefault();
+ }
+ });
+ },
+ _addSlideDragAndDropHandlers: function (target){
+ var self = this;
+ target.on('dragstart', function (ev){
+ $('.section-draggable').removeClass('hold')
+ self._unbind('section-draggable');
+ ev.originalEvent.dataTransfer.effectAllowed = 'move';
+ ev.originalEvent.dataTransfer.setData('text/html', this.outerHTML);
+ self.draggedElement = target;
+ self.draggedElement.addClass('hold');
+ });
+ target.on('dragover', function (ev){
+ if ($(ev.currentTarget) !== self.draggedElement){
+ if (ev.preventDefault){
+ ev.preventDefault();
+ }
+ target.addClass('slide-hovered');
+ }
+ });
+ target.on('dragleave', function (ev){
+ if (ev.preventDefault){
+ ev.preventDefault();
+ }
+ target.removeClass('slide-hovered');
+ });
+ target.on('drop', function (ev){
+ if (self.draggedElement.hasClass('slide-draggable') && target.hasClass('slide-draggable')){
+ if (ev.preventDefault){
+ ev.preventDefault();
+ }
+ target.removeClass('slide-hovered');
+ target.removeClass('hold');
+ if (target !== self.draggedElement){
+ self.dropTarget = $(ev.currentTarget);
+ self.draggedElement[0].parentNode.removeChild(self.draggedElement[0]);
+ var dropHTML = ev.originalEvent.dataTransfer.getData('text/html');
+ target[0].insertAdjacentHTML('beforebegin',dropHTML);
+ self.draggedElement = $(target[0].previousSibling);
+ self._reorderSlides();
+ }
+ self._unbindAll();
+ self._bindEvents();
+ }
+ });
+ target.on('dragend', function (ev){
+ if (ev.preventDefault){
+ ev.preventDefault();
+ }
+ target.removeClass('slide-hovered');
+ target.removeClass('hold');
+ });
+ },
+ _addSectionDragAndDropHandlers: function(target){
+ var self = this;
+ target.on('dragstart', function (ev){
+ self._unbind('slide-draggable');
+ self._unbind('course-section');
+ ev.originalEvent.dataTransfer.effectAllowed = 'move';
+ ev.originalEvent.dataTransfer.setData('text/html', this.outerHTML);
+ self.draggedElement = target;
+ self.draggedElement.addClass('hold');
+ });
+ target.on('dragover', function (ev){
+ if (target.hasClass('section-draggable') && self.draggedElement.hasClass('section-draggable')){
+ if (ev.preventDefault){
+ ev.preventDefault();
+ }
+ target.addClass('slide-hovered');
+ }
+ });
+ target.on('dragleave', function (ev){
+ if (ev.preventDefault){
+ ev.preventDefault();
+ }
+ target.removeClass('slide-hovered');
+ });
+ target.on('drop', function (ev){
+ if(ev.preventDefault){
+ ev.preventDefault();
+ }
+ if(self.draggedElement.hasClass('section-draggable') && target.hasClass('section-draggable')){
+ target.removeClass('slide-hovered');
+ target.removeClass('hold');
+ self.dropTarget = $(ev.currentTarget);
+ if(target !== self.draggedElement && $(ev.currentTarget).hasClass('section-draggable')){
+ self.draggedElement[0].parentNode.removeChild(self.draggedElement[0]);
+ var dropHTML = ev.originalEvent.dataTransfer.getData('text/html');
+ target[0].insertAdjacentHTML('beforebegin',dropHTML);
+ self.draggedElement = $(target[0].previousSibling);
+ self._reorderCategories();
+ self._reorderSlides();
+ self._rebindUploadButton(self.draggedElement.attr('category_id'));
+ }
+ self._unbindAll();
+ self._bindEvents();
+ }
+ });
+ target.on('dragend', function (ev){
+ if (ev.preventDefault){
+ ev.preventDefault();
+ }
+ target.removeClass('slide-hovered');
+ target.removeClass('hold');
+ });
+ },
+ _reorderCategories: function (){
+ var self = this;
+ self._getCategories();
+ self._rpc({
+ route: '/web/dataset/resequence',
+ params: {
+ model: "slide.category",
+ ids: self.categories
+ }
+ }).then(function (){
+ self._resetCategoriesIndex();
+ });
+ },
+ _resetCategoriesIndex: function (){
+ var categoriesIndexes = $('.section-index')
+ for (var i = 0; i < categoriesIndexes.length; i++){
+ $(categoriesIndexes[i]).text(i+1)
+ }
+ },
+ _reorderSlides: function(){
+ var self = this;
+ // In case the slide was transfered to another section
+ if (self.draggedElement.hasClass('slide-draggable')){
+ self.draggedElement.attr('category_id', parseInt(self.dropTarget.attr('category_id')))
+ }
+ self.slides = [];
+ self._getSlides();
+ self._rpc({
+ route: "/slides/resequence_slides",
+ params: {
+ slides_data: self.slides
+ }
+ }).then(function(){
+ });
+ },
+ _rebindUploadButton: function(categoryID){
+ var self = this;
+ this.$('.oe_slide_js_upload[data-category-id='+categoryID+']').click(function(ev){
+ ev.preventDefault();
+ var data = $(ev.currentTarget).data();
+ var dialog = new SlideUpload.SlideUploadDialog(self, data);
+ dialog.appendTo(document.body);
+ dialog.open();
+ })
+ }
+ })
+
+ sAnimations.registry.websiteSlidesCourseSlidesList = Widget.extend({
+ selector: '.oe_js_course_slides_list',
+ xmlDependencies: ['/website_slides/static/src/xml/website_slides_upload.xml'],
+ init: function (el){
+ this._super.apply(this, arguments);
+ },
+ start: function (){
+ this._super.apply(this, arguments);
+ var list = new List(this);
+ list.appendTo(".oe_js_course_slides_list");
+ }
+ });
+}); \ No newline at end of file
diff --git a/addons/website_slides/static/src/js/slides_delete_slide.js b/addons/website_slides/static/src/js/slides_delete_slide.js
new file mode 100644
index 00000000000..e3eef38502d
--- /dev/null
+++ b/addons/website_slides/static/src/js/slides_delete_slide.js
@@ -0,0 +1,88 @@
+odoo.define('website_slides.delete.slide', function (require) {
+
+ var sAnimations = require('website.content.snippets.animation');
+ var core = require('web.core');
+ var Widget = require('web.Widget');
+
+ var _t = core._t;
+ var QWeb = core.qweb;
+
+ var DeleteSlideDialog = Widget.extend({
+ template: 'website.slide.delete.slide',
+ events: {
+ 'hidden.bs.modal': 'destroy',
+ 'click button[data-dismiss="modal"]': '_cancel',
+ 'click button.delete': '_delete'
+ },
+
+ /**
+ * @override
+ * @param {Object} el
+ * @param {number} channel_id
+ */
+ init: function (el, slideID) {
+ this._super(el, slideID);
+ this.slide_id = parseInt(slideID, 10);
+ },
+ /**
+ * @override
+ */
+ start: function () {
+ this.$el.modal({
+ backdrop: 'static'
+ });
+ return this._super.apply(this, arguments);
+ },
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+ _delete: function (ev) {
+ var self = this;
+ // TO FIX: CallBack is not executed
+ $('[slide_id='+this.slide_id+']').remove();
+ this._rpc({
+ model: 'slide.slide',
+ method: 'unlink',
+ args: [[self.slide_id]],
+ }).then(function () {
+ $('[slide='+this.slide_id+']').remove();
+ });
+ },
+ /**
+ * @override
+ */
+ _cancel: function () {
+ this.trigger('cancel');
+ }
+ });
+
+ sAnimations.registry.websiteSlidesDeleteSlide = sAnimations.Class.extend({
+ selector: '.oe_slide_js_delete_slide',
+ xmlDependencies: ['/website_slides/static/src/xml/website_slides_upload.xml'],
+ read_events: {
+ 'click': '_onDeleteSlideClick',
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ _openDialog: function (slideID) {
+ new DeleteSlideDialog(this, slideID).appendTo(document.body);
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * @private
+ * @param {Event} ev
+ */
+ _onDeleteSlideClick: function (ev) {
+ var target = $(ev.currentTarget);
+ this._openDialog(target.attr('slide_id'));
+ },
+ });
+ return DeleteSlideDialog;
+ });
diff --git a/addons/website_slides/static/src/js/slides_upload.js b/addons/website_slides/static/src/js/slides_upload.js
index 855b7e52932..7b0a2964bf8 100644
--- a/addons/website_slides/static/src/js/slides_upload.js
+++ b/addons/website_slides/static/src/js/slides_upload.js
@@ -30,6 +30,7 @@ var SlideUploadDialog = Dialog.extend({
this._setup();
this.channelID = parseInt(options.channelId, 10);
+ this.defaultCategoryID = parseInt(options.categoryId,10)
this.canUpload = options.canUpload === 'True';
this.canPublish = options.canPublish === 'True';
@@ -198,6 +199,7 @@ var SlideUploadDialog = Dialog.extend({
*/
_getSelect2DropdownValues: function (){
var result = {};
+ var self = this;
// tags
var tagValues = [];
_.each(this.$('#tag_ids').select2('data'), function (val) {
@@ -211,11 +213,17 @@ var SlideUploadDialog = Dialog.extend({
result['tag_ids'] = tagValues;
}
// category
- var categoryValue = this.$('#category_id').select2('data');
- if (categoryValue && categoryValue.create) {
- result['category_id'] = [0, {'name': categoryValue.text}];
- } else if (categoryValue) {
- result['category_id'] = [categoryValue.id];
+ if(!self.defaultCategoryID){
+ var categoryValue = this.$('#category_id').select2('data');
+ if (categoryValue && categoryValue.create) {
+ result['category_id'] = [0, {'name': categoryValue.text}];
+ } else if (categoryValue) {
+ result['category_id'] = [categoryValue.id];
+ this.categoryID = categoryValue.id;
+ }
+ } else {
+ result['category_id'] = [self.defaultCategoryID];
+ this.categoryID = self.defaultCategoryID;
}
return result;
},
@@ -303,6 +311,25 @@ var SlideUploadDialog = Dialog.extend({
return values;
},
+ _reorderSlidesSequence: function(){
+ var self = this;
+ var slidesElement = $('li.content-slide');
+ var slides = [];
+ for(var i = 0; i < slidesElement.length;i++){
+ slides.push({
+ id: parseInt($(slidesElement[i]).attr('slide_id')),
+ category_id: parseInt($(slidesElement[i]).attr('category_id')),
+ sequence: i
+ })
+ }
+ self._rpc({
+ route: '/slides/resequence_slides',
+ params: {
+ slides_data: slides
+ }
+ }).then(function(){
+ })
+ },
/**
* Init the data relative to the support slide type to upload
*
@@ -325,6 +352,11 @@ var SlideUploadDialog = Dialog.extend({
label: _t('Video'),
template: 'website.slide.upload.modal.video',
},
+ quiz: {
+ icon: 'fa-question-circle',
+ label: _t('Quiz'),
+ template: 'website.slide.upload.quiz'
+ }
};
},
/**
@@ -472,12 +504,11 @@ var SlideUploadDialog = Dialog.extend({
_onChangeSlideUrl: function (ev) {
var self = this;
var url = $(ev.target).val();
-
this._alertRemove();
this.isValidUrl = false;
this.set('can_submit_form', false);
this._fetchUrlPreview(url).then(function (data) {
- this.set('can_submit_form', true);
+ self.set('can_submit_form', true);
if (data.error) {
self._alertDisplay(data.error);
} else {
@@ -508,6 +539,12 @@ var SlideUploadDialog = Dialog.extend({
self.set('state', oldType);
self._alertDisplay(data.error);
} else {
+ //Quick and really dirty fix for reordering issues
+ if(data.channel_type == 'training' && self.categoryID){
+ var categoryElement = $('ul[category_id='+self.categoryID+']');
+ $('<li hidden class="content-slide" slide_id="'+data.slide_id+'" category_id="'+self.categoryID+'">temp</li>').appendTo(categoryElement)
+ self._reorderSlidesSequence();
+ }
window.location = data.url;
}
});
diff --git a/addons/website_slides/static/src/js/website_slides.editor.js b/addons/website_slides/static/src/js/website_slides.editor.js
index 40513e785e2..72a18bb93ee 100644
--- a/addons/website_slides/static/src/js/website_slides.editor.js
+++ b/addons/website_slides/static/src/js/website_slides.editor.js
@@ -5,6 +5,7 @@ var core = require('web.core');
var Dialog = require('web.Dialog');
var QWeb = core.qweb;
var WebsiteNewMenu = require('website.newMenu');
+var wUtils = require('website.utils');
var _t = core._t;
diff --git a/addons/website_slides/static/src/scss/slide_course.scss b/addons/website_slides/static/src/scss/slide_course.scss
new file mode 100644
index 00000000000..38179498415
--- /dev/null
+++ b/addons/website_slides/static/src/scss/slide_course.scss
@@ -0,0 +1,536 @@
+
+.banner{
+ position: absolute;
+ top: 0;
+ left: 0;
+ height: 20%;
+ background-color: $primary;
+ width: 256px;
+ opacity: 0;
+ color: #fff;
+ transition: all .3s ease-out;
+ z-index: 2;
+ visibility: hidden;
+ display: flex;
+ justify-content: space-around;
+ align-items: center;
+ margin-left: 15px;
+}
+
+.banner a {
+ font-size: 1.5rem;
+ text-align: center;
+ padding: 20px;
+}
+
+.banner a:hover{
+ cursor: pointer;
+}
+
+.fill {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ overflow: hidden
+}
+.fill img {
+ flex-shrink: 0;
+ min-width: 100%;
+ min-height: 100%
+}
+
+.main_image{
+ position: relative
+}
+.main_image:hover{
+ .banner{
+ opacity: 1;
+ visibility: visible;
+ }
+
+}
+
+.course-content {
+ padding: 0;
+
+ ul {
+ list-style: none;
+ padding: 0
+ }
+}
+
+.course-section {
+ margin: 0;
+ background-color: #ddd;
+ display: flex;
+ list-style: none;
+ font-size: 1.05rem;
+ border-bottom: 1px solid #ccc
+}
+
+.content-type {
+ width: 30%;
+}
+
+.content-type:hover{
+ cursor: pointer;
+}
+
+.course-progress-bar{
+ height: 10px;
+ width: 80%;
+ box-sizing: border-box;
+ border-radius: 5px;
+ background-color: rgba(0,0,0,.2);
+ overflow: hidden;
+
+ .progress-gauge{
+ height: 100%;
+ width: 0;
+ background-color: $primary;
+
+ transition: all .8s ease-out;
+ }
+}
+
+.course-rating{
+ color:$primary;
+}
+.course-banner{
+ border-bottom: 1px solid $primary;
+}
+
+.content-tab{
+ .active {
+
+ border: none;
+ border-bottom: 1px solid $secondary;
+ }
+}
+
+.content-tab a:hover{
+ border:none;
+ border-bottom: 1px solid $secondary;
+}
+
+.content-container{
+ border:none;
+}
+
+.content-slide:nth-child(odd){
+ background-color: #f6f6f6;
+}
+
+.content-slide:nth-child(even){
+ background-color: #f9f9f9;
+}
+
+.content-slide-controls {
+ i:hover{
+ color: $primary !important;
+ cursor: pointer;
+ }
+}
+
+.viewer-container {
+ display: flex;
+ align-items: stretch;
+ height: 500px;
+ width: 100%;
+ background-color: #ccc;
+ justify-content: flex-start;
+}
+
+
+
+.slide-infos {
+ margin-top: 1rem;
+ display: flex;
+ justify-content: space-between;
+ align-items: start;
+}
+
+.oe_js_player {
+
+}
+
+.slide-deactivated {
+ pointer-events: none;
+}
+
+.slide-draggable {
+ user-select: none;
+ -khtml-user-drag: element;
+ -webkit-user-drag: element;
+}
+
+.slide-hovered{
+ margin-top: 10px;
+}
+
+.section-draggable {
+ user-select: none;
+ -khtml-user-drag: element;
+ -webkit-user-drag: element;
+}
+
+.hold{
+ border: solid #ccc 4px;
+}
+
+
+.fullscreen {
+ position: absolute;
+ top: 0;
+ right: 0;
+ width: 100%;
+ z-index: 1000;
+ height: 100%;
+ margin: 0;
+ padding: 0;
+}
+
+
+.sidebar-list{
+ position: fixed;
+ top: 46px;
+ bottom: 0;
+ left: 0;
+ width: 300px;
+ z-index: 2000;
+}
+
+
+
+.sidebar-list-visible {
+ left: 50px;
+}
+
+.show-list {
+ position: absolute;
+ top: 15%;
+ left: 50px;
+ font-size: 2rem;
+ z-index: 10000;
+ color: #a0a0a0;
+ border: 2px solid #ccc;
+ border-radius: 10px;
+ padding: 5px 10px;
+}
+
+.hover-background {
+ opacity: 0;
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ z-index: 3000;
+}
+
+.hover-background:hover {
+ opacity: 1;
+}
+
+
+.show-list:hover {
+ cursor: pointer;
+}
+
+.change-small-screen {
+ font-size: 1.5rem;
+ z-index: 10000;
+ color: #a0a0a0;
+}
+
+.change-small-screen:hover {
+ cursor: pointer;
+ border-color: $primary;
+ color: $primary;
+}
+
+.sidebar-header {
+ padding: 10px 15px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ font-size: 1.5rem;
+ border-bottom: 1px solid #ccc;
+ height: 80px;
+
+ a {
+ color: black;
+ }
+
+ a:hover {
+ color: $primary;
+ }
+
+ i:hover{
+ color: $primary;
+ }
+}
+
+.close-list:hover {
+ cursor: pointer;
+}
+
+.fullscreen-btn{
+ font-size: 2rem;
+ display: inline-block;
+ a {
+ text-decoration: none;
+ color: $text-muted;
+ }
+}
+
+.fullscreen-btn:hover{
+ cursor: pointer;
+
+ a {
+ color: $primary;
+ }
+}
+
+.sidebar-fullscreen {
+ position: relative;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ height: auto;
+ transform: translateX(-100%);
+ width: 0;
+}
+
+.fullscreen-question-container{
+ background-color: #fff;
+ width: 100%;
+ padding-top: 20px;
+ overflow: auto;
+}
+
+.fullscreen-question{
+ border-radius: 10px;
+ padding: 10px 20px;
+ box-shadow: 0px 1px 4px rgba(0,0,0,.5);
+ margin-top: 50px;
+}
+
+.question-title {
+ font-size: 1.5rem;
+}
+
+.buttons-container{
+ width: 80%;
+ display: flex;
+ justify-content: space-between;
+ flex-direction: row-reverse;
+ margin-top: 40px;
+}
+
+// .slide-resources {
+// border-bottom: 1px solid #ccc;
+// }
+
+.slide-resource {
+ padding: 5px 50px;
+ width: 100%;
+ i{
+ margin-right: 10px;
+ }
+}
+
+.list-cell {
+}
+
+
+.quiz-header{
+ border-bottom: 2px solid #CBCBCB;
+ margin-bottom: 50px;
+}
+
+.quiz-header-container{
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+
+ h1 {
+ width: 80%;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+}
+
+.quiz-good-answer{
+ background-color: #C7EAE9;
+}
+
+.quiz-bad-answer{
+ background-color: #FEBEBE;
+}
+
+.quiz-bad-answer label.radio-box,
+.quiz-good-answer label.radio-box{
+ border: none;
+}
+
+.quiz-bad-answer label.radio-box i{
+ background-color: #FD0606;
+ color: white;
+ height: 100%;
+ width: 100%;
+ font-size: 1.5rem;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ padding: 50px;
+}
+
+.quiz-ignored-answer {
+ color: $text-muted;
+}
+
+
+.quiz-good-answer label.radio-box input:checked + i{
+ height: 100%;
+ width: 100%;
+ background-color: #fff;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ font-size: 2.4rem;
+ border: 10px solid $primary;
+ border-radius: 50px;
+}
+
+.quiz-answer {
+ border-radius: 10px;
+ padding: 5px 10px;
+ transition: all .2s ease-in-out;
+ display: flex;
+ align-items: center;
+ margin-bottom: 5px;
+}
+
+.quiz-answer:not(.quiz-good-answer,.quiz-bad-answer,.quiz-ignored-answer):hover{
+ background-color: #E5E5E5;
+ cursor: pointer;
+}
+
+label.radio-box{
+ border: 2px solid #CBCBCB;
+ background-color: #fff;
+ width: 50px;
+ height: 50px;
+ display: inline-flex;
+ border-radius: 50%;
+ overflow: hidden;
+ justify-content: center;
+ align-items: center;
+ margin: 0;
+ margin-right: 20px;
+ font-size: 1.2rem;
+ outline: none;
+}
+
+.quiz-answer:not(.quiz-good-answer,.quiz-bad-answer,.quiz-ignored-answer) label.radio-box:hover{
+ cursor: pointer;
+}
+
+label.radio-box input{
+ display:none;
+ outline: none;
+ border: none;
+}
+
+
+label.radio-box span{
+ color: inherit;
+ padding: 20px;
+}
+
+label.radio-box input:checked + span{
+ background-color: $primary;
+ color: #fff;
+}
+
+label.radio-box input:checked{
+ background-color: #C7EAE9;
+}
+
+.quiz-question {
+ border-bottom: 2px solid #CBCBCB;
+ padding-bottom: 20px;
+ margin-bottom: 20px;
+}
+
+.quiz-question-answers {
+ list-style: none;
+ margin: 0;
+ margin-top: 20px;
+ padding: 0;
+ font-size: 1rem;
+}
+
+.submit-quiz {
+ margin-top: 20px;
+}
+
+
+.quiz-modal-background{
+ position: absolute;
+ top: 0;
+ left: 0;
+ background-color: rgba(0,0,0,.5);
+ width: 100%;
+ height: 100%;
+}
+
+.quiz-modal-container{
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+.quiz-modal{
+ position: absolute;
+ width: 30%;
+ height: 500px;
+ background-color: #fff;
+ top: 50%;
+ left: 50%;
+ transform: translateX(-50%) translateY(-50%);
+ border-radius: 10px;
+ z-index: 2000;
+}
+.quiz-points-container{
+ font-size: 1.2rem;
+}
+
+.quiz-points {
+ background-color: #C4C4C4;
+ padding: 0 4px ;
+ border-radius: 10px;
+ color: #fff;
+}
+
+
+.quiz-points-won{
+ background-color: $primary;
+}
+
+.webpage-container{
+ background-color: #fff;
+ width: 100%;
+}
+
+.webpage-content {
+ background-color: #fff;
+ height: 100%;
+}
+
+
+
+
diff --git a/addons/website_slides/static/src/scss/slide_slide.scss b/addons/website_slides/static/src/scss/slide_slide.scss
new file mode 100644
index 00000000000..d99aaea101a
--- /dev/null
+++ b/addons/website_slides/static/src/scss/slide_slide.scss
@@ -0,0 +1,156 @@
+.slide-header {
+ background-color: #62495B;
+ height: 200px;
+}
+
+.slide-header-container {
+ width: 1000px;
+ margin: 0 auto;
+ height: 100%;
+}
+
+.slide-header-box{
+ display: flex;
+ height: 100%;
+ width: 50%;
+ flex-direction: column;
+ justify-content: flex-end;
+ padding-bottom: 50px;
+
+ a {
+ color: #fff;
+ font-size: 2.5rem;
+ text-decoration: none !important;
+ }
+}
+
+
+.slide-content-list {
+ position: absolute;
+ top: 140px;
+ left: 80px;
+ background-color: #fff;
+ height: 500px;
+ z-index: 200;
+}
+
+.slide-list{
+ background-color: #fff;
+ list-style: none;
+ height: 100%;
+ padding: 0;
+ overflow: auto;
+ border: 1px solid #ccc;
+ border-left: none;
+ box-sizing: border-box;
+ flex: 1;
+
+ a {
+ text-decoration: none;
+ color: $text-muted;
+ }
+
+ a .active{
+ background-color: #eee !important;
+ }
+}
+
+.slide-content-list-section{
+ border-bottom: 1px solid #fff;
+ color: #9B9B9B;
+ text-transform: uppercase;
+ padding: 10px 15px;
+}
+
+.slide-content-list-slide{
+ color: #666666;
+ padding: 10px 15px;
+}
+
+.slide-list-cell{
+ word-wrap: none;
+ padding: 0.5rem 1rem;;
+ height: 40px;
+ box-sizing: border-box;
+ display: flex;
+ align-items: center;
+ text-decoration: none !important;
+}
+
+.slide-list-cell:hover{
+ background-color: #eee;
+ cursor: pointer;
+}
+
+.slide-list-cell:active{
+ background-color: #eee;
+ cursor: pointer;
+}
+
+
+.slide-content-list-header {
+ padding: 10px 15px;
+ font-size: 1.2rem;
+ background-color: #F7F7F7;
+ color: #6F6F6F;
+}
+
+.slide-button {
+ color: #666;
+ text-decoration: none;
+ border:1px solid #B9B9B9;
+ padding: 5px 10px;
+ width: 50px;
+ text-align: center;
+ text-transform: uppercase;
+ margin: 0;
+ border-radius: 3px;
+}
+
+.slide-button-fullscreen {
+ color: #666;
+ text-decoration: none;
+ border:1px solid #B9B9B9;
+ padding: 5px 10px;
+ text-align: center;
+ text-transform: uppercase;
+ margin: 0;
+ border-radius: 3px;
+}
+
+.slide-button-done {
+ color: #fff;
+ text-transform: uppercase;
+ background-color: $primary;
+ text-decoration: none;
+ padding: 10px 15px;
+ width: 50px;
+ text-align: center;
+ border-radius: 3px;
+ margin: 0;
+}
+
+.slide-title {
+ width: 60%;
+ display: flex;
+ align-items: center;
+
+ h1 {
+ font-size: 1.5rem;
+ }
+
+ .slide-points {
+ margin-left: 20px;
+ padding: 5px 10px;
+ background-color: #DC9D45;
+ border-radius: 10px;
+ color: #fff;
+ font-weight: bold;
+ }
+}
+
+.slide-documentation-sidebar {
+ position: absolute;
+ top: 300px;
+ left: 30px;
+} \ No newline at end of file
diff --git a/addons/website_slides/static/src/scss/slides_slide_fullscreen.scss b/addons/website_slides/static/src/scss/slides_slide_fullscreen.scss
new file mode 100644
index 00000000000..de5493d2f7e
--- /dev/null
+++ b/addons/website_slides/static/src/scss/slides_slide_fullscreen.scss
@@ -0,0 +1,303 @@
+.o_wslides {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ z-index: 2000;
+
+ .o_wslides_slide_fullscreen_header {
+ align-items: center;
+ background-color: #313A44;
+ color: #AEB0B2;
+ display: flex;
+ height: 50px;
+ justify-content: space-between;
+ width: 100%;
+
+ .o_wslides_fullscreen_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_fullscreen_container {
+ display: flex;
+ align-items: stretch;
+ height: 100%;
+ position: relative;
+ background-color: #313A44;
+ }
+
+ .o_wslides_fullscreen_sidebar_hidden{
+ transform: translateX(-100%);
+ }
+
+ .o_wslides_fullscreen_sidebar{
+ width: 400px;
+ min-width: 400px;
+ background-color: #313A44;
+ color: #fff;
+ 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_fullscreen_sidebar_header{
+ height: 100px;
+ padding: 20px 0;
+ display: flex;
+ flex-direction: column;
+
+ .o_wslides_slide_fullscreen_progress_box{
+ display: flex;
+ align-items: center;
+ font-size: .9rem;
+ .o_wslides_fullscreen_sidebar_progressbar{
+ height: 5px;
+ border-radius: 10px;
+ width: 70%;
+ background-color: rgba(0,0,0,.3);
+ margin-right: 40px;
+ overflow: hidden;
+
+ }
+
+ .o_wslides_fullscreen_sidebar_progress_gauge {
+ background-color: $primary;
+ height: 100%;
+ width: 0;
+ transition: all 1.5s ease-in;
+ }
+
+ span {
+ color: #BABDC0;
+ }
+
+ }
+
+ a {
+ font-size: 1.5rem;
+ font-weight: bold;
+ }
+
+ }
+
+ .o_wslides_fullscreen_sidebar_section {
+ margin-bottom: 20px;
+ background-color: #283038;
+ padding: 10px 0;
+
+ }
+
+ .o_wslides_fullscreen_sidebar_section_tab {
+ color: #656B72;
+ font-size: .8rem;
+ text-transform: uppercase;
+ font-weight: bold;
+ padding: 5px 0 5px 15px;
+ }
+
+ .o_wslides_fullscreen_sidebar_section_slides{
+ padding: 0;
+ margin-bottom: 15px;
+
+
+
+ li {
+ margin: 0;
+ padding: 0;
+ border-left: 2px solid transparent;
+ display: flex;
+
+ a {
+ flex: 1;
+ }
+ }
+
+ .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;
+ }
+
+ li:hover,
+ li.active {
+ border-left: 2px solid $primary;
+ cursor: pointer;
+ background-color: #1D2228;
+ }
+
+ .o_wslides_fullscreen_sidebar_slide_tab {
+ padding:5px 0 5px 15px;
+ font-size: .8rem;
+ color: #8A8F93;
+ font-weight: bold;
+ flex: 1;
+
+ }
+
+ .o_wslides_fullscreen_sidebar_slide_tab.active{
+ color: #fff;
+ }
+
+ .o_wslides_fullscreen_slide_tab_line{
+ 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_full_line{
+ width: 1px;
+ height: 100%;
+ background-color: #787D82;
+ }
+ }
+
+ .o_wslides_slide_link {
+ color: #098586;
+ font-size: .8rem;
+ margin-left: 15px;
+ flex: 1;
+
+ i {
+ margin-right: 10px;
+ }
+ }
+
+ .o_wslides_slide_quiz {
+ font-size: .8rem;
+ margin-left: 15px;
+ color: #8A8F93;
+ flex: 1;
+ i {
+ color: #D39B4B;
+ margin-right: 10px;
+ }
+ }
+ }
+
+
+ }
+
+ .o_wslides_fullscreen_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_fullscreen_webpage {
+ background-color: #fff;
+ width: 100%;
+ height: 100%;
+ overflow-y: scroll;
+ padding: 20px 0;
+ }
+
+ .o_wslides_fullscreen_player_no_sidebar {
+ max-width: 100%;
+ width: 100%;
+ }
+
+
+} \ No newline at end of file
diff --git a/addons/website_slides/static/src/scss/website_slides_quiz.scss b/addons/website_slides/static/src/scss/website_slides_quiz.scss
new file mode 100644
index 00000000000..9fe4438ee57
--- /dev/null
+++ b/addons/website_slides/static/src/scss/website_slides_quiz.scss
@@ -0,0 +1,237 @@
+
+ .o_wslides_fullscreen_quiz {
+ background-color: #F6F6F6;
+ width: 100%;
+ height: 100%;
+ overflow: scroll;
+ overflow-x: hidden;
+ padding-bottom: 50px;
+
+ }
+
+ .o_wslides_quiz_question {
+ margin-top: 50px;
+ width: 100%;
+ .o_wslides_quiz_question_title {
+ font-size: 1.5rem;
+ margin-bottom: 20px;
+ .o_wslides_quiz_question_title_number {
+ color: #8C8C8C;
+ font-size: 1.3rem;
+ font-weight: bold;
+ }
+
+ .o_wslides_quiz_question_title_text {
+ margin-left: 2px;
+ color: #2F2F2F;
+ }
+ }
+
+ .o_wslides_quiz_question_answers{
+ background-color: #fff;
+ list-style: none;
+ padding: 0;
+
+ .o_wslides_quiz_answer {
+ padding: 10px 20px;
+ border-bottom: 1px solid #B6B6B6;
+ font-size: 1.2rem;
+ display: flex;
+ align-items: center;
+
+ label.o_wslides_quiz_radio_box{
+ background-color: #f6f6f6;
+ width: 35px;
+ height: 35px;
+ display: inline-flex;
+ border-radius: 50%;
+ overflow: hidden;
+ justify-content: center;
+ align-items: center;
+ margin: 0;
+ margin-right: 20px;
+ font-size: 1.2rem;
+ outline: none;
+ border: none;
+ padding: 0;
+ }
+
+ label.o_wslides_quiz_radio_box input{
+ display:none;
+ outline: none;
+ border: none;
+ }
+
+
+ label.o_wslides_quiz_radio_box span{
+ width: 18px;
+ height: 18px;
+ margin: 0;
+ }
+
+ label.o_wslides_quiz_radio_box input:checked + span{
+ background-color: $primary;
+ border-radius: 50%;
+ overflow: hidden;
+ }
+
+ label.o_wslides_quiz_radio_box input:checked{
+ background-color: #C7EAE9;
+ }
+ }
+
+ .o_wslides_quiz_answer:not(.o_wslides_quiz_good_answer,.o_wslides_quiz_bad_answer,.o_wslides_quiz_ignored_answer):hover {
+ cursor: pointer;
+ }
+
+ .o_wslides_quiz_good_answer{
+ border: 2px solid #28A745;
+ label.o_wslides_quiz_radio_box input:checked + i{
+ height: 100%;
+ width: 100%;
+ background-color: #fff;
+ color: #28A745;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ font-size: 1.4rem;
+ border: 10px solid #28A745;
+ border-radius: 50px;
+ }
+ }
+
+ .o_wslides_quiz_bad_answer{
+ border: 2px solid #A94442;
+
+ label.o_wslides_quiz_radio_box i{
+ background-color: #A94442;
+ color: white;
+ height: 100%;
+ width: 100%;
+ font-size: 1rem;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ }
+ }
+
+
+ .quiz-ignored-answer {
+ color: $text-muted;
+ }
+ }
+ }
+
+ .o_wslides_quiz_modal_background{
+ position: absolute;
+ top: 0;
+ left: 0;
+ background-color: rgba(0,0,0,.2);
+ width: 100%;
+ height: 100%;
+
+ }
+
+ .o_wslides_quiz_success_modal {
+ display: flex;
+ position: absolute;
+ width: 40%;
+ max-width: 600px;
+ height: 450px;
+ background-color: #fff;
+ top: 50%;
+ left: 50%;
+ transform: translateX(-50%) translateY(-50%);
+ z-index: 2000;
+ }
+
+ .o_wslides_quiz_success_modal_left_panel {
+ width: 50%;
+ background-color: #855B79;
+
+ }
+
+ .o_wslides_quiz_success_modal_right_panel{
+ width: 50%;
+ display: flex;
+ flex-direction: column;
+ padding: 10px 20px 10px 50px;
+ position: relative;
+
+ .o_wslides_quiz_success_modal_close {
+ position: absolute;
+ top: 10px;
+ right: 15px;
+ text-transform: uppercase;
+ color: #787878;
+ font-size: 1.2rem;
+ }
+
+ .o_wslides_quiz_success_modal_close:hover{
+ cursor: pointer;
+ }
+
+ h1 {
+ color: #21272B;
+ font-weight: bold;
+ font-size: 2.3rem;
+ padding: 20px 0;
+ letter-spacing: 1px;
+ }
+
+ .o_wslides_quiz_success_progress_bar{
+ height: 10px;
+ width: 100%;
+ background-color: rgba(0,0,0,.1);
+
+ .o_wslides_quiz_success_progress_gauge{
+ width: 75%;
+ height: 100%;
+ background-color: #01ADAB;
+ }
+ }
+
+ .o_wslides_quiz_success_progress_bounds{
+ display: flex;
+ justify-content: space-between;
+ }
+
+ .o_wslides_quiz_success_progress_bounds{
+ font-weight: bold;
+ span:first-child {
+ color: #019F9D;
+ }
+ span:last-child{
+ color: #787878;
+ }
+ }
+
+ .o_wslides_quiz_success_reward {
+ height: 80px;
+ width: 100%;
+ border: 1px dashed #ccc;
+ margin-top: 70px;
+ }
+
+ .o_wslides_quiz_success_button{
+ flex: 1;
+ display: flex;
+ justify-content: flex-end;
+ align-items: flex-end;
+
+ a {
+ color: #787878;
+ text-transform: uppercase;
+ background-color: #fff;
+ border-radius: 3px;
+ border: 1px solid #C4C4C4;
+ font-weight: bold;
+ padding: 5px 15px;
+ text-align: center;
+ }
+
+ a:hover {
+ cursor: pointer;
+ }
+ }
+ } \ No newline at end of file
diff --git a/addons/website_slides/static/src/xml/website_slides_fullscreen.xml b/addons/website_slides/static/src/xml/website_slides_fullscreen.xml
new file mode 100644
index 00000000000..a9aff0d2ec7
--- /dev/null
+++ b/addons/website_slides/static/src/xml/website_slides_fullscreen.xml
@@ -0,0 +1,111 @@
+<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="slide.slide_type in ['presentation', 'document'] &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 style="width:100%;height:100%;" t-if="slide.slide_type == 'infographic' &amp;&amp; !showMiniQuiz">
+ <img t-attf-src="/web/image/slide.slide/#{slide.id}/datas" class="img-fluid" style="width: 100%" alt="Slide image"/>
+ </div>
+ <div t-if="(slide.slide_type == 'quiz' || showMiniQuiz) &amp;&amp; questions " class="o_wslides_fullscreen_quiz">
+ <div>
+ <t t-call="website.slide.quiz"/>
+ <div class="container">
+ <button t-if="!slide.done" class="btn btn-primary submit-quiz" >Check your answers</button>
+ <button t-if="embed_url" class="btn btn-primary back-to-video" >Watch again</button>
+ <button t-if="slide.done &amp;&amp; nextSlide" class="btn btn-primary next-slide" >Continue</button>
+ </div>
+ </div>
+ </div>
+ <div t-if="slide.slide_type == 'webpage'" class="o_wslides_fullscreen_webpage">
+ <div class="o_wslides_fullscreen_webpage_content container"/>
+ </div>
+ <div class="o_wslides_fullscreen_content"/>
+ </t>
+
+ <t t-name="website.slide.quiz">
+ <div class="container quiz-questions">
+ <t t-set="i" t-value="1"/>
+ <t t-foreach="questions" t-as="question">
+ <t t-set="j" t-value="0"/>
+ <div class="o_wslides_quiz_question">
+ <div>
+ <div class="o_wslides_quiz_question_title">
+ <span class="o_wslides_quiz_question_title_number"><span t-esc="i"/>. </span>
+ <span class="o_wslides_quiz_question_title_text" t-esc="question.title"/>
+ </div>
+ <ul class="o_wslides_quiz_question_answers">
+ <t t-foreach="question.answers" t-as="answer">
+ <li t-attf-id="answer#{answer.id}" t-att-question_id="question.id" t-att-class="(slide.done &amp;&amp; answer.correct) ? 'o_wslides_quiz_answer o_wslides_quiz_good_answer': slide.done ? 'o_wslides_quiz_answer o_wslides_quiz_ignored_answer' : 'o_wslides_quiz_answer'">
+ <label class="o_wslides_quiz_radio_box">
+ <input t-att-answer_id="answer.id" type="radio" t-att-name="question.id" t-att-question_id="question.id" t-att-value="answer.id" t-att-checked="(slide.done &amp;&amp; answer.correct) ? 'checked' : undefined" t-att-disabled="slide.done"/>
+ <span t-if="!(slide.done &amp;&amp; answer.correct)"/>
+ <i t-if="(slide.done &amp;&amp; answer.correct)" class="fa fa-check-circle"></i>
+ </label>
+ <span t-esc="answer.text"/>
+ </li>
+ <t t-set="j" t-value="j+1"/>
+ </t>
+ </ul>
+ </div>
+ </div>
+ <t t-set="i" t-value="i+1"/>
+ </t>
+ </div>
+ </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 t-name="website.course.quiz.success">
+ <div class="o_wslides_quiz_modal_background">
+ </div>
+ <div class="o_wslides_quiz_success_modal">
+ <div class="o_wslides_quiz_success_modal_left_panel"></div>
+ <div class="o_wslides_quiz_success_modal_right_panel">
+ <div class="o_wslides_quiz_success_modal_close"><i class="fa fa-times"></i></div>
+ <h1>Amazing!</h1>
+ <div class="o_wslides_quiz_success_xp">
+ <p>You gained <span t-esc="data.points"/> !</p>
+ <div class="o_wslides_quiz_success_progress_bar">
+ <div class="o_wslides_quiz_success_progress_gauge"/>
+ </div>
+ <div class="o_wslides_quiz_success_progress_bounds">
+ <span>600</span>
+ <span>1000</span>
+ </div>
+ </div>
+ <div class="o_wslides_quiz_success_reward"></div>
+ <div class="o_wslides_quiz_success_button">
+ <a class="next-slide">Next &gt;</a>
+ </div>
+ </div>
+ </div>
+ </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_fullscreen_slide_title_span" t-esc="slide.name"/>
+ </t>
+ <t t-else="">
+ <span><i class="fa fa-flag-checkered"></i>Quiz: <span t-esc="slide.name"/></span>
+ </t>
+ </t>
+
+</templates>
diff --git a/addons/website_slides/static/src/xml/website_slides_upload.xml b/addons/website_slides/static/src/xml/website_slides_upload.xml
index 966ff3362cd..d57923c561b 100644
--- a/addons/website_slides/static/src/xml/website_slides_upload.xml
+++ b/addons/website_slides/static/src/xml/website_slides_upload.xml
@@ -47,7 +47,7 @@
<input id="name" name="name" placeholder="Title" class="form-control" required="required"/>
</div>
</div>
- <div class="form-group row">
+ <div t-if="!widget.defaultCategoryID" class="form-group row">
<label for="category_id" class="col-form-label col-md-3">Category</label>
<div class="controls col-md-9">
<input class="form-control" id="category_id"/>
@@ -155,4 +155,107 @@
</div>
</t>
+ <t t-name="website.slide.upload.quiz">
+ <div>
+ <form class="clearfix">
+ <canvas id="data_canvas" class="d-none"></canvas>
+ <t t-call="website.slide.upload.modal.common"/>
+ </form>
+ </div>
+ </t>
+
+ <t t-name="website.slide.image.upload">
+ <div role="dialog" class="modal" tabindex="-1">
+ <div class="modal-dialog">
+ <div class="modal-content">
+ <header class="modal-header">
+ <h3 class="modal-title">Upload Image</h3>
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close">×</button>
+ </header>
+ <div class="oe_slides_upload_loading text-center" style="display:none" aria-hidden="true" role="status">
+ <h4><i class='fa fa-spinner fa-spin'></i> Uploading Image... </h4>
+ </div>
+ <main class="modal-body">
+ <form class="clearfix">
+ <div class="form-group row">
+ <div class="col-md-4">
+ <div class="img-thumbnail">
+ <div class="o_slide_preview">
+ <img src="/website_slides/static/src/img/document.png" id="slide-image" title="Content Preview" alt="Content Preview" class="img-fluid"/>
+ </div>
+ </div>
+ </div>
+ <div class="col-md-8">
+ <ul class="list-group">
+ <li class="list-group-item">
+ <h5 class="list-group-item-heading">
+ <label for="upload" class="col-form-label">Image File</label>
+ </h5>
+ <input id="upload" name="file" class="form-control" accept="image/*,application/pdf" type="file"/>
+ </li>
+ </ul>
+ </div>
+ </div>
+ <canvas id="data_canvas" style="display: none;"></canvas>
+ </form>
+ </main>
+ <footer class="modal-footer">
+ <button type="button" data-loading-text="Loading..." data-published="true" class="btn btn-primary save">Upload</button>
+ <button type="button" class="btn btn-secondary" data-dismiss="modal">Discard</button>
+ </footer>
+ </div>
+ </div>
+ </div>
+ </t>
+
+ <t t-name="website.slide.delete.slide">
+ <div role="dialog" class="modal" tabindex="-1">
+ <div class="modal-dialog">
+ <div class="modal-content">
+ <header class="modal-header">
+ <h3 class="modal-title">Delete Slide</h3>
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close">×</button>
+ </header>
+ <main class="modal-body">
+ <div>
+ <p>Are you sure you want to remove this slide ? </p>
+ </div>
+ </main>
+ <footer class="modal-footer">
+ <button type="button" class="btn btn-primary delete" data-dismiss="modal">Delete</button>
+ <button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
+ </footer>
+ </div>
+ </div>
+ </div>
+ </t>
+
+ <t t-name="website.slide.add.section">
+ <div role="dialog" class="modal" tabindex="-1">
+ <div class="modal-dialog">
+ <div class="modal-content">
+ <header class="modal-header">
+ <h3 class="modal-title">Add a section</h3>
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close">×</button>
+ </header>
+ <main class="modal-body modal-content">
+ <form class="clearfix">
+ <div class="form-group row">
+ <label for="section_name" class="col-form-label col-md-3">Section name</label>
+ <div class="col-md-9">
+ <input type="text" class="form-control" id="section_name"/>
+ </div>
+ </div>
+ <canvas id="data_canvas" style="display: none;"></canvas>
+ </form>
+ </main>
+ <footer class="modal-footer">
+ <button type="button" data-loading-text="Loading..." data-published="true" class="btn btn-primary save">Add</button>
+ <button type="button" class="btn btn-secondary" data-dismiss="modal">Discard</button>
+ </footer>
+ </div>
+ </div>
+ </div>
+ </t>
+
</templates>
diff --git a/addons/website_slides/views/assets.xml b/addons/website_slides/views/assets.xml
index 3602f06aab1..a43729f527d 100644
--- a/addons/website_slides/views/assets.xml
+++ b/addons/website_slides/views/assets.xml
@@ -6,6 +6,10 @@
<xpath expr="//link[last()]" position="after">
<link rel="stylesheet" type="text/scss" href="/website_slides/static/src/scss/website_slides.scss" t-ignore="true"/>
<link rel="stylesheet" type="text/scss" href="/website_slides/static/src/scss/website_slides_profile.scss"/>
+ <link rel="stylesheet" type="text/scss" href="/website_slides/static/src/scss/slide_course.scss" t-ignore="true"/>
+ <link rel="stylesheet" type="text/scss" href="/website_slides/static/src/scss/slide_slide.scss" t-ignore="true"/>
+ <link rel="stylesheet" type="text/scss" href="/website_slides/static/src/scss/slides_slide_fullscreen.scss" t-ignore="true"/>
+ <link rel="stylesheet" type="text/scss" href="/website_slides/static/src/scss/website_slides_quiz.scss"/>
</xpath>
<xpath expr="//script[last()]" position="after">
<script type="text/javascript" src="/website_slides/static/src/js/slides.js"/>
@@ -13,6 +17,13 @@
<script type="text/javascript" src="/website_slides/static/src/js/slides_like.js"/>
<script type="text/javascript" src="/website_slides/static/src/js/slides_share.js"/>
<script type="text/javascript" src="/website_slides/static/src/js/slides_upload.js"/>
+ <script type="text/javascript" src="/website_slides/static/src/js/slides_category.js"/>
+ <script type="text/javascript" src="/website_slides/static/src/js/slides_delete_slide.js"/>
+ <script type="text/javascript" src="/website_slides/static/src/js/slides_course_slides_list.js"/>
+ <script type="text/javascript" src="/website_slides/static/src/js/slides_course_progress_bar.js"/>
+ <script type="text/javascript" src="/website_slides/static/src/js/slides_course_sidebar_list.js"/>
+ <script type="text/javascript" src="/website_slides/static/src/js/slides_course_fullscreen_player.js"/>
+ <script type="text/javascript" src="/website_slides/static/src/js/slides_course_quiz.js"/>
</xpath>
</template>
diff --git a/addons/website_slides/views/slide_channel_views.xml b/addons/website_slides/views/slide_channel_views.xml
index 91607fbace5..4e083dc2650 100644
--- a/addons/website_slides/views/slide_channel_views.xml
+++ b/addons/website_slides/views/slide_channel_views.xml
@@ -135,7 +135,7 @@
</field>
</record>
- <record id="view_slide_channel_tree" model="ir.ui.view">
+ <record id="slide_channel_view_tree" model="ir.ui.view">
<field name="name">slide.channel.tree</field>
<field name="model">slide.channel</field>
<field name="arch" type="xml">
@@ -154,7 +154,8 @@
<field name="name">slide.channel.search</field>
<field name="model">slide.channel</field>
<field name="arch" type="xml">
- <search string="Channel">
+ <search string="Courses">
+ <field name="name" string="Course"/>
<filter string="Archived" name="inactive" domain="[('active','=',False)]"/>
</search>
</field>
@@ -165,6 +166,7 @@
<field name="res_model">slide.channel</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
+ <field name="search_view_id" ref="website_slides.slide_channel_view_search"/>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Create a channel
diff --git a/addons/website_slides/views/slide_slide_views.xml b/addons/website_slides/views/slide_slide_views.xml
index 7e7fcdf92e4..444207483c8 100644
--- a/addons/website_slides/views/slide_slide_views.xml
+++ b/addons/website_slides/views/slide_slide_views.xml
@@ -72,7 +72,7 @@
<group>
<field name="slide_type" readonly="1"/>
<field name="url"
- attrs="{'required': [('slide_type', 'in', ('document', 'video'))], 'readonly': [('slide_type', 'in', ('document', 'video'))], 'invisible': [('slide_type', 'not in', ('document', 'presentation', 'video'))]}" />
+ attrs="{'required': [('slide_type', 'in', ('video'))], 'readonly': [('slide_type', 'in', ('document', 'video'))], 'invisible': [('slide_type', 'not in', ('document', 'presentation', 'video'))]}" />
<field name="document_id" readonly="1" attrs="{'invisible': [('document_id', '=', False)]}"/>
<field name="mime_type" readonly="1"
attrs="{'invisible': [('slide_type', 'not in', ('document', 'presentation', 'infographic', 'webpage'))]}"/>
@@ -108,6 +108,26 @@
</field>
</group>
</page>
+ <page string="Quiz">
+ <group name="container_row_2">
+ <group string="Earnings">
+ <group>
+ <field name="quiz_first_attempt_reward"/>
+ <field name="quiz_second_attempt_reward"/>
+ <field name="quiz_third_attempt_reward"/>
+ <field name="quiz_fourth_attempt_reward"/>
+ </group>
+ </group>
+ <group string="Questions">
+ <field name="question_ids" nolabel="1">
+ <tree>
+ <field name="sequence" widget="handle"/>
+ <field name="question"/>
+ </tree>
+ </field>
+ </group>
+ </group>
+ </page>
</notebook>
</sheet>
<div class="oe_chatter">
@@ -118,6 +138,29 @@
</field>
</record>
+ <record id="view_slide_question_form" model="ir.ui.view">
+ <field name="name">slide.question.form</field>
+ <field name="model">slide.question</field>
+ <field name="arch" type="xml">
+ <form string="Slide">
+ <sheet>
+ <div class="oe_edit_only">
+ <label for="question" string="Question Name"/>
+ </div>
+ <h1>
+ <field name="question" default_focus="1" placeholder="Name"/>
+ </h1>
+ <field name="answer_ids">
+ <tree editable="bottom" create="true" delete="true">
+ <field name="text_value"/>
+ <field name="is_correct"/>
+ </tree>
+ </field>
+ </sheet>
+ </form>
+ </field>
+ </record>
+
<record id="view_slide_slide_tree" model="ir.ui.view">
<field name="name">slide.slide.tree</field>
<field name="model">slide.slide</field>
@@ -169,6 +212,7 @@
<field name="res_model">slide.slide</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form,graph</field>
+ <field name="context">{'search_default_channel': 1}</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Add a new slide
diff --git a/addons/website_slides/views/website_slides_templates.xml b/addons/website_slides/views/website_slides_templates.xml
index e06e75d0b66..7bcf5bd77b7 100644
--- a/addons/website_slides/views/website_slides_templates.xml
+++ b/addons/website_slides/views/website_slides_templates.xml
@@ -108,8 +108,8 @@
<div class="row o_wslides_course_header">
<div class="col-lg-12 o_wslides_container o_wslides_course_header_container">
<div class="row align-items-end">
- <div class="col o_wslides_course_header_aside">
- <img class="mr-3" t-att-src="'/web/image/slide.channel/%s/image' % channel.id"/>
+ <div class="col o_wslides_course_header_aside main_image">
+ <div t-field="channel.image" widget="image" t-options="{'widget': 'image'}"/>
</div>
<div class="col-8 o_wslides_course_header_content">
<h3 class="row"><span class="font-weight-bold col" t-field="channel.name"/></h3>
@@ -254,8 +254,74 @@
</a>
</div>
</div>
+ <!-- training mode ==============================================-->
+ <div t-if="channel.channel_type == 'training'" style="width:70%;" class="d-flex align-items-center ml-4 justify-content-center">
+ <div class="course-content oe_js_course_slides_list" style="width: 100%">
+ <ul>
+ <t t-set="i" t-value="1"/>
+ <t t-set="j" t-value="0"/>
+ <t t-foreach="channel.category_ids" t-as="category">
+ <li t-attf-class="#{'section-draggable' if user == user_id else ''}" t-attf-category_id="#{category.id}">
+ <div t-attf-category_id="#{category.id}" class="course-section text-muted font-weight-bold pt-0 pb-0 pl-2 pr-2 d-flex justify-content-between align-items-center">
+ <div style="width:50%;" class="d-flex align-items-center">
+ <i t-if="channel.user_id == user" class="fa fa-arrows mr-3 text-muted"></i>
+ <div class="mr-2">Section <span class="section-index" t-esc="i"/>:</div>
+ <span t-field="category.name"/>
+ </div>
+ <!-- <a t-if="channel.user_id == user" title="Add content to this section" class="mr-2 p-0 oe_slide_js_add_slide" href="#" t-attf-channel_id="#{channel.id}" t-attf-category_id="#{category.id}">+</a> -->
+ <a class="mr-2 oe_slide_js_upload"
+ role="button"
+ aria-label="Upload Presentation"
+ href="#"
+ style="font-size: 1.5rem;text-decoration: none;"
+ t-att-data-channel-id="channel.id"
+ t-att-data-category-id="category.id"
+ t-att-data-can-upload="channel.can_upload"
+ t-att-data-can-publish="channel.can_publish">+</a>
+ <t t-set="i" t-value="i+1"/>
+ </div>
+ <ul t-attf-category_id="#{category.id}" >
+ <t t-foreach="category.slide_ids" t-as="slide">
+ <li t-att-index="j" t-attf-slide_id="#{slide.id}" t-attf-category_id="#{category.id}" t-attf-class="#{'content-slide slide-draggable d-flex justify-content-between align-items-center p-2' if channel.user_id == user else 'content-slide d-flex justify-content-between align-items-center p-2'}">
+ <div class="content-slide-infos ml-2">
+ <i t-if="channel.user_id == user" class="fa fa-arrows mr-2 text-muted"></i>
+ <i t-if="slide.slide_type == 'document'" class="fa fa-file-pdf-o mr-2 text-muted"></i>
+ <i t-if="slide.slide_type == 'infographic'" class="fa fa-file-picture-o mr-2 text-muted"></i>
+ <i t-if="slide.slide_type == 'video'" class="fa fa-play-circle mr-2 text-muted"></i>
+ <i t-if="slide.slide_type == 'link'" class="fa fa-file-code-o mr-2 text-muted"></i>
+ <i t-if="slide.slide_type == 'webpage'" class="fa fa-file-text mr-2 text-muted"></i>
+ <i t-if="slide.slide_type == 'quiz'" class="fa fa-question-circle mr-2 text-muted"></i>
+ <i t-if="slide.slide_type == 'certification'" class="fa fa-trophy mr-2 text-muted"></i>
+ <a class="link-to-slide" t-attf-href="/slides/slide/#{slug(slide)}"><span t-field="slide.name"/></a>
+ </div>
+ <div class="content-slide-controls mr-2">
+ <i t-if="not slide.id in user_progress or not user_progress[slide.id].completed" class="check-done fa fa-check-circle text-muted mr-1"></i>
+ <i t-if="slide.id in user_progress and user_progress[slide.id].completed" class="check-done text-primary fa fa-check-circle mr-1"></i>
+ <a t-if="channel.user_id == user and not slide.slide_type == 'webpage'" t-attf-href="/web#id=#{slide.id}&amp;action=#{slide_action}&amp;model=slide.slide&amp;view_type=form"><i class="fa fa-pencil text-muted mr-1"></i></a>
+ <a t-if="channel.user_id == user and slide.slide_type == 'webpage'" t-attf-href="/slides/slide/#{slug(slide)}?enable_editor=1"><i class="fa fa-pencil text-muted mr-1"></i></a>
+ <i t-if="channel.user_id == user" t-attf-slide_id="#{slide.id}" class="fa fa-trash text-muted oe_slide_js_delete_slide"></i>
+ </div>
+ </li>
+ <t t-set="j" t-value="j+1"/>
+ </t>
+ </ul>
+ </li>
+ </t>
+ </ul>
+ <div t-if="channel.user_id == user" class="content-actions">
+ <a class="mr-2 oe_slide_js_upload"
+ role="button"
+ aria-label="Upload Presentation"
+ href="#"
+ t-att-data-channel-id="channel.id"
+ t-att-data-can-upload="channel.can_upload"
+ t-att-data-can-publish="channel.can_publish">Add Content</a>
+ <a class="oe_slide_js_add_section" t-attf-channel_id="#{channel.id}" href="#">Add Section</a>
+ </div>
+ </div>
+ </div>
<!-- Channel content, aka slides (lessons in documentation mode) -->
- <div class="col-8 mt24 ml32 o_wslides_channel_content_promoted">
+ <div t-if="channel.channel_type == 'documentation'" class="col-8 mt24 ml32 o_wslides_channel_content_promoted">
<div class="row mt16 align-items-center">
<div class="col">
<h3><i class="fa fa-plus-square"/> Featured lesson</h3>
@@ -284,7 +350,7 @@
</div>
</div>
<!-- Channel content, aka slides (lessons in documentation mode) -->
- <div class="col-12">
+ <div t-if="channel.channel_type == 'documentation'" class="col-12">
<div class="tab-content mt16" id="slideChannelContent">
<div class="tab-pane active" role="tabpanel" aria-labelledby="profile-tab" id="slideChannelContentAbout">
<t t-foreach="category_data" t-as="category">
@@ -363,14 +429,14 @@
<i class="fa fa-thumbs-down fa-1x" role="img" aria-label="Dislikes" title="Dislikes"></i>
<span t-esc="slide.dislikes"/>
</span>
- </div>
+ </div>
<small class="text-muted">
<t t-call="website_slides.slides_misc_float_time">
<t t-set="value" t-value="slide.completion_time"/>
</t>
<!-- <t t-esc="slide.total_views"/> Views
<timeago class="timeago" t-att-datetime="slide.create_date"></timeago> -->
- </small>
+ </small>
</div>
</div>
</div>
@@ -386,14 +452,100 @@
<template id="slide_detail_view" name="Slide Detailed View">
<t t-call="website.layout">
<t t-set="main_object" t-value="slide"/>
- <div class="container mt16">
+ <t t-call="website_slides.course_nav">
+ <t t-set="channel" t-value="slide.channel_id"/>
+ </t>
+ <div class="slide-header">
+ <div class="slide-header-container">
+ <div class="slide-header-box">
+ <a t-attf-href="/slides/#{slug(slide.channel_id)}" t-field="slide.channel_id.name"/>
+ <div t-if="slide.channel_id.channel_type == 'training'" class="d-flex align-items-end">
+ <div class="course-progress-bar">
+ <div class="progress-gauge m-0 oe_slide_js_progress_bar" t-attf-channel_completion="#{slide.channel_id.completion}"/>
+ </div>
+ <i class="fa fa-trophy ml-2 mb-0 p-0" style="font-size:1.8rem; color: #cdcdcd;"></i>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div t-if="slide.channel_id.channel_type == 'training'" class="sidebar-list oe_js_side_bar_list slide-content-list">
+ <ul class="slide-list">
+ <li>
+ <div class="slide-content-list-header">
+ Course content
+ </div>
+ </li>
+ <t t-set="i" t-value="0"/>
+ <t t-foreach="slide.channel_id.category_ids" t-as="category">
+ <a data-toggle="collapse" t-attf-href="#collapse-#{category.id}" role="button" aria-expanded="false" t-attf-aria-controls="collapse-#{category.id}">
+ <li class="slide-content-list-section">
+ <span t-field="category.name"/>
+ </li>
+ </a>
+ <ul class="collapse p-0 m-0" t-attf-id="collapse-#{category.id}" >
+ <t t-foreach="category.slide_ids" t-as="course_slide">
+ <li class="slide-content-list-slide">
+ <a t-attf-href="/slides/slide/#{slug(course_slide)}" t-att-index="i" t-att-slide_id="course_slide.id">
+ <div t-attf-index="#{i}" t-attf-slide_id="#{course_slide.id}" t-attf-class="#{'active' if slide.id == course_slide.id else ''} d-flex justify-content-between">
+ <div>
+ <i t-if="course_slide.slide_type == 'presentation'" class="fa fa-file-pdf-o mr-2 text-muted"></i>
+ <i t-if="course_slide.slide_type == 'infographic'" class="fa fa-file-picture-o mr-2 text-muted"></i>
+ <i t-if="course_slide.slide_type == 'video'" class="fa fa-play-circle mr-2 text-muted"></i>
+ <i t-if="course_slide.slide_type == 'link'" class="fa fa-file-code-o mr-2 text-muted"></i>
+ <i t-if="course_slide.slide_type == 'webpage'" class="fa fa-file-text mr-2 text-muted"></i>
+ <i t-if="course_slide.slide_type == 'quiz'" class="fa fa-question-circle mr-2 text-muted"></i>
+ <t t-esc="course_slide.name"/>
+ </div>
+ <div class="content-slide-controls mr-2">
+ <i t-attf-id="check-#{course_slide.id}" t-if="course_slide.id in user_progress and not user_progress[course_slide.id].completed" class="check-done fa fa-check-circle text-muted mr-1"></i>
+ <i t-attf-id="check-#{course_slide.id}" t-if="course_slide.id in user_progress and user_progress[course_slide.id].completed" class="check-done text-primary fa fa-check-circle mr-1"></i>
+ </div>
+ </div>
+ </a>
+ <ul t-if="course_slide.link_ids" class="list-group slide-resources">
+ <t t-foreach="course_slide.link_ids" t-as="resource">
+ <a t-attf-href="#{resource.link}" target="new">
+ <li><div class="slide-resource"><i class="fa fa-link"></i><span t-field="resource.link"/></div></li>
+ </a>
+ </t>
+ </ul>
+ </li>
+ <t t-set="i" t-value="i+1"/>
+ </t>
+ </ul>
+ </t>
+ </ul>
+ </div>
+ <div class="slide-header-container d-flex justify-content-between mt-4 mb-3">
+ <div class="slide-title">
+ <h1 t-field="slide.name"/>
+ <span t-if="slide.question_ids" t-attf-class="slide-points ml-2 #{'quiz-points-won' if slide.id in user_progress and user_progress[slide.id].completed else ''}">
+ <span t-if="slide.id in user_progress and user_progress[slide.id].quiz_attempts_count == 0" id="quiz-points" t-esc="slide.quiz_first_attempt_reward"/>
+ <span t-if="slide.id in user_progress and user_progress[slide.id].quiz_attempts_count == 1" id="quiz-points" t-esc="slide.quiz_second_attempt_reward"/>
+ <span t-if="slide.id in user_progress and user_progress[slide.id].quiz_attempts_count == 2" id="quiz-points" t-esc="slide.quiz_third_attempt_reward"/>
+ <span t-if="slide.id in user_progress and user_progress[slide.id].quiz_attempts_count > 2" id="quiz-points" t-esc="slide.quiz_fourth_attempt_reward"/>
+ points
+ </span>
+ </div>
+ <div class="course-nav-links">
+ <t t-if="previous_slide">
+ <a class="slide-button" t-attf-href="/slides/slide/#{previous_slide}">Prev</a>
+ </t>
+ <a t-if="slide.slide_type in ('webpage', 'video', 'document', 'iconographic') and not slide.question_ids and (slide.id in user_progress and not user_progress[slide.id].completed)" t-attf-href="/slide/completed/#{slide.id}?next_slide=#{next_slide}&amp;course=#{slug(slide.channel_id)}" class="slide-button-done">Set Done</a>
+ <t t-if="next_slide">
+ <a class="slide-button" t-attf-href="/slides/slide/#{next_slide}">Next</a>
+ </t>
+ <a t-if="slide.channel_id.channel_type == 'training'" t-attf-href="/slides/slide/#{slug(slide)}?fullscreen=1" class="slide-button-fullscreen ml-2"><i class="fa fa-desktop mr-2"></i>fullscreen</a>
+ </div>
+ </div>
+ <div class="slide-header-container mt16">
<div class="row">
- <div class="col-xl-8 col-lg-8 col-md-12 col-12">
- <div class="o_w_slides_slide_detail_container">
+ <div style="width:100%;">
+ <div class="o_w_slides_slide_detail_container" style="width: 100%;">
<t t-if="slide.datas and slide.slide_type == 'infographic'">
<img t-attf-src="/web/image/slide.slide/#{slide.id}/datas" class="img-fluid" style="width:100%" alt="Slide image"/>
</t>
- <div t-if="slide.slide_type in ('presentation', 'document')" class="embed-responsive embed-responsive-4by3 embed-responsive-item mb8">
+ <div style="height: 600px;" t-if="slide.slide_type in ('presentation', 'document')" class="embed-responsive embed-responsive-4by3 embed-responsive-item mb8">
<t t-raw="slide.embed_code"/>
</div>
<div t-if="slide.slide_type == 'video' and slide.document_id" class="embed-responsive embed-responsive-16by9 embed-responsive-item mb8">
@@ -402,8 +554,50 @@
<div t-if="slide.slide_type == 'webpage'" class="border border-light rounded">
<div t-field="slide.html_content"/>
</div>
+ <div t-if="slide.question_ids">
+ <div class=".o_wslides_fullscreen_quiz o_w_slides_quiz_no_fullscreen mt-2"
+ t-attf-slide_id="#{slide.id}"
+ t-attf-slide_done="#{slide.id in user_progress and user_progress[slide.id].completed}"
+ t-attf-nb_attempts="#{user_progress[slide.id].quiz_attempts_count if slide.id in user_progress else ''}"
+ t-attf-first_reward="#{slide.quiz_first_attempt_reward}"
+ t-attf-second_reward="#{slide.quiz_second_attempt_reward}"
+ t-attf-third_reward="#{slide.quiz_third_attempt_reward}"
+ t-attf-fourth_reward="#{slide.quiz_fourth_attempt_reward}"
+ t-if="slide.question_ids">
+ <div>
+ <t t-set="i" t-value="1"/>
+ <t t-foreach="slide.question_ids" t-as="question">
+ <t t-set="j" t-value="0"/>
+ <div class="o_wslides_quiz_question" t-attf-id="#{question.id}" t-attf-title="#{question.question}">
+ <div>
+ <div class="o_wslides_quiz_question_title">
+ <span class="o_wslides_quiz_question_title_number"><span t-esc="i"/>. </span>
+ <span class="o_wslides_quiz_question_title_text" t-esc="question.question"/>
+ </div>
+ <ul class="o_wslides_quiz_question_answers">
+ <t t-foreach="question.answer_ids" t-as="answer">
+ <li t-attf-id="answer#{answer.id}" t-att-question_id="question.id" t-attf-text_value="#{answer.text_value}" t-attf-class="#{'o_wslides_quiz_answer o_wslides_quiz_good_answer' if slide.id in user_progress and user_progress[slide.id].completed and answer.is_correct else 'o_wslides_quiz_answer o_wslides_quiz_ignored_answer' if slide.id in user_progress and user_progress[slide.id].completed else 'o_wslides_quiz_answer'}">
+ <label class="o_wslides_quiz_radio_box">
+ <input t-att-answer_id="answer.id" type="radio" t-att-name="question.id" t-att-question_id="question.id" t-att-value="answer.id" t-att-checked="'checked' if slide.id in user_progress and user_progress[slide.id].completed and answer.is_correct else False" t-att-disabled="slide.id in user_progress and user_progress[slide.id].completed"/>
+ <span t-if="not slide.id in user_progress or not (user_progress[slide.id].completed and answer.is_correct)"/>
+ <i t-if="slide.id in user_progress and user_progress[slide.id].completed and answer.is_correct" class="fa fa-check-circle"></i>
+ </label>
+ <span t-esc="answer.text_value"/>
+ </li>
+ <t t-set="j" t-value="j+1"/>
+ </t>
+ </ul>
+ </div>
+ </div>
+ <t t-set="i" t-value="i+1"/>
+ </t>
+ <button t-if="not slide.id in user_progress or not user_progress[slide.id].completed" class="btn btn-primary submit-quiz" >Check your answers</button>
+ <a t-if="next_slide" t-attf-style="#{'display: none' if not slide.id in user_progress or not user_progress[slide.id].completed else ''}" class="btn btn-primary next-slide" t-attf-href="/slides/slide/#{next_slide}">Continue</a>
+ </div>
+
+ </div>
+ </div>
<div class="row mt-3">
- <h4 t-field="slide.name" class="col-lg-6"/>
<div class="col-lg-6">
<div clas="row">
<div class="col-lg-8" t-if="slide.tag_ids">
@@ -454,6 +648,21 @@
<span class="sr-only"><t t-esc="slide.likes"/> Likes</span>
</div>
</div>
+
+ <div class="mt8">
+ <t t-call="website_rating.rating_stars_static_popup_composer">
+ <t t-set="rating_avg" t-value="rating_avg"/>
+ <t t-set="rating_total" t-value="rating_count"/>
+ <t t-set="object" t-value="slide"/>
+ <t t-set="hash" t-value="message_post_hash"/>
+ <t t-set="pid" t-value="message_post_pid"/>
+ <t t-set="default_message_id" t-value="last_message_id"/>
+ <t t-set="default_message" t-value="last_message"/>
+ <t t-set="default_rating_value" t-value="last_rating_value"/>
+ <t t-set="force_submit_url" t-value="'/slides/mail/update_comment' if last_message_id else False"/>
+ </t>
+ </div>
+
<!-- LikeButton widget -->
<div t-if="slide.channel_id.channel_type == 'documentation' and slide.channel_id.allow_comment" class="text-muted mt4">
<div class="float-right mb16 text-right o_wslides_like">
@@ -494,6 +703,11 @@
<i class="fa fa-comments-o"></i> Comments
</a>
</li>
+ <li t-if="not slide.channel_id.allow_comment" class="nav-item">
+ <a href="#discuss" aria-controls="discuss" t-attf-class="nav-link#{comments and ' active' or ''}" role="tab" data-toggle="tab">
+ <i class="fa fa-star"></i> Ratings
+ </a>
+ </li>
<li class="nav-item">
<a href="#transcript" aria-controls="transcript" class="nav-link" role="tab" data-toggle="tab">
<i class="fa fa-align-justify"></i> Transcript
@@ -532,7 +746,7 @@
The social sharing module will be unlocked when a moderator will allow your publication.
</h4>
</div>
- <div t-if="slide.channel_id.allow_comment" role="tabpanel" t-att-class="comments and 'tab-pane fade in show active' or 'tab-pane fade'" id="discuss">
+ <div role="tabpanel" t-att-class="comments and 'tab-pane fade in show active' or 'tab-pane fade'" id="discuss">
<t t-call="portal.message_thread">
<t t-set="object" t-value="slide"/>
<t t-set="hash" t-value="message_post_hash"/>
@@ -638,7 +852,7 @@
</div>
</div>
</div>
- <div class="col-xl-4 col-lg-4 col-md-12 col-12">
+ <div t-if="slide.channel_id.channel_type == 'documentation'" class="col-xl-4 col-lg-4 col-md-12 col-12">
<ul class="nav nav-tabs" role="tablist">
<li class="nav-item"><a aria-controls="related" href="#related" class="nav-link active" data-toggle="tab">Related</a></li>
<li class="nav-item"><a aria-controls="most_viewed" href="#most_viewed" class="nav-link" data-toggle="tab">Most Viewed</a></li>
@@ -651,7 +865,7 @@
<t t-if="not related_slides_list">
No presentation available.
</t>
- <t t-foreach="related_slides_list" t-as="slide">
+ <t t-else="" t-foreach="related_slides_list" t-as="related_slide">
<t t-call="website_slides.related_slides"/>
</t>
</ul>
@@ -664,7 +878,7 @@
<t t-if="not list(most_viewed_slides_list)">
No presentation available.
</t>
- <t t-foreach="most_viewed_slides_list" t-as="slide">
+ <t t-else="" t-foreach="most_viewed_slides_list" t-as="related_slide">
<t t-call="website_slides.related_slides"/>
</t>
</ul>
@@ -677,16 +891,114 @@
</t>
</template>
+<!-- Slide template for the fullscreen mode -->
+<template id="slide_fullscreen" name="Fullscreen">
+ <t t-call="website.layout">
+ <div class="o_wslides">
+ <div class="o_wslides_slide_fullscreen_header">
+ <div>
+ <a id="fullscreen_sidebar_button" href="#" class="active o_wslides_fullscreen_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_fullscreen_slide_title"/>
+ </div>
+ <div>
+ <a t-attf-href="/slides/">back to courses</a>
+ </div>
+ </div>
+ <div class="o_wslides_fullscreen_container">
+ <div class="oe_js_side_bar_list o_wslides_fullscreen_sidebar">
+ <ul>
+ <li>
+ <div class="o_wslides_fullscreen_sidebar_header">
+ <a t-attf-href="/slides/#{slug(slide.channel_id)}">
+ <span t-field="slide.channel_id.name"/>
+ </a>
+ <div class="o_wslides_slide_fullscreen_progress_box">
+ <div class="o_wslides_fullscreen_sidebar_progressbar">
+ <div class="o_wslides_fullscreen_sidebar_progress_gauge oe_slide_js_progress_bar" t-attf-channel_completion="#{slide.channel_id.completion}"/>
+ </div>
+ <span><span class="o_wslides_progress_percentage" t-esc="slide.channel_id.completion"/>%</span>
+ </div>
+ </div>
+ </li>
+ <t t-set="i" t-value="0"/>
+ <t t-foreach="slide.channel_id.category_ids" t-as="category">
+ <div class="o_wslides_fullscreen_sidebar_section">
+ <a data-toggle="collapse" t-attf-href="#collapse-#{category.id}" role="button" aria-expanded="true" t-attf-aria-controls="collapse-#{category.id}">
+ <li class="o_wslides_fullscreen_sidebar_section_tab">
+ <span t-field="category.name"/>
+ </li>
+ </a>
+ <ul class="o_wslides_fullscreen_sidebar_section_slides collapsed p-0 m-0" t-attf-id="collapse-#{category.id}" >
+ <t t-foreach="category.slide_ids" t-as="course_slide">
+ <li t-att-index="i" t-att-slide_id="course_slide.id" t-attf-class="#{'active' if slide.id == course_slide.id else ''}">
+ <span class="o_wslides_fullscreen_slide_tab_line">
+ <span class="o_wslides_top_line"></span>
+ <i t-attf-id="check-#{course_slide.id}" t-if="not course_slide.id in user_progress or not user_progress[course_slide.id].completed" class="check-done fa fa-circle-thin"></i>
+ <i t-attf-id="check-#{course_slide.id}" t-if="course_slide.id in user_progress and user_progress[course_slide.id].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-slide_id="course_slide.id">
+ <div t-attf-index="#{i}"
+ t-attf-slide_slug="#{slug(course_slide)}"
+ t-attf-done="#{course_slide.id in user_progress and user_progress[course_slide.id].completed}"
+ t-attf-slide_id="#{course_slide.id}"
+ t-attf-slide_name="#{course_slide.name}"
+ t-attf-quiz="#{True if course_slide.question_ids else False}"
+ t-attf-slide_type="#{course_slide.slide_type}"
+ t-attf-slide_embed_code="#{course_slide.embed_code}"
+ t-attf-class="o_wslides_fullscreen_sidebar_slide_tab #{'active' if slide.id == course_slide.id else ''} d-flex justify-content-between">
+ <div>
+ <i t-if="course_slide.slide_type == 'document'" class="fa fa-file-pdf-o mr-2 text-muted"></i>
+ <i t-if="course_slide.slide_type == 'infographic'" class="fa fa-file-picture-o mr-2 text-muted"></i>
+ <i t-if="course_slide.slide_type == 'video'" class="fa fa-play-circle mr-2 text-muted"></i>
+ <i t-if="course_slide.slide_type == 'link'" class="fa fa-file-code-o mr-2 text-muted"></i>
+ <i t-if="course_slide.slide_type == 'webpage'" class="fa fa-file-text mr-2 text-muted"></i>
+ <i t-if="course_slide.slide_type == 'quiz'" class="fa fa-question-circle mr-2 text-muted"></i>
+ <i t-if="course_slide.slide_type == 'certification'" class="fa fa-trophy mr-2 text-muted"></i>
+ <t t-esc="course_slide.name"/>
+ </div>
+ </div>
+ </a>
+ </li>
+ <t t-if="course_slide.link_ids" t-foreach="course_slide.link_ids" t-as="link">
+ <li>
+ <span class="o_wslides_fullscreen_slide_tab_line">
+ <span class="o_wslides_full_line"/>
+ </span>
+ <a class="o_wslides_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 class="o_wslides_fullscreen_slide_tab_line">
+ <span class="o_wslides_full_line"/>
+ </span>
+ <span class="o_wslides_slide_quiz" t-att-index="i"><i class="fa fa-flag-checkered"></i>Quiz: <span t-esc="course_slide.name"/></span>
+ </li>
+ <t t-set="i" t-value="i+1"/>
+ </t>
+ </ul>
+ </div>
+ </t>
+ </ul>
+ </div>
+ <div class="oe_js_course_slide" t-attf-user_id="#{user.id}" t-attf-course_name="#{slide.channel_id.name}" t-attf-course_id="#{slide.channel_id.id}" t-attf-course_slug="#{slug(slide.channel_id)}" t-attf-slide_id="#{slide.id}"/>
+ <div class="o_wslides_fullscreen_player"/>
+ </div>
+ </div>
+ </t>
+</template>
+
<!-- Slide sub-template: display an item in a list of related slides (Related, Most Viewed, ...) -->
<template id="related_slides" name="Related Slide">
<li class="media mt-3">
- <a t-attf-href="/slides/slide/#{slug(slide)}" class="mr-3">
- <img class="oe_slides_apart_small" t-attf-src="/web/image/slide.slide/#{slide.id}/image_small" alt="slide.name"/>
+ <a t-attf-href="/slides/slide/#{slug(related_slide)}" class="mr-3">
+ <img class="oe_slides_apart_small" t-attf-src="/web/image/slide.slide/#{related_slide.id}/image_small" alt="related_slide.name"/>
</a>
<div class="media-body">
- <a t-attf-href="/slides/slide/#{slug(slide)}"><h6 t-esc="slide.name" class="mb-1"/></a>
+ <a t-attf-href="/slides/slide/#{slug(related_slide)}"><h6 t-esc="related_slide.name" class="mb-1"/></a>
<small class="text-muted">
- <t t-esc="slide.total_views"/> Views . <timeago class="timeago" t-att-datetime="slide.create_date"></timeago>
+ <t t-esc="related_slide.total_views"/> Views . <timeago class="timeago" t-att-datetime="related_slide.create_date"></timeago>
</small>
</div>
</li>
diff --git a/addons/website_slides_forum/controllers/main.py b/addons/website_slides_forum/controllers/main.py
index 6f898b90fc4..052df5f4de2 100644
--- a/addons/website_slides_forum/controllers/main.py
+++ b/addons/website_slides_forum/controllers/main.py
@@ -9,14 +9,15 @@ class WebsiteSlidesForum(WebsiteSlides):
def _slide_channel_prepare_values(self, **kwargs):
communication_type = kwargs.get('communication_type')
+ channel = super(WebsiteSlidesForum, self)._slide_channel_prepare_values(**kwargs)
if communication_type:
if communication_type == 'forum':
forum = request.env['forum.forum'].create({
'name': kwargs.get('name')
})
- kwargs['forum_id'] = forum.id
- kwargs['allow_comment'] = communication_type == 'comment'
- return super(WebsiteSlidesForum, self)._slide_channel_prepare_values(**kwargs)
+ channel['forum_id'] = forum.id
+ channel['allow_comment'] = communication_type == 'comment'
+ return channel
# Profile
# ---------------------------------------------------
diff --git a/addons/website_slides_forum/views/website_slides_templates.xml b/addons/website_slides_forum/views/website_slides_templates.xml
index 339f4d526d7..0acb40bc241 100644
--- a/addons/website_slides_forum/views/website_slides_templates.xml
+++ b/addons/website_slides_forum/views/website_slides_templates.xml
@@ -1,14 +1,21 @@
<?xml version="1.0" ?>
<odoo><data>
+ <template id='course_main' inherit_id="website_slides.course_main">
+ <!-- Channel main template: add link to forum -->
+ <xpath expr="//li[hasclass('o_wslides_nav_tabs_item_home')]" position="after">
+ <li class="nav-item" t-if="channel.forum_id">
+ <a t-att-href="'/forum/%s' % (slug(channel.forum_id))"
+ t-att-class="'nav-link o_wslides_navlink'" target="new">Forum</a>
+ </li>
+ </xpath>
+ </template>
+
+ <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>
+ </template>
-<template id='course_main' inherit_id="website_slides.course_main">
- <!-- Channel main template: add link to forum -->
- <xpath expr="//li[hasclass('o_wslides_nav_tabs_item_home')]" position="after">
- <li class="nav-item" t-if="channel.forum_id">
- <a t-att-href="'/forum/%s' % (slug(channel))"
- t-att-class="'nav-link o_wslides_navlink'">Forum</a>
- </li>
- </xpath>
-</template>
</data></odoo>
diff --git a/addons/website_slides_survey/static/src/js/slides_course_fullscreen_player.js b/addons/website_slides_survey/static/src/js/slides_course_fullscreen_player.js
new file mode 100644
index 00000000000..9fc8ffe3407
--- /dev/null
+++ b/addons/website_slides_survey/static/src/js/slides_course_fullscreen_player.js
@@ -0,0 +1,15 @@
+odoo.define('website_slides_survey.fullscreen', function (require) {
+ "use strict";
+
+ var core = require('web.core');
+ var _t = core._t;
+ var Fullscreen = require('website_slides.fullscreen');
+
+ Fullscreen.include({
+ xmlDependencies: (Fullscreen.prototype.xmlDependencies || []).concat(
+ ["/website_slides_survey/static/src/xml/website_slides_fullscreen.xml"]
+ ),
+ });
+
+ });
+ \ No newline at end of file
diff --git a/addons/website_slides_survey/static/src/scss/website_slides.scss b/addons/website_slides_survey/static/src/scss/website_slides.scss
new file mode 100644
index 00000000000..fd1506e345b
--- /dev/null
+++ b/addons/website_slides_survey/static/src/scss/website_slides.scss
@@ -0,0 +1,12 @@
+.o_wslides_fullscreen_certification {
+ background-color: #f6f6f6;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ width: 400px;
+ height: 300px;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translateX(-50%) translateY(-50%);
+} \ No newline at end of file
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
new file mode 100644
index 00000000000..f6171db9a13
--- /dev/null
+++ b/addons/website_slides_survey/static/src/xml/website_slides_fullscreen.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<templates>
+ <t t-extend="website.slides.fullscreen">
+ <t t-jquery=".o_wslides_fullscreen_content" t-operation="append">
+ <div class="o_wslides_fullscreen_certification" t-if="slide.slide_type == 'certification'">
+ <a t-att-href="'/slides/slide/' + slide.slug" target="new">Pass Certification</a>
+ </div>
+ </t>
+ </t>
+
+ <t t-extend="website.course.fullscreen.title">
+ <t t-jquery=".o_wslides_fullscreen_slide_title_span" t-operation="before">
+ <i t-if="slide.slide_type == 'certification'" class="fa fa-trophy mr-2 text"></i>
+ </t>
+ </t>
+</templates>
diff --git a/addons/website_slides_survey/views/assets.xml b/addons/website_slides_survey/views/assets.xml
index 2ad6ba7f43a..7f239d7a1c9 100644
--- a/addons/website_slides_survey/views/assets.xml
+++ b/addons/website_slides_survey/views/assets.xml
@@ -2,10 +2,14 @@
<odoo>
<data>
<template id="assets_frontend" inherit_id="website.assets_frontend" name="Slides Certification">
+ <xpath expr="//link[last()]" position="after">
+ <link rel="stylesheet" type="text/scss" href="/website_slides_survey/static/src/scss/website_slides.scss" t-ignore="true"/>
+ </xpath>
<xpath expr="//script[last()]" position="after">
<script type="text/javascript" src="/website_slides_survey/static/src/js/slides_upload.js"/>
<script type="text/javascript" src="/website_slides_survey/static/src/js/slides_certification_download.js"/>
<link rel="stylesheet" type="text/scss" href="/website_slides_survey/static/src/scss/website_profile.scss"/>
+ <script type="text/javascript" src="/website_slides_survey/static/src/js/slides_course_fullscreen_player.js"/>
</xpath>
</template>
</data>