Merge pull request #6317 from expsa/std-dynamic-bg-selection
add option to select the /web page bg
This commit is contained in:
commit
0eae7d55e1
|
|
@ -1 +1,3 @@
|
||||||
from .hooks import test_pre_init_hook, test_post_init_hook
|
from .hooks import test_pre_init_hook, test_post_init_hook
|
||||||
|
from . import models
|
||||||
|
from . import controllers
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from . import main
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import base64
|
||||||
|
from odoo import http
|
||||||
|
from odoo.http import request
|
||||||
|
|
||||||
|
|
||||||
|
class BackgroundImageController(http.Controller):
|
||||||
|
|
||||||
|
@http.route('/expert_std_backend_theme/get_background_image', type='json', auth='user')
|
||||||
|
def get_background_image(self):
|
||||||
|
"""Return the background image URL or False if using default"""
|
||||||
|
try:
|
||||||
|
attachment_id = request.env['ir.config_parameter'].sudo().get_param(
|
||||||
|
'expert_std_backend_theme.home_menu_background_image_id', False
|
||||||
|
)
|
||||||
|
if attachment_id:
|
||||||
|
attachment = request.env['ir.attachment'].sudo().browse(int(attachment_id))
|
||||||
|
if attachment.exists() and attachment.datas:
|
||||||
|
# Add write_date as cache buster to force reload when image changes
|
||||||
|
timestamp = attachment.write_date.timestamp() if attachment.write_date else 0
|
||||||
|
return {
|
||||||
|
'url': '/web/image/%s?t=%s' % (attachment_id, int(timestamp)),
|
||||||
|
'has_custom': True
|
||||||
|
}
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return {
|
||||||
|
'url': '/expert_std_backend_theme/static/src/img/bg_app_drawer.png',
|
||||||
|
'has_custom': False
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from . import res_config_settings
|
||||||
|
|
@ -0,0 +1,86 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from odoo import api, fields, models
|
||||||
|
|
||||||
|
|
||||||
|
class ResConfigSettings(models.TransientModel):
|
||||||
|
_inherit = 'res.config.settings'
|
||||||
|
|
||||||
|
home_menu_background_image = fields.Binary(
|
||||||
|
string='Home Menu Background Image',
|
||||||
|
help='Upload a custom background image for the home menu page. If not set, the default image will be used.'
|
||||||
|
)
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def get_values(self):
|
||||||
|
res = super(ResConfigSettings, self).get_values()
|
||||||
|
attachment_id = self.env['ir.config_parameter'].sudo().get_param(
|
||||||
|
'expert_std_backend_theme.home_menu_background_image_id', False
|
||||||
|
)
|
||||||
|
if attachment_id:
|
||||||
|
attachment = self.env['ir.attachment'].sudo().browse(int(attachment_id))
|
||||||
|
if attachment.exists():
|
||||||
|
res.update(home_menu_background_image=attachment.datas)
|
||||||
|
return res
|
||||||
|
|
||||||
|
def set_values(self):
|
||||||
|
super(ResConfigSettings, self).set_values()
|
||||||
|
param = self.env['ir.config_parameter'].sudo()
|
||||||
|
|
||||||
|
if self.home_menu_background_image:
|
||||||
|
# Detect MIME type from image data
|
||||||
|
import base64
|
||||||
|
try:
|
||||||
|
image_data = base64.b64decode(self.home_menu_background_image)
|
||||||
|
# Detect image type from magic bytes
|
||||||
|
if image_data.startswith(b'\x89PNG'):
|
||||||
|
mimetype = 'image/png'
|
||||||
|
extension = 'png'
|
||||||
|
elif image_data.startswith(b'\xFF\xD8\xFF'):
|
||||||
|
mimetype = 'image/jpeg'
|
||||||
|
extension = 'jpg'
|
||||||
|
elif image_data.startswith(b'GIF'):
|
||||||
|
mimetype = 'image/gif'
|
||||||
|
extension = 'gif'
|
||||||
|
else:
|
||||||
|
mimetype = 'image/png' # Default fallback
|
||||||
|
extension = 'png'
|
||||||
|
except Exception:
|
||||||
|
mimetype = 'image/png'
|
||||||
|
extension = 'png'
|
||||||
|
|
||||||
|
# Create or update attachment
|
||||||
|
attachment_id = param.get_param('expert_std_backend_theme.home_menu_background_image_id', False)
|
||||||
|
if attachment_id:
|
||||||
|
attachment = self.env['ir.attachment'].sudo().browse(int(attachment_id))
|
||||||
|
if attachment.exists():
|
||||||
|
attachment.write({
|
||||||
|
'datas': self.home_menu_background_image,
|
||||||
|
'name': 'home_menu_background_image.%s' % extension,
|
||||||
|
'mimetype': mimetype,
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
attachment_id = False
|
||||||
|
else:
|
||||||
|
attachment_id = False
|
||||||
|
|
||||||
|
if not attachment_id:
|
||||||
|
# Create new attachment
|
||||||
|
attachment = self.env['ir.attachment'].sudo().create({
|
||||||
|
'name': 'home_menu_background_image.%s' % extension,
|
||||||
|
'type': 'binary',
|
||||||
|
'datas': self.home_menu_background_image,
|
||||||
|
'mimetype': mimetype,
|
||||||
|
'public': True, # Make it accessible without authentication
|
||||||
|
'res_model': 'res.config.settings',
|
||||||
|
'res_field': 'home_menu_background_image',
|
||||||
|
})
|
||||||
|
param.set_param('expert_std_backend_theme.home_menu_background_image_id', attachment.id)
|
||||||
|
else:
|
||||||
|
# Remove the image - delete attachment and parameter
|
||||||
|
attachment_id = param.get_param('expert_std_backend_theme.home_menu_background_image_id', False)
|
||||||
|
if attachment_id:
|
||||||
|
attachment = self.env['ir.attachment'].sudo().browse(int(attachment_id))
|
||||||
|
# Only delete if it exists and is owned by this module
|
||||||
|
if attachment.exists() and attachment.res_model == 'res.config.settings':
|
||||||
|
attachment.unlink()
|
||||||
|
param.set_param('expert_std_backend_theme.home_menu_background_image_id', False)
|
||||||
|
|
@ -0,0 +1,186 @@
|
||||||
|
odoo.define('expert_std_backend_theme.background', function (require) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var rpc = require('web.rpc');
|
||||||
|
var core = require('web.core');
|
||||||
|
|
||||||
|
// Cache for background image URL to prevent flash
|
||||||
|
var cachedBackgroundUrl = null;
|
||||||
|
var isFetching = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if we're on the home menu page
|
||||||
|
* Home menu is shown when URL hash contains 'home' or is empty/just '#'
|
||||||
|
*/
|
||||||
|
function isHomeMenuPage() {
|
||||||
|
var hash = window.location.hash || '';
|
||||||
|
// Check if hash contains 'home' or is empty/just '#'
|
||||||
|
var isHome = hash.indexOf('home') !== -1 || hash === '' || hash === '#';
|
||||||
|
// Also check if home menu element exists
|
||||||
|
var hasHomeMenu = document.querySelector('.o_home_menu') !== null;
|
||||||
|
return isHome && hasHomeMenu;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch and cache the background image URL
|
||||||
|
*/
|
||||||
|
function fetchBackgroundUrl() {
|
||||||
|
if (cachedBackgroundUrl !== null) {
|
||||||
|
return Promise.resolve(cachedBackgroundUrl);
|
||||||
|
}
|
||||||
|
if (isFetching) {
|
||||||
|
// If already fetching, wait for it
|
||||||
|
return new Promise(function(resolve) {
|
||||||
|
var checkInterval = setInterval(function() {
|
||||||
|
if (cachedBackgroundUrl !== null) {
|
||||||
|
clearInterval(checkInterval);
|
||||||
|
resolve(cachedBackgroundUrl);
|
||||||
|
}
|
||||||
|
}, 50);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
isFetching = true;
|
||||||
|
return rpc.query({
|
||||||
|
route: '/expert_std_backend_theme/get_background_image',
|
||||||
|
}).then(function (result) {
|
||||||
|
isFetching = false;
|
||||||
|
if (result && result.url) {
|
||||||
|
cachedBackgroundUrl = result.url;
|
||||||
|
} else {
|
||||||
|
cachedBackgroundUrl = '/expert_std_backend_theme/static/src/img/bg_app_drawer.png';
|
||||||
|
}
|
||||||
|
return cachedBackgroundUrl;
|
||||||
|
}).catch(function () {
|
||||||
|
isFetching = false;
|
||||||
|
cachedBackgroundUrl = '/expert_std_backend_theme/static/src/img/bg_app_drawer.png';
|
||||||
|
return cachedBackgroundUrl;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the background image for the home menu
|
||||||
|
* Checks for custom uploaded image, falls back to default
|
||||||
|
* Only applies on the actual home menu page
|
||||||
|
*/
|
||||||
|
function setHomeMenuBackground() {
|
||||||
|
var body = document.body;
|
||||||
|
if (!body) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only apply background if we're on the home menu page
|
||||||
|
if (!isHomeMenuPage()) {
|
||||||
|
// Remove background if we're not on home menu
|
||||||
|
if (body.classList.contains('o_home_menu_background')) {
|
||||||
|
body.style.backgroundImage = '';
|
||||||
|
body.style.backgroundPosition = '';
|
||||||
|
body.style.backgroundRepeat = '';
|
||||||
|
body.style.backgroundSize = '';
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only proceed if body has the home menu background class
|
||||||
|
if (!body.classList.contains('o_home_menu_background')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent CSS default from showing - clear it first
|
||||||
|
body.style.backgroundImage = 'none';
|
||||||
|
|
||||||
|
// Use cached URL if available, otherwise fetch
|
||||||
|
if (cachedBackgroundUrl !== null) {
|
||||||
|
// Apply immediately if cached
|
||||||
|
body.style.backgroundImage = 'url(' + cachedBackgroundUrl + ')';
|
||||||
|
body.style.backgroundPosition = 'center';
|
||||||
|
body.style.backgroundRepeat = 'no-repeat';
|
||||||
|
body.style.backgroundSize = 'cover';
|
||||||
|
} else {
|
||||||
|
// Fetch and apply (background is already cleared to prevent flash)
|
||||||
|
fetchBackgroundUrl().then(function(url) {
|
||||||
|
if (isHomeMenuPage() && body.classList.contains('o_home_menu_background')) {
|
||||||
|
body.style.backgroundImage = 'url(' + url + ')';
|
||||||
|
body.style.backgroundPosition = 'center';
|
||||||
|
body.style.backgroundRepeat = 'no-repeat';
|
||||||
|
body.style.backgroundSize = 'cover';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pre-fetch background URL early to prevent flash
|
||||||
|
fetchBackgroundUrl();
|
||||||
|
|
||||||
|
// Listen for home menu events - pre-fetch BEFORE showing to prevent flash
|
||||||
|
core.bus.on('will_show_home_menu', null, function () {
|
||||||
|
// Pre-fetch the background URL before home menu is shown
|
||||||
|
fetchBackgroundUrl();
|
||||||
|
});
|
||||||
|
|
||||||
|
core.bus.on('show_home_menu', null, function () {
|
||||||
|
// Apply background immediately when home menu shows
|
||||||
|
setTimeout(setHomeMenuBackground, 10);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set background when DOM is ready
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', setHomeMenuBackground);
|
||||||
|
} else {
|
||||||
|
setHomeMenuBackground();
|
||||||
|
}
|
||||||
|
|
||||||
|
core.bus.on('hide_home_menu', null, function () {
|
||||||
|
// Remove background when leaving home menu
|
||||||
|
var body = document.body;
|
||||||
|
if (body) {
|
||||||
|
body.style.backgroundImage = '';
|
||||||
|
body.style.backgroundPosition = '';
|
||||||
|
body.style.backgroundRepeat = '';
|
||||||
|
body.style.backgroundSize = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Also set it when navigating (for SPA behavior)
|
||||||
|
core.bus.on('web_client_ready', null, function () {
|
||||||
|
setTimeout(setHomeMenuBackground, 100);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Watch for URL hash changes (Odoo navigation) - using hashchange event instead of interval
|
||||||
|
window.addEventListener('hashchange', function() {
|
||||||
|
setTimeout(setHomeMenuBackground, 100);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Watch for body class changes (Odoo navigation)
|
||||||
|
var bodyObserver = new MutationObserver(function(mutations) {
|
||||||
|
mutations.forEach(function(mutation) {
|
||||||
|
if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
|
||||||
|
setTimeout(setHomeMenuBackground, 100);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (document.body) {
|
||||||
|
bodyObserver.observe(document.body, {
|
||||||
|
attributes: true,
|
||||||
|
attributeFilter: ['class']
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup observer and clear cache when page unloads
|
||||||
|
window.addEventListener('beforeunload', function() {
|
||||||
|
if (bodyObserver) {
|
||||||
|
bodyObserver.disconnect();
|
||||||
|
}
|
||||||
|
cachedBackgroundUrl = null;
|
||||||
|
isFetching = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
setHomeMenuBackground: setHomeMenuBackground,
|
||||||
|
clearCache: function() {
|
||||||
|
cachedBackgroundUrl = null;
|
||||||
|
isFetching = false;
|
||||||
|
fetchBackgroundUrl().then(setHomeMenuBackground);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
<link rel="stylesheet" type="text/scss" href="/expert_std_backend_theme/static/src/scss/theme.scss"/>
|
<link rel="stylesheet" type="text/scss" href="/expert_std_backend_theme/static/src/scss/theme.scss"/>
|
||||||
<link rel="stylesheet" type="text/scss" href="/expert_std_backend_theme/static/src/scss/web.scss"/>
|
<link rel="stylesheet" type="text/scss" href="/expert_std_backend_theme/static/src/scss/web.scss"/>
|
||||||
<script src="/expert_std_backend_theme/static/src/js/menu.js"></script>
|
<script src="/expert_std_backend_theme/static/src/js/menu.js"></script>
|
||||||
|
<script src="/expert_std_backend_theme/static/src/js/background.js"></script>
|
||||||
</xpath>
|
</xpath>
|
||||||
</template>
|
</template>
|
||||||
</data>
|
</data>
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,26 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</xpath>
|
</xpath>
|
||||||
|
<xpath expr="//div[hasclass('settings')]" position="inside">
|
||||||
|
<div class="app_settings_block" data-string="Theme" data-key="expert_std_backend_theme">
|
||||||
|
<h2>Theme Customization</h2>
|
||||||
|
<div class="row mt16 o_settings_container" name="theme_background_setting_container">
|
||||||
|
<div class="col-12 col-lg-6 o_setting_box">
|
||||||
|
<div class="o_setting_left_pane"/>
|
||||||
|
<div class="o_setting_right_pane">
|
||||||
|
<label for="home_menu_background_image" string="Home Menu Background Image"/>
|
||||||
|
<div class="text-muted mb16">
|
||||||
|
Upload a custom background image for the home menu page. If not set, the default image will be used.
|
||||||
|
</div>
|
||||||
|
<field name="home_menu_background_image" widget="image" class="o_field_image" options="{'preview_image': 'home_menu_background_image', 'size': [1024, 1024]}"/>
|
||||||
|
<div class="text-muted mt8">
|
||||||
|
<small>Recommended size: 1920x1080 or higher. Supported formats: PNG, JPG, JPEG</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</xpath>
|
||||||
</data>
|
</data>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue