first push message
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
from . import controllers
|
||||
from . import models
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
'name': "custom_template_khmer",
|
||||
|
||||
'summary': "Short (1 phrase/line) summary of the module's purpose",
|
||||
|
||||
'description': """
|
||||
Allows dynamic customization of:
|
||||
- Font Family (Upload .ttf)
|
||||
- Menu Header Background (Color or Image)
|
||||
- Responsive design for Mobile/Desktop
|
||||
- Works on Backend and Website
|
||||
""",
|
||||
|
||||
'author': "My Company",
|
||||
'license': 'LGPL-3',
|
||||
'website': "https://www.yourcompany.com",
|
||||
|
||||
# Categories can be used to filter modules in modules listing
|
||||
# Check https://github.com/odoo/odoo/blob/15.0/odoo/addons/base/data/ir_module_category_data.xml
|
||||
# for the full list
|
||||
'category': 'thems and backend',
|
||||
'version': '0.1',
|
||||
|
||||
# any module necessary for this one to work correctly
|
||||
'depends': ['web', 'website'],
|
||||
|
||||
# always loaded
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
'views/res_config_settings_views.xml',
|
||||
'templates/layout_inherit.xml',
|
||||
],
|
||||
# only loaded in demonstration mode
|
||||
'demo': [
|
||||
'demo/demo.xml',
|
||||
],
|
||||
'assets': {
|
||||
'web.assets_backend': [
|
||||
'custom_template_khmer/static/src/scss/custom_theme.css',
|
||||
],
|
||||
'website.assets_frontend': [
|
||||
'custom_template_khmer/static/src/scss/custom_theme.css',
|
||||
'custom_template_khmer/static/src/scss/fonts.css',
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
from . import main
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,46 @@
|
||||
# custom_template_khmer/controllers/main.py
|
||||
from odoo import http
|
||||
from odoo.http import request
|
||||
import logging
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CustomThemeController(http.Controller):
|
||||
|
||||
# @http.route('/custom_template_khmer/fonts', type='http', auth='public')
|
||||
# def get_font(self):
|
||||
# """Serve custom font file"""
|
||||
# try:
|
||||
# config = request.env['custom.theme.config'].sudo().search([], limit=1)
|
||||
# if config and config.font_file:
|
||||
# return request.make_response(
|
||||
# config.font_file,
|
||||
# headers=[
|
||||
# ('Content-Type', 'font/ttf'),
|
||||
# ('Content-Disposition', f'inline; filename={config.font_name}.ttf'),
|
||||
# ('Cache-Control', 'public, max-age=31536000')
|
||||
# ]
|
||||
# )
|
||||
# except Exception as e:
|
||||
# _logger.error(f"Error serving font: {str(e)}")
|
||||
#
|
||||
# return request.make_response("", headers=[('Content-Type', 'text/plain')])
|
||||
|
||||
@http.route('/custom_template_khmer/menu_image', type='http', auth='public')
|
||||
def get_menu_image(self):
|
||||
"""Serve menu background image"""
|
||||
try:
|
||||
config = request.env['custom.theme.config'].sudo().search([], limit=1)
|
||||
if config and config.menu_bg_image:
|
||||
return request.make_response(
|
||||
config.menu_bg_image,
|
||||
headers=[
|
||||
('Content-Type', 'image/png'),
|
||||
('Cache-Control', 'public, max-age=31536000')
|
||||
]
|
||||
)
|
||||
except Exception as e:
|
||||
_logger.error(f"Error serving menu image: {str(e)}")
|
||||
|
||||
return request.make_response("", headers=[('Content-Type', 'text/plain')])
|
||||
@@ -0,0 +1,30 @@
|
||||
<odoo>
|
||||
<data>
|
||||
<!--
|
||||
<record id="object0" model="custom_template_khmer.custom_template_khmer">
|
||||
<field name="name">Object 0</field>
|
||||
<field name="value">0</field>
|
||||
</record>
|
||||
|
||||
<record id="object1" model="custom_template_khmer.custom_template_khmer">
|
||||
<field name="name">Object 1</field>
|
||||
<field name="value">10</field>
|
||||
</record>
|
||||
|
||||
<record id="object2" model="custom_template_khmer.custom_template_khmer">
|
||||
<field name="name">Object 2</field>
|
||||
<field name="value">20</field>
|
||||
</record>
|
||||
|
||||
<record id="object3" model="custom_template_khmer.custom_template_khmer">
|
||||
<field name="name">Object 3</field>
|
||||
<field name="value">30</field>
|
||||
</record>
|
||||
|
||||
<record id="object4" model="custom_template_khmer.custom_template_khmer">
|
||||
<field name="name">Object 4</field>
|
||||
<field name="value">40</field>
|
||||
</record>
|
||||
-->
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1,2 @@
|
||||
from . import custom_theme_config
|
||||
from . import res_config_settings
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,118 @@
|
||||
# custom_template_khmer/models/custom_theme_config.py
|
||||
from odoo import models, fields, api
|
||||
import base64
|
||||
import io
|
||||
import logging
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
from fontTools.ttLib import TTFont
|
||||
|
||||
HAS_FONTTOOLS = True
|
||||
except ImportError:
|
||||
HAS_FONTTOOLS = False
|
||||
_logger.warning("fontTools library not installed. Auto-font-name detection will not work.")
|
||||
|
||||
|
||||
class CustomThemeConfig(models.Model):
|
||||
_name = 'custom.theme.config'
|
||||
_description = 'Custom Theme Configuration (Singleton)'
|
||||
|
||||
name = fields.Char(default='Main Configuration')
|
||||
|
||||
# Binary Fields
|
||||
font_file = fields.Binary(string="Custom Font File (.ttf)", attachment=True)
|
||||
menu_bg_image = fields.Binary(string="Menu Background Image", attachment=True)
|
||||
|
||||
# Simple Fields
|
||||
font_name = fields.Char(string="Font Family Name")
|
||||
menu_bg_color = fields.Char(string="Menu Background Color", default="#714B67")
|
||||
is_responsive = fields.Boolean(string="Enable Mobile Responsiveness", default=True)
|
||||
|
||||
@api.onchange('font_file')
|
||||
def _onchange_font_file(self):
|
||||
"""Automatically extract font name when file is uploaded"""
|
||||
if self.font_file and HAS_FONTTOOLS:
|
||||
try:
|
||||
# Decode the base64 file
|
||||
font_data = base64.b64decode(self.font_file)
|
||||
font_io = io.BytesIO(font_data)
|
||||
|
||||
# Open font with fontTools
|
||||
font = TTFont(font_io)
|
||||
|
||||
# Get the 'name' table
|
||||
name_table = font['name']
|
||||
|
||||
# Try to find the PostScript Name (ID 6) or Full Name (ID 4)
|
||||
# Priority: PostScript Name > Full Name > Font Family
|
||||
font_name = None
|
||||
|
||||
for record in name_table.names:
|
||||
# nameID 6 = PostScript Name (Best for CSS)
|
||||
if record.nameID == 6:
|
||||
font_name = str(record)
|
||||
break
|
||||
# nameID 4 = Full Name
|
||||
elif record.nameID == 4 and not font_name:
|
||||
font_name = str(record)
|
||||
|
||||
# Fallback to Family Name (ID 1) if others missing
|
||||
if not font_name:
|
||||
for record in name_table.names:
|
||||
if record.nameID == 1:
|
||||
font_name = str(record)
|
||||
break
|
||||
|
||||
if font_name:
|
||||
self.font_name = font_name
|
||||
return {
|
||||
'notification': {
|
||||
'type': 'success',
|
||||
'message': f'Font detected: {font_name}',
|
||||
'sticky': False,
|
||||
}
|
||||
}
|
||||
else:
|
||||
return {
|
||||
'notification': {
|
||||
'type': 'warning',
|
||||
'message': 'Could not detect font name. Please enter manually.',
|
||||
'sticky': True,
|
||||
}
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
_logger.error(f"Error extracting font name: {e}")
|
||||
return {
|
||||
'notification': {
|
||||
'type': 'danger',
|
||||
'message': f'Error reading font file: {str(e)}',
|
||||
'sticky': True,
|
||||
}
|
||||
}
|
||||
elif not HAS_FONTTOOLS:
|
||||
return {
|
||||
'notification': {
|
||||
'type': 'warning',
|
||||
'message': 'Install "fonttools" library to auto-detect font names.',
|
||||
'sticky': True,
|
||||
}
|
||||
}
|
||||
|
||||
@api.model
|
||||
def get_config(self):
|
||||
return self.search([], limit=1)
|
||||
|
||||
# ✅ ADD THIS FIELD to store the original filename
|
||||
font_file_name = fields.Char(string="Original Filename", compute='_compute_font_file_name', store=True)
|
||||
font_file = fields.Binary(string="Custom Font File (.ttf)", attachment=True)
|
||||
|
||||
@api.depends('font_file')
|
||||
def _compute_font_file_name(self):
|
||||
for record in self:
|
||||
if record.font_file:
|
||||
record.font_file_name = "font_uploaded.ttf"
|
||||
else:
|
||||
record.font_file_name = False
|
||||
@@ -0,0 +1,50 @@
|
||||
from odoo import models, fields, api
|
||||
|
||||
class ResConfigSettings(models.TransientModel):
|
||||
_inherit = 'res.config.settings'
|
||||
|
||||
# ✅ CRITICAL FIX: Use Many2one to link to the singleton
|
||||
# This is the ONLY field the settings wizard needs to save explicitly
|
||||
khmer_theme_config_id = fields.Many2one(
|
||||
'custom.theme.config',
|
||||
string='Theme Configuration',
|
||||
required=True,
|
||||
default=lambda self: self._get_default_theme_config()
|
||||
)
|
||||
|
||||
# ✅ Related Fields (Read/Write via the Many2one)
|
||||
# These will automatically update the linked record when the Many2one is saved
|
||||
khmer_font_file = fields.Binary(
|
||||
string="Custom Font File (.ttf)",
|
||||
related='khmer_theme_config_id.font_file',
|
||||
readonly=False
|
||||
)
|
||||
khmer_font_name = fields.Char(
|
||||
string="Font Family Name",
|
||||
related='khmer_theme_config_id.font_name',
|
||||
readonly=False
|
||||
)
|
||||
khmer_menu_bg_color = fields.Char(
|
||||
string="Menu Background Color",
|
||||
related='khmer_theme_config_id.menu_bg_color',
|
||||
readonly=False
|
||||
)
|
||||
khmer_menu_bg_image = fields.Binary(
|
||||
string="Menu Background Image",
|
||||
related='khmer_theme_config_id.menu_bg_image',
|
||||
readonly=False
|
||||
)
|
||||
khmer_is_responsive = fields.Boolean(
|
||||
string="Enable Mobile Responsiveness",
|
||||
related='khmer_theme_config_id.is_responsive',
|
||||
readonly=False
|
||||
)
|
||||
khmer_font_file_name = fields.Char(string="Font Filename", related='khmer_theme_config_id.font_file_name',
|
||||
readonly=False)
|
||||
|
||||
@api.model
|
||||
def _get_default_theme_config(self):
|
||||
config = self.env['custom.theme.config'].search([], limit=1)
|
||||
if not config:
|
||||
config = self.env['custom.theme.config'].create({'name': 'Main Configuration'})
|
||||
return config.id
|
||||
@@ -0,0 +1,4 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_custom_theme_config_user,custom.theme.config.user,model_custom_theme_config,base.group_user,1,1,1,0
|
||||
access_custom_theme_config_admin,custom.theme.config.admin,model_custom_theme_config,base.group_system,1,1,1,1
|
||||
access_res_config_settings_admin,res.config.settings.admin,model_res_config_settings,base.group_system,1,1,1,1
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,30 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { useEffect } from "@odoo/owl";
|
||||
|
||||
export function themeLoaderPlugin(env, node) {
|
||||
useEffect(() => {
|
||||
// Apply theme after render
|
||||
const applyTheme = async () => {
|
||||
try {
|
||||
const response = await fetch('/custom_template_khmer/get_config');
|
||||
const config = await response.json();
|
||||
|
||||
if (config.menu_bg_color) {
|
||||
document.documentElement.style.setProperty(
|
||||
'--khmer-menu-bg-color',
|
||||
config.menu_bg_color
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Theme config load failed:', error);
|
||||
}
|
||||
};
|
||||
|
||||
applyTheme();
|
||||
}, []);
|
||||
}
|
||||
|
||||
// Register as a service or patch existing components
|
||||
// Note: For simple CSS themes, JS is usually NOT needed
|
||||
@@ -0,0 +1,631 @@
|
||||
|
||||
body,
|
||||
.o_form_view,
|
||||
.o_form_view .o_field_widget,
|
||||
.o_form_view label,
|
||||
.o_form_view .o_wrap_label,
|
||||
.o_form_view h1, .o_form_view h2, .o_form_view h3,
|
||||
.o_list_renderer th,
|
||||
.o_list_renderer td,
|
||||
.o_main_navbar .o_menu_brand,
|
||||
.o_main_navbar .o_menu_sections a,
|
||||
.breadcrumb,
|
||||
.o_statusbar_status button,
|
||||
select,
|
||||
select option,
|
||||
.o_field_selection select,
|
||||
.o_field_selection select option,
|
||||
.o_select_menu,
|
||||
.o_select_menu .o_select_menu_item,
|
||||
.o_select_menu_item_label,
|
||||
.dropdown-menu,
|
||||
.dropdown-menu .dropdown-item,
|
||||
.o_field_many2one_selection .o_external_button,
|
||||
.ui-autocomplete,
|
||||
.ui-autocomplete .ui-menu-item {
|
||||
font-family: 'Segoe UI', Roboto, Arial, 'Battambang', sans-serif !important;
|
||||
}
|
||||
|
||||
.o_main_navbar,
|
||||
.o_main_navbar .o_menu_sections,
|
||||
.o_main_navbar .o_menu_sections > *,
|
||||
.o_main_navbar .o_menu_brand,
|
||||
.o_navbar,
|
||||
.o_menu_sections {
|
||||
background-color: #0a5e98 !important;
|
||||
background: #0a5e98 !important;
|
||||
}
|
||||
|
||||
/* Section header - orange/brown like design */
|
||||
.o_xf_section_header {
|
||||
color: #c0392b;
|
||||
font-weight: bold;
|
||||
font-size: 1.1rem;
|
||||
margin: 16px 0 8px 0;
|
||||
}
|
||||
|
||||
/* Approvers group title font size */
|
||||
.o_group.o_inner_group .o_horizontal_separator,
|
||||
.o_horizontal_separator {
|
||||
font-size: 1.2rem !important;
|
||||
font-weight: bold !important;
|
||||
}
|
||||
|
||||
|
||||
/* Make source container full width matching the table */
|
||||
.o_xf_source_container {
|
||||
width: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
.o_xf_source_label {
|
||||
white-space: nowrap !important;
|
||||
flex-shrink: 0 !important;
|
||||
min-width: 180px !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
}
|
||||
.o_xf_source_container {
|
||||
align-items: stretch !important;
|
||||
}
|
||||
.o_xf_source_container > div {
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
}
|
||||
|
||||
/* Inline radio widget using field name selector */
|
||||
[name="document_source"] {
|
||||
display: flex !important;
|
||||
flex-direction: row !important;
|
||||
flex-wrap: nowrap !important;
|
||||
align-items: center !important;
|
||||
}
|
||||
[name="document_source"] .o_radio_item {
|
||||
display: inline-flex !important;
|
||||
align-items: center !important;
|
||||
white-space: nowrap !important;
|
||||
flex-shrink: 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
[name="document_source"] .o_radio_item:not(:last-child)::after {
|
||||
content: '|' !important;
|
||||
margin: 0 10px !important;
|
||||
color: #999 !important;
|
||||
}
|
||||
[name="document_source"] input[type="radio"] {
|
||||
display: none !important;
|
||||
}
|
||||
[name="document_source"] label {
|
||||
cursor: pointer !important;
|
||||
color: #0a5e98 !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
font-weight: normal !important;
|
||||
background: none !important;
|
||||
border: none !important;
|
||||
white-space: nowrap !important;
|
||||
}
|
||||
[name="document_source"] .o_radio_item.o_checked label {
|
||||
font-weight: bold !important;
|
||||
color: #000 !important;
|
||||
}
|
||||
|
||||
/* ── Confirmation dialog — Khmer ── */
|
||||
/* Title: replace "Confirmation" */
|
||||
.o_dialog .modal-title {
|
||||
font-size: 0 !important;
|
||||
color: transparent !important;
|
||||
}
|
||||
.o_dialog .modal-title::after {
|
||||
content: 'ការបញ្ជាក់' !important;
|
||||
font-size: 1.1rem !important;
|
||||
color: #000 !important;
|
||||
font-weight: 600 !important;
|
||||
font-family: 'Segoe UI', Roboto, Arial, 'Battambang', sans-serif !important;
|
||||
}
|
||||
/* OK button: target buttons with no [name] attr (confirmation dialog only) */
|
||||
.o_dialog .modal-footer button.btn-primary:not([name]) {
|
||||
font-size: 0 !important;
|
||||
color: transparent !important;
|
||||
}
|
||||
.o_dialog .modal-footer button.btn-primary:not([name])::after {
|
||||
content: 'យល់ព្រម' !important;
|
||||
font-size: 0.875rem !important;
|
||||
color: #fff !important;
|
||||
font-family: 'Segoe UI', Roboto, Arial, 'Battambang', sans-serif !important;
|
||||
}
|
||||
/* Cancel button: no [name], no [special] = confirmation dialog Cancel */
|
||||
.o_dialog .modal-footer button.btn-secondary:not([name]):not([special]) {
|
||||
font-size: 0 !important;
|
||||
color: transparent !important;
|
||||
}
|
||||
.o_dialog .modal-footer button.btn-secondary:not([name]):not([special])::after {
|
||||
content: 'បោះបង់' !important;
|
||||
font-size: 0.875rem !important;
|
||||
color: #212529 !important;
|
||||
font-family: 'Segoe UI', Roboto, Arial, 'Battambang', sans-serif !important;
|
||||
}
|
||||
/* Wizard Cancel button (special="cancel") */
|
||||
.o_dialog .modal-footer button[special="cancel"] {
|
||||
font-size: 0 !important;
|
||||
color: transparent !important;
|
||||
}
|
||||
.o_dialog .modal-footer button[special="cancel"]::after {
|
||||
content: 'បោះបង់' !important;
|
||||
font-size: 0.875rem !important;
|
||||
color: #212529 !important;
|
||||
font-family: 'Segoe UI', Roboto, Arial, 'Battambang', sans-serif !important;
|
||||
}
|
||||
|
||||
/* ── Chatter buttons — Khmer ── */
|
||||
.o_chatter_button_new_message {
|
||||
font-size: 0 !important;
|
||||
color: transparent !important;
|
||||
}
|
||||
.o_chatter_button_new_message::after {
|
||||
content: 'ផ្ញើសារ' !important;
|
||||
font-size: 0.875rem !important;
|
||||
color: #fff !important;
|
||||
font-family: 'Segoe UI', Roboto, Arial, 'Battambang', sans-serif !important;
|
||||
}
|
||||
.o_chatter_button_log_note {
|
||||
font-size: 0 !important;
|
||||
color: transparent !important;
|
||||
}
|
||||
.o_chatter_button_log_note::after {
|
||||
content: 'កត់សម្គាល់' !important;
|
||||
font-size: 0.875rem !important;
|
||||
color: inherit !important;
|
||||
font-family: 'Segoe UI', Roboto, Arial, 'Battambang', sans-serif !important;
|
||||
}
|
||||
|
||||
/* Replace "Add a line" with Khmer in approver table */
|
||||
.o_field_one2many[name="approver_ids"] a[role="button"] {
|
||||
font-size: 0 !important;
|
||||
color: transparent !important;
|
||||
}
|
||||
.o_field_one2many[name="approver_ids"] a[role="button"]::after {
|
||||
content: 'បន្ថែម' !important;
|
||||
font-size: 0.875rem !important;
|
||||
color: #0a5e98 !important;
|
||||
font-family: 'Segoe UI', Roboto, Arial, 'Battambang', sans-serif !important;
|
||||
}
|
||||
|
||||
/* Remove background from sort icon in approver table header */
|
||||
.o_field_one2many[name="approver_ids"] .o_list_sortable_icon {
|
||||
background: none !important;
|
||||
background-color: transparent !important;
|
||||
color: #ffffff !important;
|
||||
}
|
||||
|
||||
/* Approver table - header */
|
||||
.o_form_view .o_field_one2many .o_list_renderer thead th {
|
||||
background-color: #2e75b6 !important;
|
||||
color: #ffffff !important;
|
||||
text-align: center !important;
|
||||
padding: 8px 10px !important;
|
||||
font-weight: bold !important;
|
||||
border: 1px solid #1a5a96 !important;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Approver table - rows */
|
||||
.o_form_view .o_field_one2many .o_list_renderer tbody td {
|
||||
padding: 6px 10px !important;
|
||||
border: 1px solid #d0d0d0 !important;
|
||||
vertical-align: middle !important;
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
/* Approver table - alternating rows */
|
||||
.o_form_view .o_field_one2many .o_list_renderer tbody tr:nth-child(odd) td {
|
||||
background-color: #ffffff !important;
|
||||
}
|
||||
.o_form_view .o_field_one2many .o_list_renderer tbody tr:nth-child(even) td {
|
||||
background-color: #e8f0f9 !important;
|
||||
}
|
||||
|
||||
/* Hover row highlight */
|
||||
.o_form_view .o_field_one2many .o_list_renderer tbody tr:hover td {
|
||||
background-color: #cce0f5 !important;
|
||||
}
|
||||
|
||||
/* Remove default Odoo row borders */
|
||||
.o_form_view .o_field_one2many .o_list_renderer table {
|
||||
border-collapse: collapse !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
/* Hide selection checkbox in approver_ids list */
|
||||
.o_field_one2many[name="approver_ids"] .o_list_record_selector,
|
||||
.o_field_one2many[name="approver_ids"] .o_list_record_selector * {
|
||||
display: none !important;
|
||||
width: 0 !important;
|
||||
min-width: 0 !important;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
border: none !important;
|
||||
}
|
||||
.o_field_one2many[name="approver_ids"] input[type="checkbox"] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.btn-primary,
|
||||
.o_form_button_save,
|
||||
button.oe_highlight,
|
||||
.o_statusbar_buttons .oe_highlight {
|
||||
background-color: #0a5e98 !important;
|
||||
border-color: #0a5e98 !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
/* ===================================================
|
||||
Section ខ — Modern Card Design
|
||||
=================================================== */
|
||||
|
||||
/* Section title */
|
||||
.o_xf_section_b .o_horizontal_separator {
|
||||
font-size: 1.05rem !important;
|
||||
font-weight: 700 !important;
|
||||
color: #0a5e98 !important;
|
||||
padding: 6px 0 10px 2px !important;
|
||||
border-bottom: 2px solid #0a5e98 !important;
|
||||
margin-bottom: 12px !important;
|
||||
}
|
||||
|
||||
/* Card wrapper */
|
||||
.o_xf_table {
|
||||
width: 100% !important;
|
||||
background: #ffffff !important;
|
||||
border-radius: 10px !important;
|
||||
box-shadow: 0 2px 8px rgba(10, 94, 152, 0.10),
|
||||
0 0 0 1px #dde8f5 !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
/* ── Row ── */
|
||||
.o_xf_row {
|
||||
display: flex !important;
|
||||
min-height: 50px !important;
|
||||
border-bottom: 1px solid #eef3fa !important;
|
||||
}
|
||||
.o_xf_row:last-child { border-bottom: none !important; }
|
||||
.o_xf_row_tall {
|
||||
align-items: stretch !important;
|
||||
min-height: 80px !important;
|
||||
}
|
||||
|
||||
/* ── Label cell ── */
|
||||
.o_xf_cell_label {
|
||||
flex: 0 0 200px !important;
|
||||
width: 200px !important;
|
||||
background: #f4f7fb !important;
|
||||
border-right: 3px solid #0a5e98 !important;
|
||||
padding: 0 16px !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
font-size: 0.875rem !important;
|
||||
font-weight: 600 !important;
|
||||
color: #234d73 !important;
|
||||
white-space: nowrap !important;
|
||||
}
|
||||
|
||||
/* ── Value cell ── */
|
||||
.o_xf_cell_value {
|
||||
flex: 1 !important;
|
||||
padding: 0 16px !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
background: #ffffff !important;
|
||||
min-width: 0 !important;
|
||||
}
|
||||
.o_xf_cell_value > .o_field_widget {
|
||||
width: 100% !important;
|
||||
min-width: 0 !important;
|
||||
}
|
||||
|
||||
/* Input fields inside value cell — clean underline style */
|
||||
.o_xf_cell_value .o_field_widget input.o_input,
|
||||
.o_xf_cell_value .o_field_widget textarea.o_input {
|
||||
border: none !important;
|
||||
border-bottom: 1.5px solid #d0dcea !important;
|
||||
border-radius: 0 !important;
|
||||
background: transparent !important;
|
||||
padding: 4px 2px !important;
|
||||
font-size: 0.9rem !important;
|
||||
color: #1a3550 !important;
|
||||
width: 100% !important;
|
||||
transition: border-color 0.15s !important;
|
||||
}
|
||||
.o_xf_cell_value .o_field_widget input.o_input:focus,
|
||||
.o_xf_cell_value .o_field_widget textarea.o_input:focus {
|
||||
border-bottom-color: #0a5e98 !important;
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
/* Many2one field styling */
|
||||
.o_xf_cell_value .o_field_many2one .o_input_dropdown input {
|
||||
border: none !important;
|
||||
border-bottom: 1.5px solid #d0dcea !important;
|
||||
border-radius: 0 !important;
|
||||
background: transparent !important;
|
||||
font-size: 0.9rem !important;
|
||||
color: #1a3550 !important;
|
||||
}
|
||||
.o_xf_cell_value .o_field_many2one .o_input_dropdown input:focus {
|
||||
border-bottom-color: #0a5e98 !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
/* Selection (dropdown) field */
|
||||
.o_xf_cell_value .o_field_selection select,
|
||||
.o_xf_cell_value .o_field_widget select {
|
||||
border: none !important;
|
||||
border-bottom: 1.5px solid #d0dcea !important;
|
||||
border-radius: 0 !important;
|
||||
background: transparent !important;
|
||||
font-size: 0.9rem !important;
|
||||
color: #1a3550 !important;
|
||||
padding: 4px 2px !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
/* Read-only values */
|
||||
.o_xf_cell_value .o_field_widget.o_readonly,
|
||||
.o_xf_cell_value .o_field_char.o_field_readonly,
|
||||
.o_form_readonly .o_xf_cell_value .o_field_widget {
|
||||
font-size: 0.9rem !important;
|
||||
color: #1a3550 !important;
|
||||
font-weight: 500 !important;
|
||||
}
|
||||
|
||||
/* Placeholder text */
|
||||
.o_xf_cell_value input::placeholder,
|
||||
.o_xf_cell_value textarea::placeholder {
|
||||
color: #b0bec5 !important;
|
||||
font-style: italic !important;
|
||||
font-size: 0.85rem !important;
|
||||
}
|
||||
|
||||
/* ── Tall value cell (documents / description) ── */
|
||||
.o_xf_cell_tall {
|
||||
align-items: flex-start !important;
|
||||
padding-top: 10px !important;
|
||||
padding-bottom: 10px !important;
|
||||
}
|
||||
|
||||
/* ── Split cell: file | note ── */
|
||||
.o_xf_cell_split {
|
||||
padding: 0 !important;
|
||||
align-items: stretch !important;
|
||||
}
|
||||
.o_xf_split_left,
|
||||
.o_xf_split_right {
|
||||
flex: 1 !important;
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
justify-content: center !important;
|
||||
padding: 8px 16px !important;
|
||||
gap: 5px !important;
|
||||
}
|
||||
.o_xf_split_divider {
|
||||
width: 1px !important;
|
||||
background: #dde8f5 !important;
|
||||
flex-shrink: 0 !important;
|
||||
}
|
||||
.o_xf_split_hint {
|
||||
font-size: 0.7rem !important;
|
||||
color: #90aac4 !important;
|
||||
font-weight: 700 !important;
|
||||
text-transform: uppercase !important;
|
||||
letter-spacing: 0.07em !important;
|
||||
}
|
||||
.o_xf_split_left .o_field_widget,
|
||||
.o_xf_split_right .o_field_widget {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
/* ── Upload zone (ឯកសារ row) ── */
|
||||
.o_xf_upload_zone {
|
||||
flex-direction: column !important;
|
||||
align-items: flex-start !important;
|
||||
gap: 8px !important;
|
||||
padding: 12px 16px !important;
|
||||
}
|
||||
.o_xf_upload_zone > .o_field_widget {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
/* Wrapper for the whole many2many_binary widget */
|
||||
.o_xf_upload_zone .o_field_many2many_binary {
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
gap: 6px !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
/* ── Style the raw file input ── */
|
||||
.o_xf_upload_zone input[type="file"] {
|
||||
width: 100% !important;
|
||||
font-size: 0.84rem !important;
|
||||
color: #555 !important;
|
||||
cursor: pointer !important;
|
||||
border: 1.5px dashed #a0bfdf !important;
|
||||
border-radius: 8px !important;
|
||||
padding: 8px 12px !important;
|
||||
background: #f7fafd !important;
|
||||
transition: border-color 0.15s, background 0.15s !important;
|
||||
}
|
||||
.o_xf_upload_zone input[type="file"]:hover {
|
||||
border-color: #0a5e98 !important;
|
||||
background: #edf4ff !important;
|
||||
}
|
||||
|
||||
/* "Choose Files" browser button part */
|
||||
.o_xf_upload_zone input[type="file"]::file-selector-button {
|
||||
padding: 5px 16px !important;
|
||||
border: none !important;
|
||||
border-radius: 5px !important;
|
||||
background: #0a5e98 !important;
|
||||
color: #ffffff !important;
|
||||
font-size: 0.83rem !important;
|
||||
font-weight: 600 !important;
|
||||
cursor: pointer !important;
|
||||
margin-right: 12px !important;
|
||||
transition: background 0.15s !important;
|
||||
font-family: 'Segoe UI', Roboto, Arial, 'Battambang', sans-serif !important;
|
||||
}
|
||||
.o_xf_upload_zone input[type="file"]::file-selector-button:hover {
|
||||
background: #084e82 !important;
|
||||
}
|
||||
|
||||
/* ── Uploaded file rows ── */
|
||||
.o_xf_upload_zone .o_field_many2many_binary .o_attachments,
|
||||
.o_xf_upload_zone .o_field_many2many_binary ul {
|
||||
width: 100% !important;
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
gap: 5px !important;
|
||||
list-style: none !important;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
.o_xf_upload_zone .o_field_many2many_binary .o_attachment,
|
||||
.o_xf_upload_zone .o_field_many2many_binary li {
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
gap: 8px !important;
|
||||
padding: 6px 12px !important;
|
||||
background: #edf4ff !important;
|
||||
border: 1px solid #c5daef !important;
|
||||
border-radius: 6px !important;
|
||||
font-size: 0.84rem !important;
|
||||
color: #1a3d5c !important;
|
||||
}
|
||||
.o_xf_upload_zone .o_field_many2many_binary .o_attachment a,
|
||||
.o_xf_upload_zone .o_field_many2many_binary li a {
|
||||
color: #0a5e98 !important;
|
||||
text-decoration: none !important;
|
||||
font-weight: 500 !important;
|
||||
flex: 1 !important;
|
||||
}
|
||||
.o_xf_upload_zone .o_field_many2many_binary .o_attachment a:hover,
|
||||
.o_xf_upload_zone .o_field_many2many_binary li a:hover {
|
||||
text-decoration: underline !important;
|
||||
}
|
||||
/* Delete button */
|
||||
.o_xf_upload_zone .o_field_many2many_binary .o_delete,
|
||||
.o_xf_upload_zone .o_field_many2many_binary .delete {
|
||||
color: #c0392b !important;
|
||||
opacity: 0.55 !important;
|
||||
cursor: pointer !important;
|
||||
font-size: 0.9rem !important;
|
||||
margin-left: auto !important;
|
||||
}
|
||||
.o_xf_upload_zone .o_field_many2many_binary .o_delete:hover {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
/* ── document_ids list in non-draft (ឯកសារ row) ── */
|
||||
.o_xf_doc_list .o_list_renderer {
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
.o_xf_doc_list .o_list_renderer thead { display: none !important; }
|
||||
.o_xf_doc_list .o_list_renderer tbody td {
|
||||
border: none !important;
|
||||
padding: 3px 6px !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
.o_xf_doc_list .o_list_renderer tbody tr {
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
gap: 8px !important;
|
||||
padding: 4px 8px !important;
|
||||
background: #edf4ff !important;
|
||||
border: 1px solid #c5daef !important;
|
||||
border-radius: 6px !important;
|
||||
margin-bottom: 4px !important;
|
||||
}
|
||||
|
||||
/* ── File download list (non-draft state) ── */
|
||||
.o_xf_files_list {
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
gap: 5px !important;
|
||||
width: 100% !important;
|
||||
padding: 4px 0 !important;
|
||||
}
|
||||
.o_xf_file_item {
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
gap: 8px !important;
|
||||
padding: 6px 12px !important;
|
||||
background: #edf4ff !important;
|
||||
border: 1px solid #c5daef !important;
|
||||
border-radius: 6px !important;
|
||||
font-size: 0.84rem !important;
|
||||
}
|
||||
.o_xf_file_item a {
|
||||
color: #0a5e98 !important;
|
||||
text-decoration: none !important;
|
||||
font-weight: 500 !important;
|
||||
flex: 1 !important;
|
||||
}
|
||||
.o_xf_file_item a:hover { text-decoration: underline !important; }
|
||||
.o_xf_file_icon { color: #5a9fd4 !important; font-size: 0.85rem !important; }
|
||||
|
||||
/* ── Readonly many2many_binary: ensure files are always visible ── */
|
||||
.o_xf_upload_zone .o_field_many2many_binary {
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
gap: 6px !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
/* Odoo 19 OWL: readonly renders files as <a> links directly */
|
||||
.o_xf_upload_zone .o_field_many2many_binary a,
|
||||
.o_xf_upload_zone .o_field_many2many_binary .o_form_uri,
|
||||
.o_xf_upload_zone .o_field_many2many_binary span[title] {
|
||||
display: inline-flex !important;
|
||||
align-items: center !important;
|
||||
gap: 6px !important;
|
||||
padding: 6px 12px !important;
|
||||
background: #edf4ff !important;
|
||||
border: 1px solid #c5daef !important;
|
||||
border-radius: 6px !important;
|
||||
font-size: 0.84rem !important;
|
||||
color: #0a5e98 !important;
|
||||
text-decoration: none !important;
|
||||
font-weight: 500 !important;
|
||||
}
|
||||
.o_xf_upload_zone .o_field_many2many_binary a:hover {
|
||||
text-decoration: underline !important;
|
||||
background: #dceeff !important;
|
||||
}
|
||||
|
||||
/* ── QR Code section ── */
|
||||
.o_xf_qr_group {
|
||||
margin-top: 16px !important;
|
||||
padding: 24px 20px !important;
|
||||
background: #f7fafd !important;
|
||||
border-radius: 10px !important;
|
||||
border: 1px dashed #b0cce8 !important;
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
align-items: center !important;
|
||||
gap: 10px !important;
|
||||
}
|
||||
.o_xf_qr_label {
|
||||
font-size: 0.78rem !important;
|
||||
color: #7a9ec0 !important;
|
||||
font-weight: 700 !important;
|
||||
text-transform: uppercase !important;
|
||||
letter-spacing: 0.09em !important;
|
||||
}
|
||||
.o_xf_qr_group .o_field_image img {
|
||||
border-radius: 8px !important;
|
||||
box-shadow: 0 2px 12px rgba(0,0,0,0.13) !important;
|
||||
border: 4px solid #ffffff !important;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/* custom_template_khmer/static/src/css/custom_theme.css */
|
||||
@font-face {
|
||||
font-family: 'KhmerOS_content';
|
||||
src:url('/custom_template_khmer/static/src/fonts/KhmerOS_content.ttf') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
/* Fallback variables */
|
||||
:root {
|
||||
--khmer-menu-bg: #714B67;
|
||||
--khmer-font-family: 'KhmerOS_content', 'Battambang', 'Hanuman', sans-serif;
|
||||
}
|
||||
|
||||
/* Smooth transitions */
|
||||
.o_main_navbar,
|
||||
.o_menu_navbar,
|
||||
.btn,
|
||||
.form-control {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
/* Khmer text rendering */
|
||||
body {
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
/* Utility classes
|
||||
.khmer-font {
|
||||
font-family: var(--khmer-font-family) !important;
|
||||
}
|
||||
.khmer-menu-bg {
|
||||
background-color: var(--khmer-menu-bg) !important;
|
||||
}
|
||||
*/
|
||||
@@ -0,0 +1,62 @@
|
||||
.o_sub_menu{
|
||||
font-family: Khmer OS content; src:url(../fonts/KhmerOS_content.ttf) ;format('truetype');
|
||||
font-size:16px;
|
||||
color: blue;
|
||||
}
|
||||
.oe_secondary_menu{
|
||||
font-family: Khmer OS content; src:url(../fonts/KhmerOS_content.ttf) ;format('truetype');
|
||||
font-size:14px;
|
||||
color: black;
|
||||
}
|
||||
.o_content{
|
||||
font-family: Khmer OS content; src:url(../fonts/KhmerOS_content.ttf) ;format('truetype');
|
||||
}
|
||||
.o_cp_buttons{
|
||||
font-family: Khmer OS content; src:url(../fonts/KhmerOS_content.ttf) ;format('truetype');
|
||||
}
|
||||
.active{
|
||||
font-family: Khmer OS content; src:url(../fonts/KhmerOS_content.ttf) ;format('truetype');
|
||||
|
||||
}
|
||||
.o_cp_controller{
|
||||
font-family: Khmer OS content; src:url(../fonts/KhmerOS_content.ttf) ;format('truetype');
|
||||
|
||||
}
|
||||
header.o_navbar{
|
||||
background-color: #5dda32;
|
||||
}
|
||||
header{
|
||||
background-color: #5dda32;
|
||||
}
|
||||
.o_main_navbar{
|
||||
font-family: Khmer OS content; src:url(../fonts/KhmerOS_content.ttf) ;format('truetype');
|
||||
background-color: #5dda32;
|
||||
}
|
||||
.o_menu_sections{
|
||||
font-family: Khmer OS content; src:url(../fonts/KhmerOS_content.ttf) ;format('truetype');
|
||||
background-color: #5dda32;
|
||||
}
|
||||
.o-dropdown-item.dropdown-item.o-navigable.o_nav_entry{
|
||||
background-color: #5dda32;
|
||||
}
|
||||
.o-dropdown.dropdown-toggle.dropdown{
|
||||
background-color: #5dda32;
|
||||
}
|
||||
.breadcrumb{
|
||||
font-family: Khmer OS content; src:url(../fonts/KhmerOS_content.ttf) ;format('truetype');
|
||||
}
|
||||
body{
|
||||
font-family: Khmer OS content; src:url(../fonts/KhmerOS_content.ttf) ;format('truetype');
|
||||
}
|
||||
main.o_content{
|
||||
font-family: Khmer OS content; src:url(../fonts/KhmerOS_content.ttf) ;format('truetype');
|
||||
}
|
||||
div.o_form_view_container{
|
||||
font-family: Khmer OS content; src:url(../fonts/KhmerOS_content.ttf) ;format('truetype');
|
||||
}
|
||||
.o-dropdown-item.dropdown-item.o-navigable.o_nav_entry{
|
||||
font-family: Khmer OS content; src:url(../fonts/KhmerOS_content.ttf) ;format('truetype');
|
||||
}
|
||||
.o-dropdown-item.dropdown-item.o-navigable{
|
||||
font-family: Khmer OS content; src:url('/custom_template_khmer/static/src/fonts/KhmerOS_content.ttf') format('truetype');
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<!-- custom_template_khmer/templates/debug_config.xml -->
|
||||
<odoo>
|
||||
<template id="debug_config" name="Debug Config">
|
||||
<t t-set="config" t-value="env['custom.template.config'].sudo().search([], limit=1)"/>
|
||||
<div style="position: fixed; top: 100px; right: 10px; background: red; color: white; padding: 10px; z-index: 9999;">
|
||||
<strong>DEBUG:</strong><br/>
|
||||
Config ID: <t t-esc="config.id if config else 'None'"/><br/>
|
||||
Color: <t t-esc="config.menu_bg_color if config else 'None'"/><br/>
|
||||
Font: <t t-esc="config.font_name if config else 'None'"/>
|
||||
</div>
|
||||
</template>
|
||||
</odoo>
|
||||
@@ -0,0 +1,117 @@
|
||||
<!-- custom_template_khmer/templates/layout_inherit.xml -->
|
||||
<odoo>
|
||||
<!-- Backend Layout -->
|
||||
<template id="web_layout_inherit" inherit_id="web.layout" name="Khmer Theme Backend">
|
||||
<xpath expr="//head" position="inside">
|
||||
<t t-call="custom_template_khmer.dynamic_css_injector"/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<!-- Website Layout -->
|
||||
<template id="website_layout_inherit" inherit_id="website.layout" name="Khmer Theme Website">
|
||||
<xpath expr="//head" position="inside">
|
||||
<t t-call="custom_template_khmer.dynamic_css_injector"/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<!-- Dynamic CSS Injector -->
|
||||
<template id="dynamic_css_injector" name="Dynamic CSS Injector">
|
||||
<style type="text/css">
|
||||
<t t-set="config" t-value="env['custom.theme.config'].sudo().search([], limit=1)"/>
|
||||
|
||||
<!-- 1. DYNAMIC FONT -->
|
||||
<!-- Inside templates/layout_inherit.xml -->
|
||||
<t t-if="config and config.font_file">
|
||||
<!-- 1. Define the Font Face -->
|
||||
@font-face {
|
||||
/* Use the EXACT name from your Settings field */
|
||||
font-family: '<t t-esc="config.font_name"/>';
|
||||
|
||||
/* Dynamic URL to your controller */
|
||||
src: url('/custom_template_khmer/fonts?t=<t t-esc="config.id"/>') format('truetype');
|
||||
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
/* 2. Apply the Font Globally */
|
||||
/* We simply use the name defined above. No src here! */
|
||||
body,form,.o_cp_buttons,.oe_secondary_menu,
|
||||
.o_web_client,.o_content,.o_main_navbar,o-dropdown-item.dropdown-item.o-navigable,
|
||||
.o_form_view .oe_title > h1, .o_form_view .oe_title > .h1, .o_form_view .oe_title > h2, .o_form_view .oe_title > .h2, .o_form_view .oe_title > h3, .o_form_view .oe_title > .h3{
|
||||
font-family: '<t t-esc="config.font_name"/>', Khmer-Font !important;
|
||||
}
|
||||
</t>
|
||||
|
||||
<!-- 2. DYNAMIC MENU COLOR -->
|
||||
<t t-if="config and config.menu_bg_color">
|
||||
:root {
|
||||
--khmer-menu-bg: <t t-esc="config.menu_bg_color"/>;
|
||||
}
|
||||
|
||||
.o_main_navbar,
|
||||
.o_menu_navbar,
|
||||
#o_main_navbar,
|
||||
header.o_navbar,
|
||||
.o_navbar,
|
||||
.navbar-expand-md,
|
||||
.o_menu_systray,
|
||||
.o_menu_sections {
|
||||
background-color: <t t-esc="config.menu_bg_color"/> !important;
|
||||
background: <t t-esc="config.menu_bg_color"/> !important;
|
||||
}
|
||||
|
||||
.o_menu_sections.d-flex.flex-grow-1.flex-shrink-1.w-0{
|
||||
background-color: <t t-esc="config.menu_bg_color"/> !important;
|
||||
background: <t t-esc="config.menu_bg_color"/> !important;
|
||||
}
|
||||
|
||||
.o-dropdown-item.dropdown-item.o-navigable.o_nav_entry{
|
||||
background-color: <t t-esc="config.menu_bg_color"/> !important;
|
||||
background: <t t-esc="config.menu_bg_color"/> !important;
|
||||
}
|
||||
.o_main_navbar .o_nav_entry, .o_main_navbar .dropdown-toggle:not(.o-dropdown-toggle-custo){
|
||||
background-color: <t t-esc="config.menu_bg_color"/> !important;
|
||||
background: <t t-esc="config.menu_bg_color"/> !important;
|
||||
}
|
||||
.o_menu_brand,
|
||||
.navbar-brand {
|
||||
background-color: <t t-esc="config.menu_bg_color"/> !important;
|
||||
color: #FFFFFF !important;
|
||||
}
|
||||
|
||||
<!-- With background image -->
|
||||
<t t-if="config.menu_bg_image">
|
||||
.o_main_navbar,
|
||||
.o_menu_navbar,
|
||||
#o_main_navbar {
|
||||
background-image: url('/custom_template_khmer/menu_image?t=<t t-esc="config.id"/>') !important;
|
||||
background-size: cover !important;
|
||||
background-blend-mode: overlay !important;
|
||||
}
|
||||
</t>
|
||||
|
||||
.o_navbar_main_menu,
|
||||
.navbar-custom,
|
||||
#wrapwrap header {
|
||||
background-color: <t t-esc="config.menu_bg_color"/> !important;
|
||||
}
|
||||
</t>
|
||||
|
||||
<!-- 3. RESPONSIVE DESIGN -->
|
||||
<t t-if="config and config.is_responsive">
|
||||
@media (max-width: 768px) {
|
||||
.o_main_navbar { min-height: 40px; font-size: 13px; }
|
||||
.o_menu_brand { font-size: 16px !important; }
|
||||
}
|
||||
@media (min-width: 769px) and (max-width: 1024px) {
|
||||
.o_main_navbar { min-height: 44px; font-size: 14px; }
|
||||
}
|
||||
@media (min-width: 1025px) {
|
||||
.o_main_navbar { min-height: 46px; font-size: 15px; }
|
||||
}
|
||||
</t>
|
||||
</style>
|
||||
</template>
|
||||
</odoo>
|
||||
@@ -0,0 +1,68 @@
|
||||
<odoo>
|
||||
<record id="res_config_settings_view_form_inherit_khmer" model="ir.ui.view">
|
||||
<field name="name">res.config.settings.view.form.inherit.khmer</field>
|
||||
<field name="model">res.config.settings</field>
|
||||
<field name="inherit_id" ref="base.res_config_settings_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//app[@name='general_settings']" position="inside">
|
||||
|
||||
<!-- HIDDEN LINK FIELD (Required for saving) -->
|
||||
<field name="khmer_theme_config_id" invisible="1"/>
|
||||
|
||||
<block title="🇰 Khmer Theme Customization" name="khmer_theme_settings">
|
||||
<!-- Font Settings -->
|
||||
<!-- Inside the setting block for Font -->
|
||||
<setting id="khmer_font_setting" string="Custom Font" help="Upload .ttf font file">
|
||||
<div class="content-group">
|
||||
|
||||
<!-- Font Name Field -->
|
||||
<div class="mt16 row">
|
||||
<label for="khmer_font_name" string="Font Family Name)" class="col-lg-3 o_light_label"/>
|
||||
<field name="khmer_font_name" filename="khmer_font_file"/>
|
||||
</div>
|
||||
|
||||
<!-- Font Upload Field with Filename Attribute -->
|
||||
<div class="mt16 row">
|
||||
<label for="khmer_font_file" string="Upload Font (.ttf)" class="col-lg-3 o_light_label"/>
|
||||
<!-- Add filename="khmer_font_file_name" to track the real name -->
|
||||
<field name="khmer_font_file" class="col-lg-4" widget="binary" filename="khmer_font_file_name"/>
|
||||
</div>
|
||||
|
||||
<!-- Hidden field to store the actual filename (Required for the widget to work) -->
|
||||
<field name="khmer_font_file_name" invisible="1"/>
|
||||
|
||||
<div class="text-muted small mt8">
|
||||
<i class="fa fa-info-circle"/> The font name will be extracted automatically upon upload.
|
||||
</div>
|
||||
</div>
|
||||
</setting>
|
||||
|
||||
<!-- Menu Settings -->
|
||||
<setting id="khmer_menu_setting" string="Menu Header" help="Set color or image">
|
||||
<div class="content-group">
|
||||
<div class="mt16 row">
|
||||
<label for="khmer_menu_bg_color" string="Color" class="col-lg-3 o_light_label"/>
|
||||
<field name="khmer_menu_bg_color" class="col-lg-4" widget="color"/>
|
||||
</div>
|
||||
<div class="mt16 row">
|
||||
<label for="khmer_menu_bg_image" string="Image" class="col-lg-3 o_light_label"/>
|
||||
<field name="khmer_menu_bg_image" class="col-lg-4" widget="binary"/>
|
||||
</div>
|
||||
</div>
|
||||
</setting>
|
||||
|
||||
<!-- Responsive -->
|
||||
<setting id="khmer_responsive_setting" string="Responsive">
|
||||
<div class="content-group">
|
||||
<div class="mt16 row">
|
||||
<label for="khmer_is_responsive" string="Enable" class="col-lg-3 o_light_label"/>
|
||||
<field name="khmer_is_responsive" class="col-lg-4"/>
|
||||
</div>
|
||||
</div>
|
||||
</setting>
|
||||
|
||||
</block>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -0,0 +1,18 @@
|
||||
<odoo>
|
||||
<record id="view_custom_template_config_form" model="ir.ui.view">
|
||||
<field name="name">custom.template.config.form</field>
|
||||
<field name="model">custom.template.config</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Theme Configuration">
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="font_name"/>
|
||||
<field name="font_file"/>
|
||||
<field name="menu_bg_color"/>
|
||||
<field name="menu_bg_image"/>
|
||||
<field name="is_responsive"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user