[MOV] website, *: merge the website_animate app into the website app

*: tools

The app received many fixes and its code was improved and cleaned. It
was about time to include it directly into the website features.

task-2215118

closes odoo/odoo#74697

Related: odoo/design-themes#478
Related: odoo/upgrade#2709
Signed-off-by: Quentin Smetz (qsm) <qsm@odoo.com>
This commit is contained in:
qsm-odoo
2021-08-05 14:59:10 +00:00
parent 0885212a83
commit ebf8d67485
5 changed files with 713 additions and 1 deletions
@@ -1082,6 +1082,165 @@ registry.BottomFixedElement = publicWidget.Widget.extend({
},
});
registry.WebsiteAnimate = publicWidget.Widget.extend({
selector: '#wrapwrap',
disabledInEditableMode: false,
offsetRatio: 0.3, // Dynamic offset ratio: 0.3 = (element's height/3)
offsetMin: 10, // Minimum offset for small elements (in pixels)
/**
* @override
*/
start() {
this.lastScroll = 0;
this.$scrollingElement = $().getScrollingElement(this.ownerDocument);
// By default, elements are hidden by the css of o_animate.
// Render elements and trigger the animation then pause it in state 0.
this.$animatedElements = this.$target.find('.o_animate');
_.each(this.$animatedElements, el => {
this._resetAnimation($(el));
});
// Then we render all the elements, the ones which are invisible
// in state 0 (like fade_in for example) will stay invisible.
this.$animatedElements.css("visibility", "visible");
// We use addEventListener instead of jQuery because we need 'capture'.
// Setting capture to true allows to take advantage of event bubbling
// for events that otherwise don’t support it. (e.g. useful when
// scrolling a modal)
this.__onScrollWebsiteAnimate = _.throttle(this._onScrollWebsiteAnimate.bind(this), 200);
this.$scrollingElement[0].addEventListener('scroll', this.__onScrollWebsiteAnimate, {capture: true});
$(window).on('resize.o_animate, shown.bs.modal.o_animate, slid.bs.carousel.o_animate', () => {
this.windowsHeight = $(window).height();
this.$scrollingElement[0].dispatchEvent(new Event('scroll'));
}).trigger("resize");
return this._super(...arguments);
},
/**
* @override
*/
destroy() {
this._super(...arguments);
this.$target.find('.o_animate')
.removeClass('o_animating o_animated o_animate_preview')
.css({
'animation-name': '',
'animation-play-state': '',
'visibility': '',
});
$(window).off('.o_animate');
this.$scrollingElement[0].removeEventListener('scroll', this.__onScrollWebsiteAnimate, {capture: true});
this.$scrollingElement[0].classList.remove('o_wanim_overflow_x_hidden');
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* Starts animation and/or update element's state.
*
* @private
* @param {jQuery} $el
*/
_startAnimation($el) {
// Forces the browser to redraw using setTimeout.
setTimeout(() => {
this._toggleOverflowXHidden(true);
$el
.css({"animation-play-state": "running"})
.addClass("o_animating")
.one('webkitAnimationEnd oanimationend msAnimationEnd animationend', () => {
$el.addClass("o_animated").removeClass("o_animating");
this._toggleOverflowXHidden(false);
$(window).trigger("resize");
});
});
},
/**
* @private
* @param {jQuery} $el
*/
_resetAnimation($el) {
const animationName = $el.css("animation-name");
$el.css({"animation-name": "dummy-none", "animation-play-state": ""})
.removeClass("o_animated o_animating");
this._toggleOverflowXHidden(false);
// trigger a DOM reflow
void $el[0].offsetWidth;
$el.css({'animation-name': animationName , 'animation-play-state': 'paused'});
},
/**
* Shows/hides the horizontal scrollbar (on the #wrapwrap).
*
* @private
* @param {Boolean} add
*/
_toggleOverflowXHidden(add) {
if (add) {
this.$scrollingElement[0].classList.add('o_wanim_overflow_x_hidden');
} else if (!this.$scrollingElement.find('.o_animating').length) {
this.$scrollingElement[0].classList.remove('o_wanim_overflow_x_hidden');
}
},
/**
* Gets element top offset by not taking CSS transforms into calculations.
*
* @private
* @param {Element} el
*/
_getElementOffsetTop(el) {
// Loop through the DOM tree and add its parent's offset to get page offset.
var top = 0;
do {
top += el.offsetTop || 0;
el = el.offsetParent;
} while (el);
return top;
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* @private
* @param {Event} ev
*/
_onScrollWebsiteAnimate(ev) {
const scroll = $(ev.currentTarget).scrollTop();
// Handle reverse scrolling
const direction = (scroll < this.lastScroll) ? -1 : 1;
this.lastScroll = scroll;
_.each(this.$target.find('.o_animate'), el => {
const $el = $(el);
const elHeight = $el.height();
const elOffset = direction * Math.max((elHeight * this.offsetRatio), this.offsetMin);
const state = $el.css("animation-play-state");
// We need to offset for the change in position from some animation.
// So we get the top value by not taking CSS transforms into calculations.
const $openModal = this.$target.find('.modal:visible');
const scrollTop = $openModal.length ? $openModal.scrollTop() : this.$scrollingElement.scrollTop();
const elTop = this._getElementOffsetTop($el[0]) - scrollTop;
const visible = this.windowsHeight > (elTop + elOffset) && 0 < (elTop + elHeight - elOffset);
if (visible && state === "paused") {
$el.addClass("o_visible");
this._startAnimation($el);
} else if (!visible && $el.hasClass("o_animate_both_scroll") && state === "running") {
$el.removeClass("o_visible");
this._resetAnimation($el);
}
});
},
});
return {
Widget: publicWidget.Widget,
Animation: Animation,
@@ -3037,6 +3037,45 @@ options.registry.ConditionalVisibility = options.Class.extend({
},
});
options.registry.WebsiteAnimate = options.Class.extend({
//--------------------------------------------------------------------------
// Options
//--------------------------------------------------------------------------
/**
* @override
*/
async selectClass(previewMode, widgetValue, params) {
await this._super(...arguments);
if (params.isAnimationTypeSelection) {
this._forceAnimation();
this.$target.toggleClass('o_animate_preview o_animate', !!widgetValue);
}
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* @private
*/
_forceAnimation() {
const $scrollingElement = $().getScrollingElement();
this.$target.css('animation-name', 'dummy');
// Trigger a DOM reflow.
void this.$target[0].offsetWidth;
this.$target.addClass('o_animating');
$scrollingElement[0].classList.add('o_wanim_overflow_x_hidden');
this.$target.css('animation-name', '');
this.$target.one('webkitAnimationEnd oanimationend msAnimationEnd animationend', () => {
$scrollingElement[0].classList.remove('o_wanim_overflow_x_hidden');
this.$target.removeClass('o_animating');
});
},
});
return {
UrlPickerUserValueWidget: UrlPickerUserValueWidget,
FontFamilyPickerUserValueWidget: FontFamilyPickerUserValueWidget,
+469
View File
@@ -1429,3 +1429,472 @@ ul.o_checklist > li.o_checked::after {
// hide them if a modal is open.
display: none !important;
}
//------------------------------------------------------------------------------
// Website Animate
//------------------------------------------------------------------------------
.o_animate {
animation-duration: 1s;
animation-fill-mode: both;
transform: translate3d(0,0,0); // force GPU acceleration
backface-visibility: hidden; // avoid flickering
text-rendering: geometricPrecision; // take care of animated titles
visibility: hidden;
&:not(.o_animating) {
transform: none !important;
}
}
.o_animate_preview {
visibility: visible;
}
.o_wanim_overflow_x_hidden {
overflow-x: hidden !important;
}
@keyframes o_anim_bounce_in {
0%, 20%, 40%, 60%, 80%, 100% {
transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
}
0% {
opacity: 0;
transform: scale(0.3);
}
20% {
transform: scale(1.1);
}
40% {
transform: scale(0.9);
}
60% {
opacity: 1;
transform: scale(1.03);
}
80% {
transform: scale(0.97);
}
100% {
opacity: 1;
transform: scale(1);
}
}
.o_anim_bounce_in {
animation-name: o_anim_bounce_in;
}
@keyframes o_anim_bounce_in_down {
0%, 20%, 40%, 60%, 80%, 100% {
transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
}
0% {
opacity: 0;
transform: translate(0, -3000px);
}
60% {
opacity: 1;
transform: translate(0, 25px);
}
75% {
transform: translate(0, -10px);
}
90% {
transform: translate(0, 5px);
}
100% {
transform: translate(0, 0);
}
}
.o_anim_bounce_in_down {
animation-name: o_anim_bounce_in_down;
}
@keyframes o_anim_bounce_in_left {
0%, 20%, 40%, 60%, 80%, 100% {
transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
}
0% {
opacity: 0;
transform: translate(-3000px, 0);
}
60% {
opacity: 1;
transform: translate(25px, 0);
}
75% {
transform: translate(-10px, 0);
}
90% {
transform: translate(5px, 0);
}
100% {
transform: translate(0, 0);
}
}
.o_anim_bounce_in_left {
animation-name: o_anim_bounce_in_left;
}
@keyframes o_anim_bounce_in_right {
0%, 20%, 40%, 60%, 80%, 100% {
transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
}
0% {
opacity: 0;
transform: translate(3000px, 0);
}
60% {
opacity: 1;
transform: translate(-25px, 0);
}
75% {
transform: translate(10px, 0);
}
90% {
transform: translate(-5px, 0);
}
100% {
transform: translate(0, 0);
}
}
.o_anim_bounce_in_right {
animation-name: o_anim_bounce_in_right;
}
@keyframes o_anim_fade_in {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
.o_anim_fade_in {
animation-name: o_anim_fade_in;
}
@keyframes o_anim_fade_in_down {
0% {
opacity: 0;
transform: translate(0, -100%);
}
100% {
opacity: 1;
transform: translate(0, 0);
}
}
.o_anim_fade_in_down {
animation-name: o_anim_fade_in_down;
}
@keyframes o_anim_fade_in_left {
0% {
opacity: 0;
transform: translate(-100%, 0);
}
100% {
opacity: 1;
transform: translate(0, 0);
}
}
.o_anim_fade_in_left {
animation-name: o_anim_fade_in_left;
}
@keyframes o_anim_fade_in_right {
0% {
opacity: 0;
transform: translate(100%, 0);
}
100% {
opacity: 1;
transform: translate(0, 0);
}
}
.o_anim_fade_in_right {
animation-name: o_anim_fade_in_right;
}
@keyframes o_anim_fade_in_up {
0% {
opacity: 0;
transform: translate(0, 100%);
}
100% {
opacity: 1;
transform: translate(0, 0);
}
}
.o_anim_fade_in_up {
animation-name: o_anim_fade_in_up;
}
@keyframes o_anim_fade_out {
0% { opacity: 1; }
100% { opacity: 0; }
}
.o_anim_fade_out {
animation-name: o_anim_fade_out;
}
@keyframes o_anim_rotate_in {
0% {
opacity: 0;
transform: rotate(-200deg);
}
100% {
opacity: 1;
transform: rotate(0);
}
}
.o_anim_rotate_in {
animation-name: o_anim_rotate_in;
}
@keyframes o_anim_rotate_in_down_left {
0% {
opacity: 0;
transform-origin: left bottom;
transform: rotate(-45deg);
}
100% {
opacity: 1;
transform-origin: left bottom;
transform: rotate(0);
}
}
.o_anim_rotate_in_down_left {
animation-name: o_anim_rotate_in_down_left;
}
@keyframes o_anim_rotate_in_down_right {
0% {
opacity: 0;
transform-origin: right bottom;
transform: rotate(45deg);
}
100% {
opacity: 1;
transform-origin: right bottom;
transform: rotate(0);
}
}
.o_anim_rotate_in_down_right {
animation-name: o_anim_rotate_in_down_right;
}
@keyframes o_anim_zoom_in {
0% {
opacity: 0;
transform: scale(0.3);
}
50% {
opacity: 1;
}
}
.o_anim_zoom_in {
animation-name: o_anim_zoom_in;
}
@keyframes o_anim_zoom_in_down {
0% {
opacity: 0;
transform: scale3d(0.1, 0.1, 0.1) translate3d(0, -1000px, 0);
animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
}
60% {
opacity: 1;
transform: scale3d(0.475, 0.475, 0.475) translate3d(0, 60px, 0);
animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
}
}
.o_anim_zoom_in_down {
animation-name: o_anim_zoom_in_down;
}
@keyframes o_anim_zoom_in_left {
0% {
opacity: 0;
transform: scale3d(0.1, 0.1, 0.1) translate3d(-1000px, 0, 0);
animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
}
60% {
opacity: 1;
transform: scale3d(0.475, 0.475, 0.475) translate3d(10px, 0, 0);
animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
}
}
.o_anim_zoom_in_left {
animation-name: o_anim_zoom_in_left;
}
@keyframes o_anim_zoom_in_right {
0% {
opacity: 0;
transform: scale3d(0.1, 0.1, 0.1) translate3d(1000px, 0, 0);
animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
}
60% {
opacity: 1;
transform: scale3d(0.475, 0.475, 0.475) translate3d(-10px, 0, 0);
animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
}
}
.o_anim_zoom_in_right {
animation-name: o_anim_zoom_in_right;
}
@keyframes o_anim_flash {
0%, 50%, 100% {
opacity: 1;
}
25%, 75% {
opacity: 0;
}
}
.o_anim_flash {
animation-name: o_anim_flash;
}
@keyframes o_anim_pulse {
0% {
transform: scale(1);
}
50% {
transform: scale(1.05);
}
100% {
transform: scale(1);
}
}
.o_anim_pulse {
animation-name: o_anim_pulse;
}
@keyframes o_anim_shake {
0%, 100% {
transform: translate(0, 0);
}
10%, 30%, 50%, 70%, 90% {
transform: translate(-10px, 0);
}
20%, 40%, 60%, 80% {
transform: translate(10px, 0);
}
}
.o_anim_shake {
animation-name: o_anim_shake;
}
@keyframes o_anim_tada {
0% {
transform: scale3d(1, 1, 1);
}
10%, 20% {
transform: scale3d(0.9, 0.9, 0.9) rotate3d(0, 0, 1, -3deg);
}
30%, 50%, 70%, 90% {
transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg);
}
40%, 60%, 80% {
transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg);
}
100% {
transform: scale3d(1, 1, 1);
}
}
.o_anim_tada {
animation-name: o_anim_tada;
}
@keyframes o_anim_flip_in_x {
0% {
perspective: 400px;
transform: rotateX(90deg);
transition-timing-function: ease-in;
opacity: 0;
}
40% {
perspective: 400px;
transform: rotateX(-20deg);
transition-timing-function: ease-in;
}
60% {
perspective: 400px;
transform: rotateX(10deg);
opacity: 1;
}
80% {
perspective: 400px;
transform: rotateX(-5deg);
}
100% {
perspective: 400px;
}
}
.o_anim_flip_in_x {
backface-visibility: visible;
animation-name: o_anim_flip_in_x;
}
@keyframes o_anim_flip_in_y {
0% {
perspective: 400px;
transform: rotateY(90deg);
transition-timing-function: ease-in;
opacity: 0;
}
40% {
perspective: 400px;
transform: rotateY(-20deg);
transition-timing-function: ease-in;
}
60% {
perspective: 400px;
transform: rotateY(10deg);
opacity: 1;
}
80% {
perspective: 400px;
transform: rotateY(-5deg);
}
100% {
perspective: 400px;
}
}
.o_anim_flip_in_y {
backface-visibility: visible;
animation-name: o_anim_flip_in_y;
}
// Compatibility <= 13.0
.o_anim_dur500 {
animation-duration: 500ms;
}
.o_anim_dur1500 {
animation-duration: 1500ms;
}
.o_anim_dur2000 {
animation-duration: 2000ms;
}
.o_anim_dur2500 {
animation-duration: 2500ms;
}
.o_anim_dur3000 {
animation-duration: 3000ms;
}
.o_anim_del500 {
animation-delay: 500ms;
}
.o_anim_del1000 {
animation-delay: 1000ms;
}
.o_anim_del1500 {
animation-delay: 1500ms;
}
.o_anim_del2000 {
animation-delay: 2000ms;
}
.o_anim_del2500 {
animation-delay: 2500ms;
}
@@ -1148,6 +1148,51 @@
</we-select>
</div>
<!-- Website Animate -->
<div data-js="WebsiteAnimate"
data-selector=".o_animable, section, img, .fa, [class^='col-md'], .btn"
data-exclude=".o_not-animable">
<we-select string="Animate" data-is-animation-type-selection="true">
<we-button data-select-class="" data-name="no_animation_opt">Disable animation</we-button>
<we-divider/>
<we-button data-select-class="o_anim_fade_in">Fade In</we-button>
<we-button data-select-class="o_anim_fade_in_down">Fade In-Down</we-button>
<we-button data-select-class="o_anim_fade_in_left">Fade In-Left</we-button>
<we-button data-select-class="o_anim_fade_in_right">Fade In-Right</we-button>
<we-button data-select-class="o_anim_fade_in_up">Fade In-Up</we-button>
<we-divider/>
<we-button data-select-class="o_anim_bounce_in">Bounce In</we-button>
<we-button data-select-class="o_anim_bounce_in_down">Bounce In-Down</we-button>
<we-button data-select-class="o_anim_bounce_in_left">Bounce In-Left</we-button>
<we-button data-select-class="o_anim_bounce_in_right">Bounce In-Right</we-button>
<we-divider/>
<we-button data-select-class="o_anim_rotate_in">Rotate In</we-button>
<we-button data-select-class="o_anim_rotate_in_down_left">Rotate In-Down-Left</we-button>
<we-button data-select-class="o_anim_rotate_in_down_right">Rotate In-Down-Right</we-button>
<we-divider/>
<we-button data-select-class="o_anim_zoom_in">Zoom In</we-button>
<we-button data-select-class="o_anim_zoom_in_down">Zoom In-Down</we-button>
<we-button data-select-class="o_anim_zoom_in_left">Zoom In-Left</we-button>
<we-button data-select-class="o_anim_zoom_in_right">Zoom In-Right</we-button>
<we-divider/>
<we-button data-select-class="o_anim_flash">Flash</we-button>
<we-button data-select-class="o_anim_pulse">Pulse</we-button>
<we-button data-select-class="o_anim_shake">Shake</we-button>
<we-button data-select-class="o_anim_tada">Tada</we-button>
<we-divider/>
<we-button data-select-class="o_anim_flip_in_x">Flip-In-X</we-button>
<we-button data-select-class="o_anim_flip_in_y">Flip-In-Y</we-button>
</we-select>
<we-select string="Animation Launch" data-dependencies="!no_animation_opt">
<we-button data-select-class="">First Time Only</we-button>
<we-button data-select-class="o_animate_both_scroll">Every Time</we-button>
</we-select>
<we-input string="Animation Duration" data-dependencies="!no_animation_opt"
data-select-style="0.4s" data-css-property="animation-duration" data-unit="s"/>
<we-input string="Animation Delay" data-dependencies="!no_animation_opt"
data-select-style="0s" data-css-property="animation-delay" data-unit="s"/>
</div>
<!-- Theme options -->
<div data-js="ThemeColors" data-selector="theme-colors" data-no-check="true">
<we-alert class="o_old_color_system_warning d-none">
+1 -1
View File
@@ -19,7 +19,7 @@ DEFAULT_EXCLUDE = [
"migrations/**/*",
]
STANDARD_MODULES = ['web', 'web_enterprise', 'website_animate', 'base']
STANDARD_MODULES = ['web', 'web_enterprise', 'base']
MAX_FILE_SIZE = 25 * 2**20 # 25 MB
class Cloc(object):