[REF] website: move s_countdown snippet to dedicated files

This commit is contained in:
qsm-odoo
2019-12-18 02:28:36 +01:00
8 changed files with 624 additions and 587 deletions
+1
View File
@@ -45,6 +45,7 @@
'views/snippets/s_hr.xml',
'views/snippets/s_facebook_page.xml',
'views/snippets/s_image_gallery.xml',
'views/snippets/s_countdown.xml',
'views/website_views.xml',
'views/website_visitor_views.xml',
'views/res_config_settings_views.xml',
@@ -919,412 +919,6 @@ registry.anchorSlide = publicWidget.Widget.extend({
},
});
registry.countdown = publicWidget.Widget.extend({
selector: '.s_countdown',
xmlDependencies: ['/website/static/src/xml/website.s_countdown.xml'],
disabledInEditableMode: false,
/**
* @override
*/
start: function () {
this.$wrapper = this.$('.s_countdown_canvas_wrapper');
this.hereBeforeTimerEnds = false;
this.endAction = this.el.dataset.endAction;
this.endTime = parseInt(this.el.dataset.endTime);
this.size = parseInt(this.el.dataset.size);
this.display = this.el.dataset.display;
this.layout = this.el.dataset.layout;
this.layoutBackground = this.el.dataset.layoutBackground;
this.progressBarStyle = this.el.dataset.progressBarStyle;
this.progressBarWeight = this.el.dataset.progressBarWeight;
this.textColor = this._ensureCssColor(this.el.dataset.textColor);
this.layoutBackgroundColor = this._ensureCssColor(this.el.dataset.layoutBackgroundColor);
this.progressBarColor = this._ensureCssColor(this.el.dataset.progressBarColor);
this.onlyOneUnit = this.display === 'd';
this.width = parseInt(this.size);
if (this.layout === 'boxes') {
this.width /= 1.75;
}
this._initTimeDiff();
this._render();
this.setInterval = setInterval(this._render.bind(this), 1000);
return this._super(...arguments);
},
/**
* @override
*/
destroy: function () {
this.$('.s_countdown_end_redirect_message').remove();
this.$('canvas').remove();
this.$('.s_countdown_end_message').addClass('d-none');
this.$('.s_countdown_text_wrapper').remove();
this.$('.s_countdown_canvas_wrapper').removeClass('d-none');
clearInterval(this.setInterval);
this._super(...arguments);
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* Ensures the color is an actual css color. In case of a color variable,
* the color will be mapped to hexa.
*
* @private
* @param {string} color
* @returns {string}
*/
_ensureCssColor: function (color) {
if (ColorpickerDialog.isCSSColor(color)) {
return color;
}
const style = window.getComputedStyle(document.documentElement);
return style.getPropertyValue('--' + color).trim();
},
/**
* Gets the time difference in seconds between now and countdown due date.
*
* @private
*/
_getDelta: function () {
const currentTimestamp = Date.now() / 1000;
return this.endTime - currentTimestamp;
},
/**
* Handles the action that should be executed once the countdown ends.
*
* @private
*/
_handleEndCountdownAction: function () {
if (this.endAction === 'redirect') {
const redirectUrl = this.el.dataset.redirectUrl;
if (this.hereBeforeTimerEnds) {
// Wait a bit, if the landing page has the same publish date
setTimeout(() => window.location = redirectUrl, 500);
} else {
// Show (non editable) msg when user lands on already finished countdown
if (!this.$('.s_countdown_end_redirect_message').length) {
this.$target.find('.container').append(
$(qweb.render('website.s_countdown.end_redirect_message', {
redirectUrl: redirectUrl,
}))
);
}
}
} else if (this.endAction === 'message') {
this.$('.s_countdown_end_message').removeClass('d-none');
}
},
/**
* Initializes the `diff` object. It will contains every visible time unit
* which will each contain its related canvas, total step, label..
*
* @private
*/
_initTimeDiff: function () {
const delta = this._getDelta();
this.diff = [];
if (this._isUnitVisible('d') && !(this.onlyOneUnit && delta < 86400)) {
this.diff.push({
canvas: $('<canvas/>').appendTo(this.$wrapper)[0],
// There is no logical number of unit (total) on which day units
// can be compared against, so we use an arbitrary number.
total: 15,
label: _t("Days"),
nbSeconds: 86400,
});
}
if (this._isUnitVisible('h') || (this.onlyOneUnit && delta < 86400 && delta > 3600)) {
this.diff.push({
canvas: $('<canvas/>').appendTo(this.$wrapper)[0],
total: 24,
label: _t("Hours"),
nbSeconds: 3600,
});
}
if (this._isUnitVisible('m') || (this.onlyOneUnit && delta < 3600 && delta > 60)) {
this.diff.push({
canvas: $('<canvas/>').appendTo(this.$wrapper)[0],
total: 60,
label: _t("Minutes"),
nbSeconds: 60,
});
}
if (this._isUnitVisible('s') || (this.onlyOneUnit && delta < 60)) {
this.diff.push({
canvas: $('<canvas/>').appendTo(this.$wrapper)[0],
total: 60,
label: _t("Seconds"),
nbSeconds: 1,
});
}
},
/**
* Returns weither or not the countdown should be displayed for the given
* unit (days, sec..).
*
* @private
* @param {string} unit - either 'd', 'm', 'h', or 's'
* @returns {boolean}
*/
_isUnitVisible: function (unit) {
return this.display.includes(unit);
},
/**
* Draws the whole countdown, including one countdown for each time unit.
*
* @private
*/
_render: function () {
// If only one unit mode, restart widget on unit change to populate diff
if (this.onlyOneUnit && this._getDelta() < this.diff[0].nbSeconds) {
this.$('canvas').remove();
this._initTimeDiff();
}
this._updateTimeDiff();
const hideCountdown = this.isFinished && !this.editableMode && this.$el.hasClass('hide-countdown');
if (this.layout === 'text') {
this.$('canvas').addClass('d-none');
if (!this.$textWrapper) {
this.$textWrapper = $('<span/>').attr({
class: 's_countdown_text_wrapper d-none',
});
this.$textWrapper.text(_t("Countdown ends in"));
this.$textWrapper.append($('<span/>').attr({
class: 's_countdown_text ml-1',
}));
this.$textWrapper.appendTo(this.$wrapper);
}
this.$textWrapper.toggleClass('d-none', hideCountdown);
const countdownText = this.diff.map(e => e.nb + ' ' + e.label).join(', ');
this.$('.s_countdown_text').text(countdownText.toLowerCase());
} else {
for (const val of this.diff) {
const canvas = val.canvas;
const ctx = canvas.getContext("2d");
ctx.canvas.width = this.width;
ctx.canvas.height = this.size;
this._clearCanvas(ctx);
$(canvas).toggleClass('d-none', hideCountdown);
if (hideCountdown) {
return;
}
// Draw canvas elements
if (this.layoutBackground !== 'none') {
this._drawBgShape(ctx, this.layoutBackground === 'plain');
}
this._drawText(canvas, val.nb, val.label, this.layoutBackground === 'plain');
if (this.progressBarStyle === 'surrounded') {
this._drawProgressBarBg(ctx, this.progressBarWeight === 'thin');
}
if (this.progressBarStyle !== 'none') {
this._drawProgressBar(ctx, val.nb, val.total, this.progressBarWeight === 'thin');
}
$(canvas).toggleClass('mx-2', this.layout === 'boxes');
}
}
if (this.isFinished) {
clearInterval(this.setInterval);
if (!this.editableMode) {
this._handleEndCountdownAction();
}
}
},
/**
* Updates the remaining units into the `diff` object.
*
* @private
*/
_updateTimeDiff: function () {
let delta = this._getDelta();
this.isFinished = delta < 0;
if (this.isFinished) {
for (const unitData of this.diff) {
unitData.nb = 0;
}
return;
}
this.hereBeforeTimerEnds = true;
for (const unitData of this.diff) {
unitData.nb = Math.floor(delta / unitData.nbSeconds);
delta -= unitData.nb * unitData.nbSeconds;
}
},
//--------------------------------------------------------------------------
// Canvas drawing methods
//--------------------------------------------------------------------------
/**
* Erases the canvas.
*
* @private
* @param {RenderingContext} ctx - Context of the canvas
*/
_clearCanvas: function (ctx) {
ctx.clearRect(0, 0, this.size, this.size);
},
/**
* Draws a text into the canvas.
*
* @private
* @param {HTMLCanvasElement} canvas
* @param {string} textNb - text to display in the center of the canvas, in big
* @param {string} textUnit - text to display bellow `textNb` in small
* @param {boolean} full - if true, the shape will be drawn up to the progressbar
*/
_drawText: function (canvas, textNb, textUnit, full = false) {
const ctx = canvas.getContext("2d");
const nbSize = this.size / 4;
ctx.font = `${nbSize}px Arial`;
ctx.fillStyle = this.textColor;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(textNb, canvas.width / 2, canvas.height / 2);
const unitSize = this.size / 12;
ctx.font = `${unitSize}px Arial`;
ctx.fillText(textUnit, canvas.width / 2, canvas.height / 2 + nbSize / 1.5, this.width);
if (this.layout === 'boxes' && this.layoutBackground !== 'none' && this.progressBarStyle === 'none') {
let barWidth = this.size / (this.progressBarWeight === 'thin' ? 31 : 10);
if (full) {
barWidth = 0;
}
ctx.beginPath();
ctx.moveTo(barWidth, this.size / 2);
ctx.lineTo(this.width - barWidth, this.size / 2);
ctx.stroke();
}
},
/**
* Draws a plain shape into the canvas.
*
* @private
* @param {RenderingContext} ctx - Context of the canvas
* @param {boolean} full - if true, the shape will be drawn up to the progressbar
*/
_drawBgShape: function (ctx, full = false) {
ctx.fillStyle = this.layoutBackgroundColor;
ctx.beginPath();
if (this.layout === 'circle') {
let rayon = this.size / 2;
if (this.progressBarWeight === 'thin') {
rayon -= full ? this.size / 29 : this.size / 15;
} else {
rayon -= full ? 0 : this.size / 10;
}
ctx.arc(this.size / 2, this.size / 2, rayon, 0, Math.PI * 2);
ctx.fill();
} else if (this.layout === 'boxes') {
let barWidth = this.size / (this.progressBarWeight === 'thin' ? 31 : 10);
if (full) {
barWidth = 0;
}
ctx.fillStyle = this.layoutBackgroundColor;
ctx.rect(barWidth, barWidth, this.width - barWidth * 2, this.size - barWidth * 2);
ctx.fill();
const gradient = ctx.createLinearGradient(0, this.width, 0, 0);
gradient.addColorStop(0, '#ffffff24');
gradient.addColorStop(1, this.layoutBackgroundColor);
ctx.fillStyle = gradient;
ctx.rect(barWidth, barWidth, this.width - barWidth * 2, this.size - barWidth * 2);
ctx.fill();
$(ctx.canvas).css({'border-radius': '8px'});
}
},
/**
* Draws a progress bar around the countdown shape.
*
* @private
* @param {RenderingContext} ctx - Context of the canvas
* @param {string} nbUnit - how many unit should fill progress bar
* @param {string} totalUnit - number of unit to do a complete progress bar
* @param {boolean} thinLine - if true, the progress bar will be thiner
*/
_drawProgressBar: function (ctx, nbUnit, totalUnit, thinLine) {
ctx.strokeStyle = this.progressBarColor;
ctx.lineWidth = thinLine ? this.size / 35 : this.size / 10;
if (this.layout === 'circle') {
ctx.beginPath();
ctx.arc(this.size / 2, this.size / 2, this.size / 2 - this.size / 20, Math.PI / -2, (Math.PI * 2) * (nbUnit / totalUnit) + (Math.PI / -2));
ctx.stroke();
} else if (this.layout === 'boxes') {
ctx.lineWidth *= 2;
let pc = nbUnit / totalUnit * 100;
// Lines: Top(x1,y1,x2,y2) Right(x1,y1,x2,y2) Bottom(x1,y1,x2,y2) Left(x1,y1,x2,y2)
const linesCoordFuncs = [
(linePc) => [0 + ctx.lineWidth / 2, 0, (this.width - ctx.lineWidth / 2) * linePc / 25 + ctx.lineWidth / 2, 0],
(linePc) => [this.width, 0 + ctx.lineWidth / 2, this.width, (this.size - ctx.lineWidth / 2) * linePc / 25 + ctx.lineWidth / 2],
(linePc) => [this.width - ((this.width - ctx.lineWidth / 2) * linePc / 25) - ctx.lineWidth / 2, this.size, this.width - ctx.lineWidth / 2, this.size],
(linePc) => [0, this.size - ((this.size - ctx.lineWidth / 2) * linePc / 25) - ctx.lineWidth / 2, 0, this.size - ctx.lineWidth / 2],
];
while (pc > 0 && linesCoordFuncs.length) {
const linePc = Math.min(pc, 25);
const lineCoord = (linesCoordFuncs.shift())(linePc);
ctx.beginPath();
ctx.moveTo(lineCoord[0], lineCoord[1]);
ctx.lineTo(lineCoord[2], lineCoord[3]);
ctx.stroke();
pc -= linePc;
}
}
},
/**
* Draws a full lighter background progressbar around the shape.
*
* @private
* @param {RenderingContext} ctx - Context of the canvas
* @param {boolean} thinLine - if true, the progress bar will be thiner
*/
_drawProgressBarBg: function (ctx, thinLine) {
ctx.strokeStyle = this.progressBarColor;
ctx.globalAlpha = 0.2;
ctx.lineWidth = thinLine ? this.size / 35 : this.size / 10;
if (this.layout === 'circle') {
ctx.beginPath();
ctx.arc(this.size / 2, this.size / 2, this.size / 2 - this.size / 20, 0, Math.PI * 2);
ctx.stroke();
} else if (this.layout === 'boxes') {
ctx.lineWidth *= 2;
// Lines: Top(x1,y1,x2,y2) Right(x1,y1,x2,y2) Bottom(x1,y1,x2,y2) Left(x1,y1,x2,y2)
const points = [
[0 + ctx.lineWidth / 2, 0, this.width, 0],
[this.width, 0 + ctx.lineWidth / 2, this.width, this.size],
[0, this.size, this.width - ctx.lineWidth / 2, this.size],
[0, 0, 0, this.size - ctx.lineWidth / 2],
];
while (points.length) {
const point = points.shift();
ctx.beginPath();
ctx.moveTo(point[0], point[1]);
ctx.lineTo(point[2], point[3]);
ctx.stroke();
}
}
ctx.globalAlpha = 1;
},
});
registry.chart = publicWidget.Widget.extend({
selector: '.s_chart',
disabledInEditableMode: false,
@@ -844,122 +844,6 @@ options.registry.collapse = options.Class.extend({
},
});
options.registry.countdown = options.Class.extend({
events: _.extend({}, options.Class.prototype.events || {}, {
'click .toggle-edit-message': '_onToggleEndMessageClick',
}),
//--------------------------------------------------------------------------
// Options
//--------------------------------------------------------------------------
/**
* Changes the countdown action at zero.
*
* @see this.selectClass for parameters
*/
endAction: function (previewMode, widgetValue, params) {
this.$target[0].dataset.endAction = widgetValue;
if (widgetValue === 'message') {
if (!this.$target.find('.s_countdown_end_message').length) {
const message = this.endMessage || qweb.render('website.s_countdown.end_message');
this.$target.find('.container').append(message);
}
} else {
const $message = this.$target.find('.s_countdown_end_message').detach();
if ($message.length) {
this.endMessage = $message[0].outerHTML;
}
}
},
/**
* Changes the countdown style.
*
* @see this.selectClass for parameters
*/
layout: function (previewMode, widgetValue, params) {
switch (widgetValue) {
case 'circle':
this.$target[0].dataset.progressBarStyle = 'disappear';
this.$target[0].dataset.progressBarWeight = 'thin';
this.$target[0].dataset.layoutBackground = 'none';
break;
case 'boxes':
this.$target[0].dataset.progressBarStyle = 'none';
this.$target[0].dataset.layoutBackground = 'plain';
break;
case 'clean':
this.$target[0].dataset.progressBarStyle = 'none';
this.$target[0].dataset.layoutBackground = 'none';
break;
case 'text':
this.$target[0].dataset.progressBarStyle = 'none';
this.$target[0].dataset.layoutBackground = 'none';
break;
}
this.$target[0].dataset.layout = widgetValue;
},
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
/**
* @override
*/
updateUIVisibility: async function () {
await this._super(...arguments);
const dataset = this.$target[0].dataset;
// End Action UI
this.$el.find('.toggle-edit-message')
.toggleClass('d-none', dataset.endAction !== 'message');
// End Message UI
this.updateUIEndMessage();
},
/**
* @see this.updateUI
*/
updateUIEndMessage: function () {
this.$target.find('.s_countdown_canvas_wrapper')
.toggleClass("d-none", this.showEndMessage === true && this.$target.hasClass("hide-countdown"));
this.$target.find('.s_countdown_end_message')
.toggleClass("d-none", !this.showEndMessage);
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* @override
*/
_computeWidgetState: function (methodName, params) {
switch (methodName) {
case 'endAction':
case 'layout':
return this.$target[0].dataset[methodName];
}
return this._super(...arguments);
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* @private
*/
_onToggleEndMessageClick: function () {
this.showEndMessage = !this.showEndMessage;
this.$el.find(".toggle-edit-message")
.toggleClass('text-primary', this.showEndMessage);
this.updateUIEndMessage();
this.trigger_up('cover_update');
},
});
options.registry.topMenuTransparency = options.Class.extend({
//--------------------------------------------------------------------------
@@ -0,0 +1,420 @@
odoo.define('website.s_countdown', function (require) {
'use strict';
const ColorpickerDialog = require('web.ColorpickerDialog');
const core = require('web.core');
const publicWidget = require('web.public.widget');
const qweb = core.qweb;
const _t = core._t;
const CountdownWidget = publicWidget.Widget.extend({
selector: '.s_countdown',
xmlDependencies: ['/website/static/src/xml/website.s_countdown.xml'],
disabledInEditableMode: false,
/**
* @override
*/
start: function () {
this.$wrapper = this.$('.s_countdown_canvas_wrapper');
this.hereBeforeTimerEnds = false;
this.endAction = this.el.dataset.endAction;
this.endTime = parseInt(this.el.dataset.endTime);
this.size = parseInt(this.el.dataset.size);
this.display = this.el.dataset.display;
this.layout = this.el.dataset.layout;
this.layoutBackground = this.el.dataset.layoutBackground;
this.progressBarStyle = this.el.dataset.progressBarStyle;
this.progressBarWeight = this.el.dataset.progressBarWeight;
this.textColor = this._ensureCssColor(this.el.dataset.textColor);
this.layoutBackgroundColor = this._ensureCssColor(this.el.dataset.layoutBackgroundColor);
this.progressBarColor = this._ensureCssColor(this.el.dataset.progressBarColor);
this.onlyOneUnit = this.display === 'd';
this.width = parseInt(this.size);
if (this.layout === 'boxes') {
this.width /= 1.75;
}
this._initTimeDiff();
this._render();
this.setInterval = setInterval(this._render.bind(this), 1000);
return this._super(...arguments);
},
/**
* @override
*/
destroy: function () {
this.$('.s_countdown_end_redirect_message').remove();
this.$('canvas').remove();
this.$('.s_countdown_end_message').addClass('d-none');
this.$('.s_countdown_text_wrapper').remove();
this.$('.s_countdown_canvas_wrapper').removeClass('d-none');
clearInterval(this.setInterval);
this._super(...arguments);
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* Ensures the color is an actual css color. In case of a color variable,
* the color will be mapped to hexa.
*
* @private
* @param {string} color
* @returns {string}
*/
_ensureCssColor: function (color) {
if (ColorpickerDialog.isCSSColor(color)) {
return color;
}
const style = window.getComputedStyle(document.documentElement);
return style.getPropertyValue('--' + color).trim();
},
/**
* Gets the time difference in seconds between now and countdown due date.
*
* @private
*/
_getDelta: function () {
const currentTimestamp = Date.now() / 1000;
return this.endTime - currentTimestamp;
},
/**
* Handles the action that should be executed once the countdown ends.
*
* @private
*/
_handleEndCountdownAction: function () {
if (this.endAction === 'redirect') {
const redirectUrl = this.el.dataset.redirectUrl;
if (this.hereBeforeTimerEnds) {
// Wait a bit, if the landing page has the same publish date
setTimeout(() => window.location = redirectUrl, 500);
} else {
// Show (non editable) msg when user lands on already finished countdown
if (!this.$('.s_countdown_end_redirect_message').length) {
this.$target.find('.container').append(
$(qweb.render('website.s_countdown.end_redirect_message', {
redirectUrl: redirectUrl,
}))
);
}
}
} else if (this.endAction === 'message') {
this.$('.s_countdown_end_message').removeClass('d-none');
}
},
/**
* Initializes the `diff` object. It will contains every visible time unit
* which will each contain its related canvas, total step, label..
*
* @private
*/
_initTimeDiff: function () {
const delta = this._getDelta();
this.diff = [];
if (this._isUnitVisible('d') && !(this.onlyOneUnit && delta < 86400)) {
this.diff.push({
canvas: $('<canvas/>').appendTo(this.$wrapper)[0],
// There is no logical number of unit (total) on which day units
// can be compared against, so we use an arbitrary number.
total: 15,
label: _t("Days"),
nbSeconds: 86400,
});
}
if (this._isUnitVisible('h') || (this.onlyOneUnit && delta < 86400 && delta > 3600)) {
this.diff.push({
canvas: $('<canvas/>').appendTo(this.$wrapper)[0],
total: 24,
label: _t("Hours"),
nbSeconds: 3600,
});
}
if (this._isUnitVisible('m') || (this.onlyOneUnit && delta < 3600 && delta > 60)) {
this.diff.push({
canvas: $('<canvas/>').appendTo(this.$wrapper)[0],
total: 60,
label: _t("Minutes"),
nbSeconds: 60,
});
}
if (this._isUnitVisible('s') || (this.onlyOneUnit && delta < 60)) {
this.diff.push({
canvas: $('<canvas/>').appendTo(this.$wrapper)[0],
total: 60,
label: _t("Seconds"),
nbSeconds: 1,
});
}
},
/**
* Returns weither or not the countdown should be displayed for the given
* unit (days, sec..).
*
* @private
* @param {string} unit - either 'd', 'm', 'h', or 's'
* @returns {boolean}
*/
_isUnitVisible: function (unit) {
return this.display.includes(unit);
},
/**
* Draws the whole countdown, including one countdown for each time unit.
*
* @private
*/
_render: function () {
// If only one unit mode, restart widget on unit change to populate diff
if (this.onlyOneUnit && this._getDelta() < this.diff[0].nbSeconds) {
this.$('canvas').remove();
this._initTimeDiff();
}
this._updateTimeDiff();
const hideCountdown = this.isFinished && !this.editableMode && this.$el.hasClass('hide-countdown');
if (this.layout === 'text') {
this.$('canvas').addClass('d-none');
if (!this.$textWrapper) {
this.$textWrapper = $('<span/>').attr({
class: 's_countdown_text_wrapper d-none',
});
this.$textWrapper.text(_t("Countdown ends in"));
this.$textWrapper.append($('<span/>').attr({
class: 's_countdown_text ml-1',
}));
this.$textWrapper.appendTo(this.$wrapper);
}
this.$textWrapper.toggleClass('d-none', hideCountdown);
const countdownText = this.diff.map(e => e.nb + ' ' + e.label).join(', ');
this.$('.s_countdown_text').text(countdownText.toLowerCase());
} else {
for (const val of this.diff) {
const canvas = val.canvas;
const ctx = canvas.getContext("2d");
ctx.canvas.width = this.width;
ctx.canvas.height = this.size;
this._clearCanvas(ctx);
$(canvas).toggleClass('d-none', hideCountdown);
if (hideCountdown) {
return;
}
// Draw canvas elements
if (this.layoutBackground !== 'none') {
this._drawBgShape(ctx, this.layoutBackground === 'plain');
}
this._drawText(canvas, val.nb, val.label, this.layoutBackground === 'plain');
if (this.progressBarStyle === 'surrounded') {
this._drawProgressBarBg(ctx, this.progressBarWeight === 'thin');
}
if (this.progressBarStyle !== 'none') {
this._drawProgressBar(ctx, val.nb, val.total, this.progressBarWeight === 'thin');
}
$(canvas).toggleClass('mx-2', this.layout === 'boxes');
}
}
if (this.isFinished) {
clearInterval(this.setInterval);
if (!this.editableMode) {
this._handleEndCountdownAction();
}
}
},
/**
* Updates the remaining units into the `diff` object.
*
* @private
*/
_updateTimeDiff: function () {
let delta = this._getDelta();
this.isFinished = delta < 0;
if (this.isFinished) {
for (const unitData of this.diff) {
unitData.nb = 0;
}
return;
}
this.hereBeforeTimerEnds = true;
for (const unitData of this.diff) {
unitData.nb = Math.floor(delta / unitData.nbSeconds);
delta -= unitData.nb * unitData.nbSeconds;
}
},
//--------------------------------------------------------------------------
// Canvas drawing methods
//--------------------------------------------------------------------------
/**
* Erases the canvas.
*
* @private
* @param {RenderingContext} ctx - Context of the canvas
*/
_clearCanvas: function (ctx) {
ctx.clearRect(0, 0, this.size, this.size);
},
/**
* Draws a text into the canvas.
*
* @private
* @param {HTMLCanvasElement} canvas
* @param {string} textNb - text to display in the center of the canvas, in big
* @param {string} textUnit - text to display bellow `textNb` in small
* @param {boolean} full - if true, the shape will be drawn up to the progressbar
*/
_drawText: function (canvas, textNb, textUnit, full = false) {
const ctx = canvas.getContext("2d");
const nbSize = this.size / 4;
ctx.font = `${nbSize}px Arial`;
ctx.fillStyle = this.textColor;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(textNb, canvas.width / 2, canvas.height / 2);
const unitSize = this.size / 12;
ctx.font = `${unitSize}px Arial`;
ctx.fillText(textUnit, canvas.width / 2, canvas.height / 2 + nbSize / 1.5, this.width);
if (this.layout === 'boxes' && this.layoutBackground !== 'none' && this.progressBarStyle === 'none') {
let barWidth = this.size / (this.progressBarWeight === 'thin' ? 31 : 10);
if (full) {
barWidth = 0;
}
ctx.beginPath();
ctx.moveTo(barWidth, this.size / 2);
ctx.lineTo(this.width - barWidth, this.size / 2);
ctx.stroke();
}
},
/**
* Draws a plain shape into the canvas.
*
* @private
* @param {RenderingContext} ctx - Context of the canvas
* @param {boolean} full - if true, the shape will be drawn up to the progressbar
*/
_drawBgShape: function (ctx, full = false) {
ctx.fillStyle = this.layoutBackgroundColor;
ctx.beginPath();
if (this.layout === 'circle') {
let rayon = this.size / 2;
if (this.progressBarWeight === 'thin') {
rayon -= full ? this.size / 29 : this.size / 15;
} else {
rayon -= full ? 0 : this.size / 10;
}
ctx.arc(this.size / 2, this.size / 2, rayon, 0, Math.PI * 2);
ctx.fill();
} else if (this.layout === 'boxes') {
let barWidth = this.size / (this.progressBarWeight === 'thin' ? 31 : 10);
if (full) {
barWidth = 0;
}
ctx.fillStyle = this.layoutBackgroundColor;
ctx.rect(barWidth, barWidth, this.width - barWidth * 2, this.size - barWidth * 2);
ctx.fill();
const gradient = ctx.createLinearGradient(0, this.width, 0, 0);
gradient.addColorStop(0, '#ffffff24');
gradient.addColorStop(1, this.layoutBackgroundColor);
ctx.fillStyle = gradient;
ctx.rect(barWidth, barWidth, this.width - barWidth * 2, this.size - barWidth * 2);
ctx.fill();
$(ctx.canvas).css({'border-radius': '8px'});
}
},
/**
* Draws a progress bar around the countdown shape.
*
* @private
* @param {RenderingContext} ctx - Context of the canvas
* @param {string} nbUnit - how many unit should fill progress bar
* @param {string} totalUnit - number of unit to do a complete progress bar
* @param {boolean} thinLine - if true, the progress bar will be thiner
*/
_drawProgressBar: function (ctx, nbUnit, totalUnit, thinLine) {
ctx.strokeStyle = this.progressBarColor;
ctx.lineWidth = thinLine ? this.size / 35 : this.size / 10;
if (this.layout === 'circle') {
ctx.beginPath();
ctx.arc(this.size / 2, this.size / 2, this.size / 2 - this.size / 20, Math.PI / -2, (Math.PI * 2) * (nbUnit / totalUnit) + (Math.PI / -2));
ctx.stroke();
} else if (this.layout === 'boxes') {
ctx.lineWidth *= 2;
let pc = nbUnit / totalUnit * 100;
// Lines: Top(x1,y1,x2,y2) Right(x1,y1,x2,y2) Bottom(x1,y1,x2,y2) Left(x1,y1,x2,y2)
const linesCoordFuncs = [
(linePc) => [0 + ctx.lineWidth / 2, 0, (this.width - ctx.lineWidth / 2) * linePc / 25 + ctx.lineWidth / 2, 0],
(linePc) => [this.width, 0 + ctx.lineWidth / 2, this.width, (this.size - ctx.lineWidth / 2) * linePc / 25 + ctx.lineWidth / 2],
(linePc) => [this.width - ((this.width - ctx.lineWidth / 2) * linePc / 25) - ctx.lineWidth / 2, this.size, this.width - ctx.lineWidth / 2, this.size],
(linePc) => [0, this.size - ((this.size - ctx.lineWidth / 2) * linePc / 25) - ctx.lineWidth / 2, 0, this.size - ctx.lineWidth / 2],
];
while (pc > 0 && linesCoordFuncs.length) {
const linePc = Math.min(pc, 25);
const lineCoord = (linesCoordFuncs.shift())(linePc);
ctx.beginPath();
ctx.moveTo(lineCoord[0], lineCoord[1]);
ctx.lineTo(lineCoord[2], lineCoord[3]);
ctx.stroke();
pc -= linePc;
}
}
},
/**
* Draws a full lighter background progressbar around the shape.
*
* @private
* @param {RenderingContext} ctx - Context of the canvas
* @param {boolean} thinLine - if true, the progress bar will be thiner
*/
_drawProgressBarBg: function (ctx, thinLine) {
ctx.strokeStyle = this.progressBarColor;
ctx.globalAlpha = 0.2;
ctx.lineWidth = thinLine ? this.size / 35 : this.size / 10;
if (this.layout === 'circle') {
ctx.beginPath();
ctx.arc(this.size / 2, this.size / 2, this.size / 2 - this.size / 20, 0, Math.PI * 2);
ctx.stroke();
} else if (this.layout === 'boxes') {
ctx.lineWidth *= 2;
// Lines: Top(x1,y1,x2,y2) Right(x1,y1,x2,y2) Bottom(x1,y1,x2,y2) Left(x1,y1,x2,y2)
const points = [
[0 + ctx.lineWidth / 2, 0, this.width, 0],
[this.width, 0 + ctx.lineWidth / 2, this.width, this.size],
[0, this.size, this.width - ctx.lineWidth / 2, this.size],
[0, 0, 0, this.size - ctx.lineWidth / 2],
];
while (points.length) {
const point = points.shift();
ctx.beginPath();
ctx.moveTo(point[0], point[1]);
ctx.lineTo(point[2], point[3]);
ctx.stroke();
}
}
ctx.globalAlpha = 1;
},
});
publicWidget.registry.countdown = CountdownWidget;
return CountdownWidget;
});
@@ -0,0 +1,124 @@
odoo.define('website.s_countdown_options', function (require) {
'use strict';
const core = require('web.core');
const options = require('web_editor.snippets.options');
const qweb = core.qweb;
options.registry.countdown = options.Class.extend({
events: _.extend({}, options.Class.prototype.events || {}, {
'click .toggle-edit-message': '_onToggleEndMessageClick',
}),
//--------------------------------------------------------------------------
// Options
//--------------------------------------------------------------------------
/**
* Changes the countdown action at zero.
*
* @see this.selectClass for parameters
*/
endAction: function (previewMode, widgetValue, params) {
this.$target[0].dataset.endAction = widgetValue;
if (widgetValue === 'message') {
if (!this.$target.find('.s_countdown_end_message').length) {
const message = this.endMessage || qweb.render('website.s_countdown.end_message');
this.$target.find('.container').append(message);
}
} else {
const $message = this.$target.find('.s_countdown_end_message').detach();
if ($message.length) {
this.endMessage = $message[0].outerHTML;
}
}
},
/**
* Changes the countdown style.
*
* @see this.selectClass for parameters
*/
layout: function (previewMode, widgetValue, params) {
switch (widgetValue) {
case 'circle':
this.$target[0].dataset.progressBarStyle = 'disappear';
this.$target[0].dataset.progressBarWeight = 'thin';
this.$target[0].dataset.layoutBackground = 'none';
break;
case 'boxes':
this.$target[0].dataset.progressBarStyle = 'none';
this.$target[0].dataset.layoutBackground = 'plain';
break;
case 'clean':
this.$target[0].dataset.progressBarStyle = 'none';
this.$target[0].dataset.layoutBackground = 'none';
break;
case 'text':
this.$target[0].dataset.progressBarStyle = 'none';
this.$target[0].dataset.layoutBackground = 'none';
break;
}
this.$target[0].dataset.layout = widgetValue;
},
//--------------------------------------------------------------------------
// Public
//--------------------------------------------------------------------------
/**
* @override
*/
updateUIVisibility: async function () {
await this._super(...arguments);
const dataset = this.$target[0].dataset;
// End Action UI
this.$el.find('.toggle-edit-message')
.toggleClass('d-none', dataset.endAction !== 'message');
// End Message UI
this.updateUIEndMessage();
},
/**
* @see this.updateUI
*/
updateUIEndMessage: function () {
this.$target.find('.s_countdown_canvas_wrapper')
.toggleClass("d-none", this.showEndMessage === true && this.$target.hasClass("hide-countdown"));
this.$target.find('.s_countdown_end_message')
.toggleClass("d-none", !this.showEndMessage);
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* @override
*/
_computeWidgetState: function (methodName, params) {
switch (methodName) {
case 'endAction':
case 'layout':
return this.$target[0].dataset[methodName];
}
return this._super(...arguments);
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* @private
*/
_onToggleEndMessageClick: function () {
this.showEndMessage = !this.showEndMessage;
this.$el.find(".toggle-edit-message")
.toggleClass('text-primary', this.showEndMessage);
this.updateUIEndMessage();
this.trigger_up('cover_update');
},
});
});
+1
View File
@@ -110,6 +110,7 @@
<script type="text/javascript" src="/website/static/src/js/editor/snippets.options.js"/>
<script type="text/javascript" src="/website/static/src/snippets/s_facebook_page/options.js"/>
<script type="text/javascript" src="/website/static/src/snippets/s_image_gallery/options.js"/>
<script type="text/javascript" src="/website/static/src/snippets/s_countdown/options.js"/>
<script type="text/javascript" src="/website/static/src/js/editor/wysiwyg_multizone.js"/>
<script type="text/javascript" src="/website/static/src/js/editor/wysiwyg_multizone_translate.js"/>
@@ -0,0 +1,78 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="s_countdown" name="Countdown">
<section class="s_countdown"
data-display="dhms" data-end-action="nothing" data-size="175"
t-att-data-end-time="datetime.datetime.now().timestamp() + 228307"
data-layout="circle" data-layout-background="none"
data-progress-bar-style="surrounded" data-progress-bar-weight="thin"
data-text-color="alpha" data-layout-background-color="400" data-progress-bar-color="alpha">
<div class="container">
<div class="s_countdown_canvas_wrapper text-center pt8 pb8"/>
</div>
</section>
</template>
<template id="s_countdown_options" inherit_id="website.snippet_options">
<xpath expr="." position="inside">
<div data-js="countdown" data-selector=".s_countdown">
<we-datetimepicker string="Due Date" data-select-data-attribute="" data-attribute-name="endTime"/>
<we-row>
<we-select string="What should happen at the end?" data-no-preview="true">
<we-button data-end-action="nothing" data-name="no_end_action_opt">Nothing</we-button>
<we-button data-end-action="redirect" data-name="redirect_end_action_opt">Redirect</we-button>
<we-button data-end-action="message">Show Message</we-button>
</we-select>
<we-button class="far fa-edit fa-fw align-self-end toggle-edit-message" title="Edit Message"/>
</we-row>
<we-checkbox string="Hide countdown at the end" data-dependencies="!no_end_action_opt"
data-select-class="hide-countdown"/>
<we-urlpicker string="URL" data-dependencies="redirect_end_action_opt"
data-select-data-attribute="/my-awesome-page" data-attribute-name="redirectUrl"/>
<we-select string="Size" data-attribute-name="size">
<we-button data-select-data-attribute="80">Small</we-button>
<we-button data-select-data-attribute="120">Medium</we-button>
<we-button data-select-data-attribute="175">Large</we-button>
</we-select>
<we-select string="Display" data-attribute-name="display">
<we-button data-select-data-attribute="d">D</we-button>
<we-button data-select-data-attribute="dhm">D - H - M</we-button>
<we-button data-select-data-attribute="dhms">D - H - M - S</we-button>
</we-select>
<we-colorpicker string="Text Color" data-attribute-name="textColor" data-select-data-attribute=""/>
<we-select string="Layout">
<we-button data-layout="circle" data-name="circle_layout_opt">Circle</we-button>
<we-button data-layout="boxes" data-name="boxes_layout_opt">Boxes</we-button>
<we-button data-layout="clean">Clean</we-button>
<we-button data-layout="text">Text Inline</we-button>
</we-select>
<we-select string="Layout Background" data-attribute-name="layoutBackground" data-dependencies="circle_layout_opt, boxes_layout_opt">
<we-button data-select-data-attribute="inner">Inner</we-button>
<we-button data-select-data-attribute="plain">Plain</we-button>
<we-button data-select-data-attribute="none" data-name="no_background_layout_opt">None</we-button>
</we-select>
<we-colorpicker string="Layout Background Color" data-dependencies="!no_background_layout_opt"
data-attribute-name="layoutBackgroundColor" data-select-data-attribute=""/>
<we-select string="Progress Bar Style" data-attribute-name="progressBarStyle" data-dependencies="circle_layout_opt, boxes_layout_opt">
<we-button data-select-data-attribute="surrounded">Surrounded</we-button>
<we-button data-select-data-attribute="disappear">Disappearing</we-button>
<we-button data-select-data-attribute="none" data-name="no_progressbar_style_opt">None</we-button>
</we-select>
<we-select string="Progress Bar Weight" data-attribute-name="progressBarWeight" data-dependencies="!no_progressbar_style_opt">
<we-button data-select-data-attribute="thin">Thin</we-button>
<we-button data-select-data-attribute="thick">Thick</we-button>
</we-select>
<we-colorpicker string="Progress Bar Color" data-dependencies="!no_progressbar_style_opt"
data-attribute-name="progressBarColor" data-select-data-attribute=""/>
</div>
</xpath>
</template>
<template id="assets_snippet_s_countdown_js_000" inherit_id="website.assets_frontend">
<xpath expr="//script[last()]" position="after">
<script type="text/javascript" src="/website/static/src/snippets/s_countdown/000.js"/>
</xpath>
</template>
</odoo>
@@ -1,19 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="s_countdown" name="Countdown">
<section class="s_countdown"
data-display="dhms" data-end-action="nothing" data-size="175"
t-att-data-end-time="datetime.datetime.now().timestamp() + 228307"
data-layout="circle" data-layout-background="none"
data-progress-bar-style="surrounded" data-progress-bar-weight="thin"
data-text-color="alpha" data-layout-background-color="400" data-progress-bar-color="alpha">
<div class="container">
<div class="s_countdown_canvas_wrapper text-center pt8 pb8"/>
</div>
</section>
</template>
<template id="s_comparisons" name="Comparisons">
<section class="s_comparisons pt48 pb24">
<div class="container">
@@ -969,58 +956,6 @@
<we-checkbox string="Stretch" data-select-class="container|container-fluid" data-no-preview="true"/>
</div>
<!-- DISPLAY | .s_countdown -->
<div data-js="countdown" data-selector=".s_countdown">
<we-datetimepicker string="Due Date" data-select-data-attribute="" data-attribute-name="endTime"/>
<we-row>
<we-select string="What should happen at the end?" data-no-preview="true">
<we-button data-end-action="nothing" data-name="no_end_action_opt">Nothing</we-button>
<we-button data-end-action="redirect" data-name="redirect_end_action_opt">Redirect</we-button>
<we-button data-end-action="message">Show Message</we-button>
</we-select>
<we-button class="far fa-edit fa-fw align-self-end toggle-edit-message" title="Edit Message"/>
</we-row>
<we-checkbox string="Hide countdown at the end" data-dependencies="!no_end_action_opt"
data-select-class="hide-countdown"/>
<we-urlpicker string="URL" data-dependencies="redirect_end_action_opt"
data-select-data-attribute="/my-awesome-page" data-attribute-name="redirectUrl"/>
<we-select string="Size" data-attribute-name="size">
<we-button data-select-data-attribute="80">Small</we-button>
<we-button data-select-data-attribute="120">Medium</we-button>
<we-button data-select-data-attribute="175">Large</we-button>
</we-select>
<we-select string="Display" data-attribute-name="display">
<we-button data-select-data-attribute="d">D</we-button>
<we-button data-select-data-attribute="dhm">D - H - M</we-button>
<we-button data-select-data-attribute="dhms">D - H - M - S</we-button>
</we-select>
<we-colorpicker string="Text Color" data-attribute-name="textColor" data-select-data-attribute=""/>
<we-select string="Layout">
<we-button data-layout="circle" data-name="circle_layout_opt">Circle</we-button>
<we-button data-layout="boxes" data-name="boxes_layout_opt">Boxes</we-button>
<we-button data-layout="clean">Clean</we-button>
<we-button data-layout="text">Text Inline</we-button>
</we-select>
<we-select string="Layout Background" data-attribute-name="layoutBackground" data-dependencies="circle_layout_opt, boxes_layout_opt">
<we-button data-select-data-attribute="inner">Inner</we-button>
<we-button data-select-data-attribute="plain">Plain</we-button>
<we-button data-select-data-attribute="none" data-name="no_background_layout_opt">None</we-button>
</we-select>
<we-colorpicker string="Layout Background Color" data-dependencies="!no_background_layout_opt"
data-attribute-name="layoutBackgroundColor" data-select-data-attribute=""/>
<we-select string="Progress Bar Style" data-attribute-name="progressBarStyle" data-dependencies="circle_layout_opt, boxes_layout_opt">
<we-button data-select-data-attribute="surrounded">Surrounded</we-button>
<we-button data-select-data-attribute="disappear">Disappearing</we-button>
<we-button data-select-data-attribute="none" data-name="no_progressbar_style_opt">None</we-button>
</we-select>
<we-select string="Progress Bar Weight" data-attribute-name="progressBarWeight" data-dependencies="!no_progressbar_style_opt">
<we-button data-select-data-attribute="thin">Thin</we-button>
<we-button data-select-data-attribute="thick">Thick</we-button>
</we-select>
<we-colorpicker string="Progress Bar Color" data-dependencies="!no_progressbar_style_opt"
data-attribute-name="progressBarColor" data-select-data-attribute=""/>
</div>
<!-- Chart Inner -->
<div data-js="InnerChart" string="Chart"
data-selector=".s_chart">