feat: Enhance intro loader with customizable text and exit animations
This commit is contained in:
parent
c747afc842
commit
3a4691d3be
|
|
@ -33,6 +33,7 @@
|
||||||
'expert_theme/static/src/scss/expert_login.scss',
|
'expert_theme/static/src/scss/expert_login.scss',
|
||||||
'expert_theme/static/src/scss/login_minimal.scss',
|
'expert_theme/static/src/scss/login_minimal.scss',
|
||||||
'expert_theme/static/src/js/expert_login_template.js',
|
'expert_theme/static/src/js/expert_login_template.js',
|
||||||
|
'expert_theme/static/src/scss/frontend.scss',
|
||||||
],
|
],
|
||||||
'web.assets_backend': [
|
'web.assets_backend': [
|
||||||
'expert_theme/static/src/fonts/en_fonts.scss',
|
'expert_theme/static/src/fonts/en_fonts.scss',
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,7 @@ class IntroLoaderController(http.Controller):
|
||||||
img_b64 = request.env['ir.config_parameter'].sudo().get_param('intro_loader.image_data')
|
img_b64 = request.env['ir.config_parameter'].sudo().get_param('intro_loader.image_data')
|
||||||
|
|
||||||
if not img_b64:
|
if not img_b64:
|
||||||
# Return a 1x1 transparent pixel or default Odoo logo if none set
|
return request.redirect('/expert_theme/static/src/img/logo.webp')
|
||||||
return request.redirect('/web/static/img/logo2.png')
|
|
||||||
|
|
||||||
image_data = base64.b64decode(img_b64)
|
image_data = base64.b64decode(img_b64)
|
||||||
headers = [('Content-Type', 'image/png')]
|
headers = [('Content-Type', 'image/png')]
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,17 @@ class ResConfigSettings(models.TransientModel):
|
||||||
# Text Configuration
|
# Text Configuration
|
||||||
intro_loader_text = fields.Char(string="Loader Text", config_parameter='intro_loader.text', default="Loading...")
|
intro_loader_text = fields.Char(string="Loader Text", config_parameter='intro_loader.text', default="Loading...")
|
||||||
intro_loader_show_text = fields.Boolean(string="Show Text", config_parameter='intro_loader.show_text')
|
intro_loader_show_text = fields.Boolean(string="Show Text", config_parameter='intro_loader.show_text')
|
||||||
|
# Text Styling
|
||||||
|
intro_loader_text_color = fields.Char(
|
||||||
|
string="Text Color",
|
||||||
|
config_parameter='intro_loader.text_color',
|
||||||
|
default="#6c757d" # Standard Bootstrap muted gray
|
||||||
|
)
|
||||||
|
intro_loader_text_size = fields.Integer(
|
||||||
|
string="Text Size (px)",
|
||||||
|
config_parameter='intro_loader.text_size',
|
||||||
|
default=24
|
||||||
|
)
|
||||||
|
|
||||||
# Spinner Configuration
|
# Spinner Configuration
|
||||||
intro_loader_show_spinner = fields.Boolean(string="Show Spinner", config_parameter='intro_loader.show_spinner')
|
intro_loader_show_spinner = fields.Boolean(string="Show Spinner", config_parameter='intro_loader.show_spinner')
|
||||||
|
|
@ -21,6 +32,20 @@ class ResConfigSettings(models.TransientModel):
|
||||||
('none', 'Static')
|
('none', 'Static')
|
||||||
], string="Image Animation", config_parameter='intro_loader.animation', default='pulse')
|
], string="Image Animation", config_parameter='intro_loader.animation', default='pulse')
|
||||||
|
|
||||||
|
# Exit Animation Configuration
|
||||||
|
intro_loader_exit_type = fields.Selection([
|
||||||
|
('fade', 'Fade Out'),
|
||||||
|
('slide_up', 'Slide Up (Curtain)'),
|
||||||
|
('zoom_out', 'Zoom Out'),
|
||||||
|
('circle_mask', 'Circle Mask')
|
||||||
|
], string="Exit Animation", config_parameter='intro_loader.exit_type', default='fade')
|
||||||
|
|
||||||
|
intro_loader_exit_duration = fields.Float(
|
||||||
|
string="Exit Duration (Seconds)",
|
||||||
|
config_parameter='intro_loader.exit_duration',
|
||||||
|
default=0.8
|
||||||
|
)
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def get_values(self):
|
def get_values(self):
|
||||||
res = super(ResConfigSettings, self).get_values()
|
res = super(ResConfigSettings, self).get_values()
|
||||||
|
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 102 KiB |
|
|
@ -9,26 +9,23 @@ patch(WebClient.prototype, {
|
||||||
super.setup();
|
super.setup();
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// Select the loader element
|
|
||||||
const loader = document.getElementById("o_intro_loader");
|
const loader = document.getElementById("o_intro_loader");
|
||||||
|
|
||||||
if (loader) {
|
if (loader) {
|
||||||
// Optional: Check sessionStorage if you want it ONCE per browser session
|
// Read duration from HTML attribute (default to 800ms if missing)
|
||||||
// const hasLoaded = sessionStorage.getItem('odoo_intro_shown');
|
const duration = parseInt(loader.dataset.durationMs) || 800;
|
||||||
|
|
||||||
// Add the hidden class to trigger CSS transition
|
// Add delay for visual stability (optional 500ms)
|
||||||
// We add a small delay (e.g., 500ms) to ensure the UI is visually stable behind it
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
loader.classList.add("o_hidden");
|
// Trigger CSS animation
|
||||||
|
loader.classList.add("o_hiddens");
|
||||||
|
|
||||||
// Cleanup DOM after animation finishes (optional but cleaner)
|
// Remove from DOM exactly when animation finishes
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
loader.remove();
|
loader.remove();
|
||||||
}, 1000);
|
}, duration + 100); // +100ms buffer
|
||||||
|
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
// sessionStorage.setItem('odoo_intro_shown', 'true');
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
.o_loader_content{
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
@ -6,7 +6,10 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
transition: opacity 0.8s ease-out, visibility 0.8s;
|
|
||||||
|
/* Default Transition Base */
|
||||||
|
transition-timing-function: ease-in-out;
|
||||||
|
transition-duration: var(--exit-duration, 0.8s); /* Use dynamic var */
|
||||||
|
|
||||||
.o_loader_content {
|
.o_loader_content {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -33,10 +36,45 @@
|
||||||
animation: loader-spin 1s linear infinite;
|
animation: loader-spin 1s linear infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.o_hidden {
|
// &.o_hiddens {
|
||||||
|
// opacity: 0;
|
||||||
|
// visibility: hidden;
|
||||||
|
// pointer-events: none;
|
||||||
|
// }
|
||||||
|
|
||||||
|
/* 1. Fade Out */
|
||||||
|
&.exit-fade {
|
||||||
|
transition-property: opacity, visibility;
|
||||||
|
&.o_hiddens {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
visibility: hidden;
|
// visibility: hidden;
|
||||||
pointer-events: none;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 2. Slide Up (Curtain) */
|
||||||
|
&.exit-slide_up {
|
||||||
|
transition-property: transform;
|
||||||
|
&.o_hiddens {
|
||||||
|
transform: translateY(-100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 3. Zoom Out */
|
||||||
|
&.exit-zoom_out {
|
||||||
|
transition-property: opacity, transform;
|
||||||
|
&.o_hiddens {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(1.5); /* Expands out while fading */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 4. Circle Mask (Advanced CSS Clip Path) */
|
||||||
|
&.exit-circle_mask {
|
||||||
|
transition-property: clip-path;
|
||||||
|
clip-path: circle(150% at 50% 50%);
|
||||||
|
&.o_hiddens {
|
||||||
|
clip-path: circle(0% at 50% 50%);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,24 +6,38 @@
|
||||||
<field name="inherit_id" ref="base.res_config_settings_view_form" />
|
<field name="inherit_id" ref="base.res_config_settings_view_form" />
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<xpath expr="//form" position="inside">
|
<xpath expr="//form" position="inside">
|
||||||
<app data-string="Odex Intro Loader" string="Odex Intro Loader"
|
<app data-string="Odex Theme" string="Odex Theme"
|
||||||
name="expert_theme">
|
name="expert_theme">
|
||||||
<block title="Intro Loader" id="intro_loader_settings">
|
<block title="Intro Loader" id="intro_loader_settings">
|
||||||
<setting id="loader_global_enable"
|
<setting id="loader_global_enable"
|
||||||
help="Show a full screen loader on page load.">
|
help="Show a full screen loader on page load.">
|
||||||
<field name="intro_loader_active" />
|
<field name="intro_loader_active" />
|
||||||
</setting>
|
</setting>
|
||||||
|
<setting string="Exit Animation" invisible="not intro_loader_active"
|
||||||
<setting invisible="not intro_loader_active">
|
help="Customize how the loader disappears when Odoo is ready.">
|
||||||
|
<group>
|
||||||
|
<field name="intro_loader_exit_type" />
|
||||||
|
<field name="intro_loader_exit_duration" />
|
||||||
|
</group>
|
||||||
|
</setting>
|
||||||
|
<setting string="Visuals" invisible="not intro_loader_active"
|
||||||
|
help="Customize the appearance of the intro loader.">
|
||||||
<group>
|
<group>
|
||||||
<field name="intro_loader_image" widget="image" class="oe_avatar" />
|
<field name="intro_loader_image" widget="image" class="oe_avatar" />
|
||||||
<field name="intro_loader_animation" />
|
<field name="intro_loader_animation" />
|
||||||
</group>
|
</group>
|
||||||
|
</setting>
|
||||||
|
<setting string="Content" invisible="not intro_loader_active"
|
||||||
|
help="Customize the content of the intro loader.">
|
||||||
<group>
|
<group>
|
||||||
<field name="intro_loader_show_spinner" />
|
<field name="intro_loader_show_spinner" />
|
||||||
<field name="intro_loader_show_text" />
|
<field name="intro_loader_show_text" />
|
||||||
<field name="intro_loader_text"
|
<field name="intro_loader_text"
|
||||||
invisible="not intro_loader_show_text" />
|
invisible="not intro_loader_show_text" />
|
||||||
|
<field name="intro_loader_text_color" widget="color"
|
||||||
|
invisible="not intro_loader_show_text" />
|
||||||
|
<field name="intro_loader_text_size"
|
||||||
|
invisible="not intro_loader_show_text" />
|
||||||
</group>
|
</group>
|
||||||
</setting>
|
</setting>
|
||||||
</block>
|
</block>
|
||||||
|
|
|
||||||
|
|
@ -2,25 +2,43 @@
|
||||||
<odoo>
|
<odoo>
|
||||||
<template id="layout_intro_loader" inherit_id="web.layout">
|
<template id="layout_intro_loader" inherit_id="web.layout">
|
||||||
<xpath expr="//body" position="before">
|
<xpath expr="//body" position="before">
|
||||||
<t t-set="loader_active" t-value="request.env['ir.config_parameter'].sudo().get_param('intro_loader.active')"/>
|
<t t-set="loader_active"
|
||||||
|
t-value="request.env['ir.config_parameter'].sudo().get_param('intro_loader.active')" />
|
||||||
|
|
||||||
<t t-if="loader_active">
|
<t t-if="loader_active">
|
||||||
<t t-set="anim_type" t-value="request.env['ir.config_parameter'].sudo().get_param('intro_loader.animation') or 'pulse'"/>
|
<t t-set="text_color"
|
||||||
<t t-set="show_text" t-value="request.env['ir.config_parameter'].sudo().get_param('intro_loader.show_text')"/>
|
t-value="request.env['ir.config_parameter'].sudo().get_param('intro_loader.text_color') or '#6c757d'" />
|
||||||
<t t-set="loader_text" t-value="request.env['ir.config_parameter'].sudo().get_param('intro_loader.text')"/>
|
<t t-set="text_size"
|
||||||
<t t-set="show_spinner" t-value="request.env['ir.config_parameter'].sudo().get_param('intro_loader.show_spinner')"/>
|
t-value="request.env['ir.config_parameter'].sudo().get_param('intro_loader.text_size') or '24'" />
|
||||||
|
|
||||||
|
<t t-set="anim_type"
|
||||||
|
t-value="request.env['ir.config_parameter'].sudo().get_param('intro_loader.animation') or 'pulse'" />
|
||||||
|
<t t-set="show_text"
|
||||||
|
t-value="request.env['ir.config_parameter'].sudo().get_param('intro_loader.show_text')" />
|
||||||
|
<t t-set="loader_text"
|
||||||
|
t-value="request.env['ir.config_parameter'].sudo().get_param('intro_loader.text')" />
|
||||||
|
<t t-set="show_spinner"
|
||||||
|
t-value="request.env['ir.config_parameter'].sudo().get_param('intro_loader.show_spinner')" />
|
||||||
|
|
||||||
|
<t t-set="exit_type"
|
||||||
|
t-value="request.env['ir.config_parameter'].sudo().get_param('intro_loader.exit_type') or 'fade'" />
|
||||||
|
<t t-set="exit_duration"
|
||||||
|
t-value="request.env['ir.config_parameter'].sudo().get_param('intro_loader.exit_duration') or 0.8" />
|
||||||
|
|
||||||
|
<div id="o_intro_loader"
|
||||||
|
t-attf-class="o_intro_loader exit-#{exit_type}"
|
||||||
|
t-attf-style="--exit-duration: #{exit_duration}s;"
|
||||||
|
t-att-data-duration-ms="float(exit_duration) * 1000">
|
||||||
|
|
||||||
<div id="o_intro_loader" class="o_intro_loader">
|
|
||||||
<div class="o_loader_content">
|
<div class="o_loader_content">
|
||||||
<img src="/intro_loader/image"
|
<img src="/intro_loader/image"
|
||||||
t-attf-class="o_loader_logo mb-4 anim-#{anim_type}"/>
|
t-attf-class="o_loader_logo mb-4 anim-#{anim_type}" />
|
||||||
|
|
||||||
<t t-if="show_spinner">
|
<t t-if="show_spinner">
|
||||||
<div class="o_loader_spinner"></div>
|
<div class="o_loader_spinner"></div>
|
||||||
</t>
|
</t>
|
||||||
|
|
||||||
<t t-if="show_text">
|
<t t-if="show_text">
|
||||||
<h3 class="text-muted mt-3" t-esc="loader_text"/>
|
<h3 class="text-muted mt-3" t-esc="loader_text"
|
||||||
|
t-attf-style="color: #{text_color}; font-size: #{text_size}px; font-weight: 500;" />
|
||||||
</t>
|
</t>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue