[FW][MERGE] (website_)event: improve Online features display and use

PURPOSE

Add a few minor usability features as well as a few layout adjustments for the
'event' modules.

SPECIFICATIONS

Add "Hide and show" feature for the tracks.

Add a dynamic countdown for the tracks.

Provide various changes and improvements on frontend layout display
next to intensive user testing and feedback.

See underlying commits for more details.

LINKS

Task-2347597
COM PR odoo/odoo#69102
ENT PR odoo/enterprise#17612

closes odoo/odoo#69117

Forward-port-of: odoo/odoo#69102
Related: odoo/enterprise#17621
Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
This commit is contained in:
Odoo's Mergebot
2021-04-12 14:05:18 +02:00
committed by GitHub
29 changed files with 411 additions and 211 deletions
-11
View File
@@ -480,17 +480,6 @@ class EventEvent(models.Model):
if event.date_end < event.date_begin:
raise ValidationError(_('The closing date cannot be earlier than the beginning date.'))
@api.depends('name', 'date_begin', 'date_end')
def name_get(self):
result = []
for event in self:
date_begin = fields.Datetime.from_string(event.date_begin)
date_end = fields.Datetime.from_string(event.date_end)
dates = [fields.Date.to_string(fields.Datetime.context_timestamp(event, dt)) for dt in [date_begin, date_end] if dt]
dates = sorted(set(dates))
result.append((event.id, '%s (%s)' % (event.name, ' - '.join(dates))))
return result
@api.model
def _read_group_stage_ids(self, stages, domain, order):
return self.env['event.stage'].search([])
@@ -72,6 +72,8 @@
.dropdown-menu {
margin-top: $navbar-padding-y;
min-width: 12rem;
max-height: 250px;
overflow-y: auto;
}
.dropdown-item {
&.active .badge { // Invert badge display when the item is active
@@ -137,13 +139,6 @@
}
.o_wevent_online_page_container {
// Widescreen mode
@include media-breakpoint-up(md) {
&:not(.container) {
padding-left: 0.5rem;
padding-right: 0.5rem;
}
}
// Main panel
.o_wevent_online_page_main {
@@ -163,10 +163,10 @@
<!-- List -->
<div t-foreach="event_ids" t-as="event" t-attf-class=" #{opt_event_size} mb-4">
<a t-attf-href="/event/#{ slug(event) }/#{(not event.menu_id) and 'register'}" class="text-decoration-none">
<article t-attf-class="h-100 #{opt_events_list_cards and 'card border-0 shadow-sm'}" itemscope="itemscope" itemtype="http://schema.org/Event">
<article t-attf-class="h-100 #{opt_events_list_cards and 'card rounded-0 border-0 shadow-sm'}" itemscope="itemscope" itemtype="http://schema.org/Event">
<div class="h-100 row no-gutters">
<!-- Header -->
<header t-attf-class="overflow-hidden bg-secondary #{opt_events_list_columns and 'col-12 rounded-top' or 'col-sm-4 col-lg-3 rounded-left'} #{(not opt_events_list_cards) and 'rounded shadow'} #{(not opt_events_list_cards and not opt_events_list_columns) and 'rounded-top'}">
<header t-attf-class="overflow-hidden bg-secondary #{opt_events_list_columns and 'col-12' or 'col-sm-4 col-lg-3'} #{(not opt_events_list_cards) and 'shadow'}">
<!-- Image + Link -->
<div class="d-block h-100 w-100">
<t t-call="website.record_cover">
@@ -39,7 +39,7 @@
<t t-else="">
<nav class="navbar navbar-light border-top shadow-sm navbar-expand-md">
<div class="container align-items-baseline">
<a href="#" t-field="event.name" class="navbar-brand h4 my-0 mr-0 mr-md-4"/>
<a t-att-href="event.website_url" t-field="event.name" class="navbar-brand h4 my-0 mr-0 mr-md-4"/>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#o_wevent_event_submenu" aria-controls="o_wevent_event_submenu" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
@@ -78,4 +78,13 @@
</div>
</template>
<template id="display_timer_alert_widget" name="Display Countdown widget">
<div class="o_we_track_timer alert alert-warning alert-dismissible fade show d-none mx-3 mt-3 mb-0" role="alert" t-att-data-time-to-live="time_to_live">
Starts <span />
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<i class="fa fa-times"/>
</button>
</div>
</template>
</odoo>
@@ -150,11 +150,11 @@
<!-- ============================================================ -->
<template id="exhibitor_card" name="Exhibitor Card">
<article t-att-class="'h-100 card border-0 shadow-sm o_wesponsor_card %s' % ('o_wesponsor_card_unpublished' if not sponsor.is_published else '')"
<article t-att-class="'h-100 card rounded-0 border-0 shadow-sm o_wesponsor_card %s' % ('o_wesponsor_card_unpublished' if not sponsor.is_published else '')"
itemscope="itemscope" itemtype="http://schema.org/Event">
<div class="h-100 row no-gutters">
<t t-set="sponsor_image_url" t-value="sponsor.website_image_url"/>
<header t-att-class="'overflow-hidden col-12 rounded-top %s' % ('bg-secondary' if not sponsor_image_url else '')">
<header t-att-class="'overflow-hidden col-12 %s' % ('bg-secondary' if not sponsor_image_url else '')">
<div class="d-block h-100 w-100 o_wesponsor_js_connect"
t-att-data-sponsor-url="sponsor.website_url"
t-attf-data-register-url="/event/#{slug(event)}/register?from_sponsor_id=#{sponsor.id}"
@@ -202,8 +202,8 @@
<div class="col-12 h-100">
<main class="card-body">
<!-- Title -->
<h5 class="card-title mt-0 mb-0 text-truncate d-flex align-items-top">
<span t-field="sponsor.name" itemprop="name"/>
<h5 class="card-title mt-0 mb-0 d-flex align-items-start">
<span t-field="sponsor.name" itemprop="name" class="text-break"/>
<span t-if="sponsor.is_in_opening_hours and sponsor.chat_room_id"
class="ml-auto badge badge-danger">Live
</span>
@@ -89,10 +89,10 @@
</t>
</div>
<!-- SPONSOR DESCRIPTION -->
<span class="h5 d-inline-block mx-3 mt-3 mb-0">
<div class="h5 m-3">
About <t t-esc="sponsor.name"/>
</span>
<hr class="mt-2 mb-0"/>
</div>
<hr class="my-0"/>
<div class="ml-3 clearfix">
<div class="float-left pt-3">
<span t-if="sponsor.image_512" t-field="sponsor.image_512" class="o_wevent_online_page_avatar"
@@ -149,7 +149,7 @@
<!-- ============================================================ -->
<template id="exhibitor_aside" name="Exhibitor: Aside">
<div t-att-class="'col-12 pl-0 pr-0 pr-md-3 o_wevent_online_page_aside o_wesponsor_exhibitor_aside %s' % ('col-md-3 col-lg-2' if option_widescreen else 'col-md-4 col-lg-3')">
<div t-att-class="'col-12 pl-0 pr-0 o_wevent_online_page_aside o_wesponsor_exhibitor_aside %s' % ('col-md-3 col-lg-2' if option_widescreen else 'col-md-4 col-lg-3')">
<div class="bg-white o_wevent_online_page_aside_content">
<div class="d-flex align-items-center justify-content-between mx-2">
<span class="h5 mb-0 pt-0 pt-md-3 pb-0 pb-md-2">Other exhibitors</span>
@@ -162,9 +162,11 @@
<templates>
<t t-name="kanban-box">
<div class="oe_kanban_global_click overflow-hidden">
<div class="row my-auto">
<img t-att-src="kanban_image('event.sponsor', 'image_128', record.id.raw_value)" alt="Sponsor image" class="col-4"/>
<div class="col-8 d-flex flex-column">
<div class="d-flex align-items-center">
<div class="w-25 d-flex justify-content-center">
<img t-att-src="kanban_image('event.sponsor', 'image_128', record.id.raw_value)" class="img-fluid" alt="Sponsor image"/>
</div>
<div class="w-75 d-flex flex-column ml-3">
<h1 class="o_kanban_record_title"><field name="name"/></h1>
<strong>Level: <field name="sponsor_type_id"/></strong>
<span class="text-muted"><field name="exhibitor_type"/></span>
@@ -135,10 +135,10 @@ class WebsiteEventMeetController(EventCommunityController):
)
def _event_meeting_room_page_get_values(self, event, meeting_room):
# search for meeting room list
# search for meeting room list. Set a limit to 6 because it is better than 5 or 7
meeting_rooms_other = request.env['event.meeting.room'].sudo().search([
('event_id', '=', event.id), ('id', '!=', meeting_room.id), ('is_published', '=', True),
])
], limit=6)
if not request.env.user.has_group("event.group_event_manager"):
# only the event manager can see meeting rooms which are full
@@ -114,7 +114,7 @@
t-att-data-meeting-room-id="meeting_room.id"
t-att-data-open-room="opened"
t-att-data-is-event-manager="int(is_event_user)"
t-attf-class="card o_wevent_meeting_room_card w-100 my-2 bg-light d-block text-decoration-none"
t-attf-class="card o_wevent_meeting_room_card w-100 my-2 bg-light d-block text-decoration-none rounded-0"
t-att-href="meeting_room_href"
t-att-data-toggle="meeting_room_data_toggle"
t-att-data-target="meeting_room_data_target">
@@ -81,11 +81,12 @@
</div>
<!-- ROOM DESCRIPTION -->
<div class="mx-3">
<span class="h5 d-inline-block mt-3 mb-1" t-esc="meeting_room.name"/>
<br/>
<span class="text-muted">A chat among <span class="font-weight-bold" t-esc="meeting_room.target_audience"/></span>
<div class="h5 my-3" t-esc="meeting_room.name"/>
<div t-if="meeting_room.room_participant_count > 0" class="text-muted mb-3">
A chat among <t t-esc="meeting_room.room_participant_count"/> <span class="font-weight-bold" t-esc="meeting_room.target_audience"/>
</div>
</div>
<hr class="mt-2 mb-0"/>
<hr class="my-0"/>
<div t-field="meeting_room.summary" class="my-2 mx-3 oe_no_empty"/>
</div>
</template>
@@ -95,9 +96,9 @@
<!-- ============================================================ -->
<template id="meeting_room_aside" name="Meeting Room: Aside">
<div t-att-class="'col-12 pl-0 pr-0 pr-md-3 o_wevent_online_page_aside %s' % ('col-md-3 col-lg-2' if option_widescreen else 'col-md-4 col-lg-3')">
<div t-att-class="'col-12 pl-0 pr-0 o_wevent_online_page_aside %s' % ('col-md-3 col-lg-2' if option_widescreen else 'col-md-4 col-lg-3')">
<div class="bg-white o_wevent_online_page_aside_content">
<div class="d-flex align-items-center justify-content-between mx-2 py-1">
<div class="d-flex align-items-center justify-content-between mx-2">
<span class="h5 mb-0 pt-0 pt-md-3 pb-0 pb-md-2">Other Rooms</span>
<a href="#collapse_meet_room_aside" data-toggle="collapse" class="d-lg-none p-2 text-decoration-none o_wevent_online_page_aside_collapse collapsed">
<i class="fa fa-chevron-down d-lg-none"/>
@@ -51,6 +51,7 @@
'website_event_track/static/src/js/website_event_track_proposal_form.js',
'website_event_track/static/src/js/website_event_track_proposal_form_tags.js',
'website_event_track/static/src/js/event_track_reminder.js',
'website_event_track/static/src/js/event_track_timer.js',
'website_event_track/static/src/js/website_event_pwa_widget.js',
'website_event_track/static/lib/idb-keyval/idb-keyval.js',
],
@@ -121,6 +121,13 @@ class EventTrackController(http.Controller):
tracks_announced = tracks_announced.sorted('wishlisted_by_default', reverse=True)
tracks_by_day.append({'date': False, 'name': _('Coming soon'), 'tracks': tracks_announced})
for tracks_group in tracks_by_day:
# the tracks group is folded if all tracks are done (and if it's not "today")
tracks_group['default_collapsed'] = (today_tz != tracks_group['date']) and all(
track.is_track_done and not track.is_track_live
for track in tracks_group['tracks']
)
# return rendering values
return {
# event information
@@ -353,11 +360,11 @@ class EventTrackController(http.Controller):
@http.route("/event/track/toggle_reminder", type="json", auth="public", website=True)
def track_reminder_toggle(self, track_id, set_reminder_on):
""" Set a reminder a track for current visitor. Track visitor is created or updated
if it already exists. Exception made if un-wishlisting and no track_visitor
if it already exists. Exception made if un-favoriting and no track_visitor
record found (should not happen unless manually done).
:param boolean set_reminder_on:
If True, set as a wishlist, otherwise un-wishlist track;
If True, set as a favorite, otherwise un-favorite track;
If the track is a Key Track (wishlisted_by_default):
if set_reminder_on = False, blacklist the track_partner
otherwise, un-blacklist the track_partner
@@ -74,8 +74,11 @@ class Track(models.Model):
partner_company_name = fields.Char(
'Company Name', compute='_compute_partner_company_name',
readonly=False, store=True)
partner_tag_line = fields.Char(
'Tag Line', compute='_compute_partner_tag_line',
help='Description of the partner (name, function and company name)')
image = fields.Image(
string="Speaker Photo", compute="_compute_speaker_image",
string="Speaker Photo", compute="_compute_partner_image",
readonly=False, store=True,
max_width=256, max_height=256)
# contact information
@@ -91,7 +94,7 @@ class Track(models.Model):
# time information
date = fields.Datetime('Track Date')
date_end = fields.Datetime('Track End Date', compute='_compute_end_date', store=True)
duration = fields.Float('Duration', default=1.5, help="Track duration in hours.")
duration = fields.Float('Duration', default=0.5, help="Track duration in hours.")
is_track_live = fields.Boolean(
'Is Track Live', compute='_compute_track_time_data',
help="Track has started and is ongoing")
@@ -134,7 +137,7 @@ class Track(models.Model):
groups="event.group_event_user")
wishlisted_by_default = fields.Boolean(
string='Always Wishlisted',
help="""If set, the talk will be starred for each attendee registered to the event. The attendee won't be able to un-star this talk.""")
help="""If set, the talk will be set as favorite for each attendee registered to the event.""")
# Call to action
website_cta = fields.Boolean('Magic Button',
help="Display a Call to Action button to your Attendees while they watch your Track.")
@@ -199,8 +202,32 @@ class Track(models.Model):
elif not track.partner_company_name:
track.partner_company_name = track.partner_id.parent_id.name
@api.depends('partner_name', 'partner_function', 'partner_company_name')
def _compute_partner_tag_line(self):
for track in self:
if not track.partner_name:
track.partner_tag_line = False
continue
tag_line = track.partner_name
if track.partner_function:
if track.partner_company_name:
tag_line = _('%(name)s, %(function)s at %(company)s',
name=track.partner_name,
function=track.partner_function,
company=track.partner_company_name
)
else:
tag_line = '%s, %s' % (track.partner_name, track.partner_function)
elif track.partner_company_name:
tag_line = _('%(name)s from %(company)s',
name=tag_line,
company=track.partner_company_name
)
track.partner_tag_line = tag_line
@api.depends('partner_id')
def _compute_speaker_image(self):
def _compute_partner_image(self):
for track in self:
if not track.image:
track.image = track.partner_id.image_256
@@ -21,7 +21,7 @@ class TrackVisitor(models.Model):
'event.track', string='Track',
index=True, required=True, ondelete='cascade')
is_wishlisted = fields.Boolean(string="Is Wishlisted")
is_blacklisted = fields.Boolean(string="Is reminder off", help="As key track cannot be un-wishlisted, this field store the partner choice to remove the reminder for key tracks.")
is_blacklisted = fields.Boolean(string="Is reminder off", help="As key track cannot be un-favorited, this field store the partner choice to remove the reminder for key tracks.")
@api.depends('visitor_id')
def _compute_partner_id(self):
@@ -63,6 +63,13 @@ publicWidget.registry.websiteEventTrackReminder = publicWidget.Widget.extend({
type: 'info',
title: message
});
if (self.reminderOn) {
core.bus.trigger('open_notification_request', 'add_track_to_favorite', {
title: _t('Allow push notifications?'),
body: _t('You have to enable push notifications to get reminders for your favorite tracks.'),
delay: 0
});
}
}
if (result.visitor_uuid) {
utils.set_cookie('visitor_uuid', result.visitor_uuid);
@@ -0,0 +1,87 @@
odoo.define('website_event_track.website_event_track_timer', function (require) {
'use strict';
const publicWidget = require('web.public.widget');
/*
* Simple implementation of a timer widget that uses a "time to live" configuration
* value to countdown seconds on a target element.
* Will be used to visually countdown the time before a talk starts.
* When the timer reaches 0, the element destroys itself.
*/
publicWidget.registry.websiteEventTrackTimer = publicWidget.Widget.extend({
selector: '.o_we_track_timer',
events: {
'click .close': '_onCloseClick',
},
/**
* @override
*/
start: function () {
return this._super.apply(this, arguments).then(() => {
let timeToLive = this.$el.data('time-to-live');
let deadline = moment().add(timeToLive, 'seconds');
let remainingMs = deadline.diff(moment());
if (remainingMs > 0) {
this._updateTimerDisplay(remainingMs);
this.$el.removeClass('d-none');
this.deadline = deadline;
this.timer = setInterval(this._refreshTimer.bind(this), 1000);
} else {
this.destroy();
}
});
},
/**
* @override
*/
destroy: function() {
this.$el.parent().remove();
clearInterval(this.timer);
this._super(...arguments);
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
_onCloseClick: function () {
this.destroy();
},
/**
* The function will trigger an update if the timer has not yet expired.
* Otherwise, the component will be destroyed.
*/
_refreshTimer: function () {
let remainingMs = this.deadline.diff(moment());
if (remainingMs > 0) {
this._updateTimerDisplay(remainingMs);
} else {
this.destroy();
}
},
/**
* The function will have the responsibility to update the text indicating
* the time remaining before the counter expires. The function will use
* MomentJS to transform the remaining time in more a human friendly format
* Example: "in 32 minutes", "in 17 hours", etc.
* @param {integer} remainingMs - Time remaining before the counter expires (in ms).
*/
_updateTimerDisplay: function (remainingMs) {
let $timerTextEl = this.$el.find('span');
let str = moment.duration(remainingMs, 'ms').humanize(true);
if (str !== $timerTextEl.text()) {
$timerTextEl.text(str);
}
},
});
return publicWidget.registry.websiteEventTrackTimer;
});
@@ -81,12 +81,12 @@
}
div.o_we_agenda_card_content {
height: 100%;
span {
cursor: pointer;
}
.o_we_agenda_card_title, small {
word-break: break-word;
}
.badge:hover {
cursor: pointer;
}
}
.badge {
height: fit-content;
@@ -180,6 +180,13 @@
/*
* EVENT TOOL: DATE
*/
.o_wevent_event .o_we_track_day_header > div > span.h1 {
font-size: xx-large;
.o_wevent_event {
.o_we_track_day_header {
& > div > span.h1 {
font-size: xx-large;
}
a.collapsed {
transform: rotate(-90deg);
}
}
}
@@ -11,8 +11,7 @@
<button name="%(action_event_track_from_event)d"
type="action"
class="oe_stat_button"
icon="fa-microphone"
context="{'default_date': date_begin}">
icon="fa-microphone">
<field name="track_count" string="Tracks" widget="statinfo"/>
</button>
</xpath>
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Add a shortcut to wishlist / talks after registration -->
<!-- Add a shortcut to favorites / tracks after registration -->
<template id="registration_complete" inherit_id="website_event.registration_complete">
<xpath expr="//div[hasclass('row')][last()]" position="after">
<div t-if="event.website_track" class="row mt-5 mb256">
@@ -43,7 +43,7 @@
</nav>
</template>
<!-- Option: Tracks display: optional wishlist -->
<!-- Option: Tracks display: optional favorites -->
<template id="agenda_topbar_wishlist"
inherit_id="website_event_track.agenda_topbar"
name="Allow Wishlists"
@@ -157,13 +157,13 @@
<div class="d-flex flex-column h-100">
<div class="d-flex justify-content-end flex-wrap-reverse align-items-center o_weagenda_track_badges">
<small t-if="track.is_track_live and not track.is_track_done and track.website_published"
class="mx-1 badge badge-danger">Live
class="mx-1 badge badge-danger rounded-sm">Live
</small>
<small t-if="not track.website_published and is_event_user and track.stage_id.is_accepted"
title="Unpublished"
class="ml-1 badge badge-danger o_wevent_online_badge_unpublished">Unpublished</small>
class="ml-1 badge badge-danger o_wevent_online_badge_unpublished rounded-sm">Unpublished</small>
<small t-if="not track.stage_id.is_accepted" title="Not Accepted"
class="ml-1 badge badge-danger o_wevent_online_badge_unpublished">Not Accepted</small>
class="ml-1 badge badge-danger o_wevent_online_badge_unpublished rounded-sm">Not Accepted</small>
<span t-if="option_track_wishlist">
<t t-call="website_event_track.track_widget_reminder">
<t t-set="reminder_light" t-value="True"/>
@@ -174,14 +174,20 @@
</div>
<div class="o_we_agenda_card_content d-flex flex-column justify-content-center my-1">
<div t-att-class="'text-black' if track.website_published or is_event_user else 'text-muted'"
t-att-onclick="'window.location=\'/event/%s/track/%s\'' % (slug(event), slug(track))
if track.website_published or is_event_user else ''">
<span class="o_we_agenda_card_title text-bold" t-esc="track.name"/>
<div class="o_we_agenda_card_title">
<t t-if="track.website_published or is_event_user">
<a t-att-href="'/event/%s/track/%s' % (slug(event), slug(track))" class="text-black text-bold">
<t t-esc="track.name"/>
</a>
</t>
<t t-else="">
<span class="text-muted text-bold">
<t t-esc="track.name"/>
</span>
</t>
</div>
<div class="text-muted text-center">
<small t-if="track.partner_id or track.partner_name">
<t t-esc="track.partner_id.sudo().name or track.partner_name"/></small>
<div class="text-muted text-center" t-if="track.partner_tag_line">
<small t-esc="track.partner_tag_line"/>
</div>
<div class="d-flex justify-content-center flex-wrap">
<t t-foreach="track.tag_ids" t-as="tag">
@@ -170,8 +170,8 @@
<t t-set="tracks_header_name" t-value="tracks_info['name']"/>
<t t-set="tracks" t-value="tracks_info['tracks']"/>
<!-- DAY HEADER -->
<div class="o_we_track_day_header w-100">
<div t-if="tracks_date" class="d-flex">
<div class="o_we_track_day_header d-flex">
<div class="d-flex flex-grow-1" t-if="tracks_date">
<span class="h1 m-0 font-weight-bold" t-esc="tracks_date"
t-options="{'widget': 'date', 'format': 'EEEE dd'}"/>
<div class="d-flex flex-column ml-2">
@@ -181,102 +181,112 @@
t-options="{'widget': 'date', 'format': 'YYYY'}"/>
</div>
</div>
<div t-elif="tracks_header_name">
<div class="d-flex flex-grow-1" t-elif="tracks_header_name">
<span class="h1 m-0 font-weight-bold"
t-esc="tracks_header_name"/>
</div>
<a t-attf-class="ml-auto align-self-start text-black {{ 'collapsed' if tracks_info['default_collapsed'] else '' }}"
t-attf-href="#collapse_session_list_{{ tracks_info_index }}"
t-attf-aria-controls="collapse_session_list_{{ tracks_info_index }}"
t-att-aria-expanded="'false' if tracks_info['default_collapsed'] else 'true'"
data-toggle="collapse">
<i class="fa fa-2x fa-chevron-down"/>
</a>
<hr class="mt-2 pb-1 mb-1"/>
</div>
<!-- DAY TRACKS LIST -->
<div t-foreach="tracks" t-as="track"
t-att-class="'o_wesession_list_item px-2 py-2 event_color_%d' % (track.color)">
<!-- Side information in a floating div (desktop only) -->
<div t-if="not event.is_done and (not track.date or today_tz &lt;= tracks_date) and option_track_wishlist"
class="float-right d-none d-md-block ml-2">
<t t-call="website_event_track.track_widget_reminder">
<t t-set="reminder_small" t-value="False"/>
<t t-set="reminder_light" t-value="False"/>
</t>
</div>
<div class="row no-gutters">
<!-- Main column: name, speaker -->
<div class="col-md-7">
<!-- Reminder widget: directly in line to gain space, mobile only -->
<div t-if="not event.is_done and (not track.date or today_tz &lt;= tracks_date) and option_track_wishlist"
class="float-right d-block d-md-none ml-2">
<t t-call="website_event_track.track_widget_reminder">
<t t-set="reminder_small" t-value="True"/>
<t t-set="reminder_light" t-value="False"/>
</t>
</div>
<span class="h5 mb0">
<a t-if="track.is_published or is_event_user"
class="mr-2"
t-att-href="track.website_url">
<span t-field="track.name"/>
</a>
<t t-else="">
<span class="mr-2" t-field="track.name"/>
</t>
<span t-if="not track.is_published and is_event_user"
class="badge badge-danger o_wevent_online_badge_unpublished">
Unpublished
</span>
</span>
<div class="text-muted d-flex align-items-center">
<span class="text-muted" t-esc="track.partner_name"/>
<t t-if="tracks_date and today_tz &lt;= tracks_date">
<!-- Hour: Live > Remaining > Hour: mobile only -->
<div class="d-block d-md-none">
<span t-if="track.partner_name" class="ml-2">&amp;bull;</span>
<span t-if="track.is_track_live and not track.is_track_done"
class="badge badge-danger ml-2">Live</span>
<span t-elif="not track.is_track_done and track.is_track_soon"
class="ml-2">
<span t-esc="track.track_start_remaining"
t-options="{'widget': 'duration', 'digital': False, 'format': 'narrow',
'add_direction': True, 'unit': 'second', 'round': 'minute'}"/>
</span>
<span t-elif="not track.is_track_done and not track.is_track_soon"
class="ml-2"
t-esc="track.date"
t-options="{'widget': 'datetime', 'time_only': True, 'format': 'short'}"/>
<span t-else="" class="badge badge-info ml-2">Finished</span>
</div>
<!-- Duration (desktop only) -->
<t t-if="track.duration and not track.is_track_done and not track.is_track_done">
<span class="d-none d-md-block ml-2">&amp;bull;</span>
<span class="d-none d-md-block ml-2"
t-esc="track.duration"
t-options="{'widget': 'duration', 'digital': False, 'format': 'short', 'unit': 'hour', 'round': 'minute'}"/>
</t>
</t>
</div>
<div t-attf-class="collapse {{ '' if tracks_info['default_collapsed'] else 'show' }}"
t-attf-id="collapse_session_list_{{ tracks_info_index }}">
<div t-foreach="tracks" t-as="track"
t-att-class="'o_wesession_list_item px-2 py-2 event_color_%d' % (track.color)">
<!-- Side information in a floating div (desktop only) -->
<div t-if="not event.is_done and (not track.date or today_tz &lt;= tracks_date) and option_track_wishlist"
class="float-right d-none d-md-block ml-2">
<t t-call="website_event_track.track_widget_reminder">
<t t-set="reminder_small" t-value="False"/>
<t t-set="reminder_light" t-value="False"/>
</t>
</div>
<!-- Aside column: date, tags -->
<div class="col-md-5">
<!-- Hour: Live > Remaining > Hour: desktop only -->
<div t-if="tracks_date and today_tz &lt;= tracks_date"
class="d-none d-md-block float-right">
<span t-if="track.is_track_live and not track.is_track_done"
class="badge badge-danger ml-2">Live</span>
<span t-elif="not track.is_track_done and track.is_track_soon"
class="ml-2">
<span t-esc="track.track_start_remaining"
t-options="{'widget': 'duration', 'digital': False, 'format': 'narrow',
'add_direction': True, 'unit': 'second', 'round': 'minute'}"/>
<div class="row no-gutters">
<!-- Main column: name, speaker -->
<div class="col-md-7">
<!-- Reminder widget: directly in line to gain space, mobile only -->
<div t-if="not event.is_done and (not track.date or today_tz &lt;= tracks_date) and option_track_wishlist"
class="float-right d-block d-md-none ml-2">
<t t-call="website_event_track.track_widget_reminder">
<t t-set="reminder_small" t-value="True"/>
<t t-set="reminder_light" t-value="False"/>
</t>
</div>
<span class="h5 mb0">
<a t-if="track.is_published or is_event_user"
class="mr-2"
t-att-href="track.website_url">
<span t-field="track.name"/>
</a>
<t t-else="">
<span class="mr-2" t-field="track.name"/>
</t>
<span t-if="not track.is_published and is_event_user"
class="badge badge-danger o_wevent_online_badge_unpublished">
Unpublished
</span>
</span>
<span t-elif="not track.is_track_done and not track.is_track_soon"
class="ml-2"
t-esc="track.date"
t-options="{'widget': 'datetime', 'time_only': True, 'format': 'short'}"/>
<span t-else="" class="badge badge-info ml-2">Finished</span>
<div class="text-muted d-flex align-items-center">
<span class="text-muted" t-esc="track.partner_tag_line"/>
<t t-if="tracks_date and today_tz &lt;= tracks_date">
<!-- Hour: Live > Remaining > Hour: mobile only -->
<div class="d-block d-md-none">
<span t-if="track.partner_tag_line" class="ml-2">&amp;bull;</span>
<span t-if="track.is_track_live and not track.is_track_done"
class="badge badge-danger ml-2">Live</span>
<span t-elif="not track.is_track_done and track.is_track_soon"
class="ml-2">
<span t-esc="track.track_start_remaining"
t-options="{'widget': 'duration', 'digital': False, 'format': 'narrow',
'add_direction': True, 'unit': 'second', 'round': 'minute'}"/>
</span>
<span t-elif="not track.is_track_done and not track.is_track_soon"
class="ml-2"
t-esc="track.date"
t-options="{'widget': 'datetime', 'time_only': True, 'format': 'short'}"/>
<span t-else="" class="badge badge-info ml-2">Finished</span>
</div>
<!-- Duration (desktop only) -->
<t t-if="track.duration and not track.is_track_done and not track.is_track_done">
<span class="d-none d-md-block ml-2">&amp;bull;</span>
<span class="d-none d-md-block ml-2"
t-esc="track.duration"
t-options="{'widget': 'duration', 'digital': False, 'format': 'short', 'unit': 'hour', 'round': 'minute'}"/>
</t>
</t>
</div>
</div>
<!-- Tags: desktop only -->
<div class="d-none d-md-block">
<t t-foreach="track.tag_ids" t-as="tag">
<t t-if="tag.color" t-call="website_event_track.track_tag_badge_link"/>
</t>
<!-- Aside column: date, tags -->
<div class="col-md-5">
<!-- Hour: Live > Remaining > Hour: desktop only -->
<div t-if="tracks_date and today_tz &lt;= tracks_date"
class="d-none d-md-block float-right">
<span t-if="track.is_track_live and not track.is_track_done"
class="badge badge-danger ml-2">Live</span>
<span t-elif="not track.is_track_done and track.is_track_soon"
class="ml-2">
<span t-esc="track.track_start_remaining"
t-options="{'widget': 'duration', 'digital': False, 'format': 'narrow',
'add_direction': True, 'unit': 'second', 'round': 'minute'}"/>
</span>
<span t-elif="not track.is_track_done and not track.is_track_soon"
class="ml-2"
t-esc="track.date"
t-options="{'widget': 'datetime', 'time_only': True, 'format': 'short'}"/>
<span t-else="" class="badge badge-info ml-2">Finished</span>
</div>
<!-- Tags: desktop only -->
<div class="d-none d-md-block">
<t t-foreach="track.tag_ids" t-as="tag">
<t t-if="tag.color" t-call="website_event_track.track_tag_badge_link"/>
</t>
</div>
</div>
</div>
</div>
@@ -292,10 +302,10 @@
<template id="tracks_cards_track" name="Track Card">
<a t-att-href="'/event/%s/track/%s' % (slug(track.event_id), slug(track))" class="text-decoration-none">
<article t-att-class="'h-100 card border-0 shadow-sm o_wesession_track_card %s' % ('o_wesession_track_card_unpublished' if not track.is_published else '')"
<article t-att-class="'h-100 card rounded-0 border-0 shadow-sm o_wesession_track_card %s' % ('o_wesession_track_card_unpublished' if not track.is_published else '')"
itemscope="itemscope" itemtype="http://schema.org/Event">
<div class="h-100 row no-gutters">
<header class="overflow-hidden bg-secondary col-12 rounded-top">
<header class="overflow-hidden bg-secondary col-12">
<small t-if="not track.is_published" class="o_wesession_track_card_header_badge bg-danger">
<i class="fa fa-ban mr-2"/>Unpublished
</small>
@@ -362,7 +372,7 @@
<div class="d-flex align-items-center mb-3">
<span t-if="search_wishlist"
class="align-items-baseline border d-inline-flex pl-2 mt-3 rounded ml16 mb-2 bg-white">
<i class="fa fa-bell mr-2 text-muted"/> Wishlisted Talks
<i class="fa fa-bell mr-2 text-muted"/> Favorite Talks
<a t-att-href="'/event/%s/track?%s' % (slug(event), keep_query('*', search_wishlist=''))"
class="btn border-0 py-1">
&#215;
@@ -45,12 +45,10 @@
</span>
</div>
</div>
<div t-elif="track.track_start_remaining" class="pt-3">
<div class="mx-3 alert alert-warning">
Starting in
<span t-esc="track.track_start_remaining"
t-options="{'widget': 'duration', 'digital': False, 'unit': 'second', 'round': 'minute'}"/>
</div>
<div t-elif="track.track_start_remaining">
<t t-call="website_event.display_timer_alert_widget">
<t t-set="time_to_live" t-value="track.track_start_remaining"/>
</t>
</div>
<!-- TRACK DESCRIPTION -->
<div class="o_wesession_track_main_description overflow-auto">
@@ -135,9 +133,9 @@
<!-- ============================================================ -->
<template id="event_track_aside" name="Track: Aside">
<div t-att-class="'col-12 pl-0 pr-0 pr-md-3 o_wevent_online_page_aside o_wesession_track_aside %s' % ('col-md-3 col-lg-2' if option_widescreen else 'col-md-4 col-lg-3')">
<div t-att-class="'col-12 pl-0 pr-0 o_wevent_online_page_aside o_wesession_track_aside %s' % ('col-md-3 col-lg-2' if option_widescreen else 'col-md-4 col-lg-3')">
<div class="bg-white pt-0 pt-md-3 o_wevent_online_page_aside_content">
<div class="mx-2" t-if="track.website_cta">
<div class="mx-2" t-if="track.date and track.website_cta">
<t t-set="cta_coundown" t-value="bool(track.website_cta_start_remaining)"/>
<div t-if="cta_coundown" class="text-center mt-2 mt-md-0 w-100 btn btn-primary d-none">
<t t-call="website_event.display_timer_widget">
@@ -7,7 +7,7 @@
<field name="domain">[('event_track_wishlisted_ids', 'in', [active_id])]</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
Wait for visitors to wishlist your tracks
Wait for visitors to add this track to their list of favorites
</p>
</field>
</record>
@@ -292,7 +292,7 @@
<field name="domain">[('wishlist_visitor_ids', 'in', [active_id])]</field>
<field name="help" type="html">
<p class="o_view_nocontent_empty_folder">
No track wishlisted by this visitor
No track favorited by this visitor
</p>
</field>
</record>
@@ -26,6 +26,7 @@
'assets': {
'web.assets_frontend': [
'website_event_track_live/static/src/scss/website_event_track_live.scss',
'website_event_track_live/static/src/js/website_event_track_replay_suggestion.js',
'website_event_track_live/static/src/js/website_event_track_suggestion.js',
'website_event_track_live/static/src/js/website_event_track_live.js',
],
@@ -5,10 +5,7 @@ odoo.define('website_event_track_live.website_event_youtube_embed', function (re
var publicWidget = require('web.public.widget');
var TrackSuggestionWidget = require('website_event_track_live.website_event_track_suggestion');
var YOUTUBE_VIDEO_ENDED = 0;
var YOUTUBE_VIDEO_PLAYING = 1;
var YOUTUBE_VIDEO_PAUSED = 2;
var ReplaySuggestionWidget = require('website_event_track_live.website_event_track_replay_suggestion');
publicWidget.registry.websiteEventTrackLive = publicWidget.Widget.extend({
selector: '.o_wevent_event_track_live',
@@ -20,7 +17,6 @@ publicWidget.registry.websiteEventTrackLive = publicWidget.Widget.extend({
var self = this;
return this._super(...arguments).then(function () {
self._setupYoutubePlayer();
self.isFullScreen = !!document.fullscreenElement;
});
},
@@ -29,30 +25,27 @@ publicWidget.registry.websiteEventTrackLive = publicWidget.Widget.extend({
//--------------------------------------------------------------------------
_onPlayerReady: function () {
$(window).on('resize', this._onResize.bind(this));
this.$('.o_wevent_event_track_live_loading').remove();
},
_onPlayerStateChange: function (event) {
if (event.data === YOUTUBE_VIDEO_ENDED) {
this.trigger('video-ended');
} else if (event.data === YOUTUBE_VIDEO_PLAYING) {
this.trigger('video-playing');
} else if (event.data === YOUTUBE_VIDEO_PAUSED) {
this.trigger('video-paused');
}
switch (event.data) {
case YT.PlayerState.ENDED:
this.trigger('video-ended');
return;
case YT.PlayerState.PLAYING:
this.trigger('video-playing');
return;
case YT.PlayerState.PAUSED:
this.trigger('video-paused');
return;
};
},
_onVideoEnded: function () {
if (this.$el.data('hasNextSuggestion')) {
// if we have an upcoming suggestion, add a covering block to avoid
// showing Youtube suggestions while we fetch the appropriate suggestion
// using a rpc. This allows avoiding a 'flicker' effect.
this.$el.append($('<div/>', {
class:'owevent_track_suggestion_loading position-absolute w-100'
}));
}
this.$el.append($('<div/>', {
class: 'owevent_track_suggestion_loading position-absolute w-100'
}));
var self = this;
this._rpc({
route: '/event_track/get_track_suggestion',
@@ -69,21 +62,11 @@ publicWidget.registry.websiteEventTrackLive = publicWidget.Widget.extend({
this.youtubePlayer.seekTo(0);
this.youtubePlayer.playVideo();
this.$('.owevent_track_suggestion_loading').remove();
if (this.trackSuggestion) {
delete this.trackSuggestion;
if (this.outro) {
delete this.outro;
}
},
/**
* The 'fullscreenchange' event is probably a better fit but it is unfortunately
* not triggered when Youtube enters fullscreen mode.
* However, the global window 'resize' is.
*/
_onResize: function () {
this.isFullScreen = !!document.fullscreenElement;
this._showSuggestion();
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
@@ -116,14 +99,26 @@ publicWidget.registry.websiteEventTrackLive = publicWidget.Widget.extend({
},
/**
* Automatically launches the next suggested track when this one ends
* and the user is not in fullscreen mode.
* If a new suggestion has been found, a cover containing a replay button
* as well as a suggestion will automatically be placed over the Youtube
* player when the video ends (in non-full screen mode). If no suggestion
* has been found, the cover will only contain a replay button.
*/
_showSuggestion: function () {
if (this.nextSuggestion && !this.isFullScreen && !this.trackSuggestion) {
this.trackSuggestion = new TrackSuggestionWidget(this, this.nextSuggestion);
this.trackSuggestion.appendTo(this.$el);
this.trackSuggestion.on('replay', null, this._onReplay.bind(this));
if (!this.outro) {
if (this.nextSuggestion) {
this.outro = new TrackSuggestionWidget(this, this.nextSuggestion);
} else {
var data = this.$el.data();
this.outro = new ReplaySuggestionWidget(this, {
current_track: {
name: data.trackName,
website_image_url: data.trackWebsiteImageUrl
}
});
}
this.outro.appendTo(this.$el);
this.outro.on('replay', null, this._onReplay.bind(this));
}
}
});
@@ -0,0 +1,42 @@
odoo.define('website_event_track_live.website_event_track_replay_suggestion', function (require) {
'use strict';
var Widget = require('web.Widget');
/**
* The widget will have the responsibility to manage the interactions between the
* Youtube player and the cover containing a replay button. This widget will
* be used when no suggestion can be found in order to hide the Youtube suggestions.
*/
var WebsiteEventReplaySuggestion = Widget.extend({
template: 'website_event_track_live.website_event_track_replay_suggestion',
xmlDependencies: ['/website_event_track_live/static/src/xml/website_event_track_live_templates.xml'],
events: {
'click .owevent_track_suggestion_replay': '_onReplayClick'
},
init: function (parent, options) {
this._super(...arguments);
this.currentTrack = {
'name': options.current_track.name,
'imageSrc': options.current_track.website_image_url,
};
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* If the user clicks on the replay button, the function will remove the
* cover and send a new event to the parent to replay the video from the
* beginning.
*/
_onReplayClick: function () {
this.trigger_up('replay');
this.destroy();
}
});
return WebsiteEventReplaySuggestion;
});
@@ -1,6 +1,22 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<div t-name="website_event_track_live.website_event_track_replay_suggestion"
class="owevent_track_suggestion position-absolute w-100"
t-attf-style="background-image: url('#{widget.currentTrack.imageSrc}');">
<div class="h-100 w-100 p-4 d-flex flex-column align-items-center owevent_track_suggestion_content">
<div class="flex-grow-1 pt-5 d-flex flex-column align-items-center justify-content-center">
<div class="text-white">You just watched:</div>
<div class="h1 text-white" t-esc="widget.currentTrack.name"/>
<div>
<a href="#" class="owevent_track_suggestion_replay font-weight-bold">
<span>Replay Video</span>
</a>
</div>
</div>
</div>
</div>
<div t-name="website_event_track_live.website_event_track_suggestion"
class="owevent_track_suggestion position-absolute w-100"
t-attf-style="background-image: url('#{widget.currentTrack.imageSrc}');">
@@ -20,8 +20,9 @@
<t t-set="show_youtube_frame" t-value="track.youtube_video_url and (track.is_youtube_replay or track.is_track_soon or track.is_track_live or track.is_track_done)" />
<div t-if="show_youtube_frame" class="flex-grow-1 o_wevent_event_track_live"
t-att-data-track-id="track.id"
t-att-data-youtube-video-id="track.youtube_video_id"
t-att-data-has-next-suggestion="bool(track._get_track_suggestions(restrict_domain=[('youtube_video_url', '!=', False), ('is_published', '=', True)], limit=1))">
t-att-data-track-name="track.name"
t-att-data-track-website-image-url="track.website_image_url"
t-att-data-youtube-video-id="track.youtube_video_id">
<div class="position-absolute o_wevent_event_track_live_loading w-100 d-flex align-items-center justify-content-center text-white h4">
<i class="fa fa-spin fa-spinner position-relative"/>
<span class="pl-2">Loading Video...</span>