Files
gzth/anita_theme_setting/models/anita_theme_mode.py
T
2024-06-20 13:39:24 +08:00

588 lines
21 KiB
Python

# -*- coding: utf-8 -*-
from odoo import models, fields, api, exceptions, tools
import json
import re
from odoo.tools.misc import file_path
from odoo.tools.config import config
from .anita_utility import apply_alpha
try:
import sass as libsass
except ImportError:
libsass = None
import logging
_logger = logging.getLogger(__name__)
class AnitaThemeMode(models.Model):
'''
user theme style setting
'''
_name = 'anita_theme_setting.theme_mode'
_description = 'anita theme mode'
name = fields.Char(string="Name", required=True)
sequence = fields.Integer(string="Mode Sequence", default=0)
theme_styles = fields.One2many(
string="Theme Styles",
comodel_name="anita_theme_setting.theme_style",
inverse_name="theme_mode")
mode_template = fields.Many2one(
comodel_name="anita_theme_setting.mode_template",
string="Mode Template")
template_var_vals = fields.One2many(
comodel_name="anita_theme_setting.template_var_val",
inverse_name="theme_mode",
string="Template Vars")
# if owner is False, so the style own to global
owner = fields.Reference(
string="Owner",
selection=[('res.company', 'res.company'),
('res.users', 'res.users')],
default=False,
help="owner which this theme is create by, if it is false, it mean it is owned to the system!")
mode_style_css = fields.Text(string="Mode Style Css")
compiled_mode_style_css = fields.Text(string="Compiled mode style css")
is_default = fields.Boolean(string="Is Default", default=False)
version = fields.Char(
string="Version", help="use version to check update or not")
_sql_constraints = [('theme_mode_name_unique', 'UNIQUE(name, owner)',
"Theme mode name and owner must unique")]
def get_mode_preview_data(self, style_id=False):
"""
get mode preview data
:return:
"""
if self.mode_template:
if style_id:
theme_style = self.env['anita_theme_setting.theme_style'].browse(
style_id)
if theme_style.is_semi_transparent():
result = theme_style.get_mode_style_css()
else:
result = self.get_mode_style_css()
else:
result = self.get_mode_style_css()
else:
result = ""
mode_name = 'body.{name}'.format(name=self.name)
# replace mode_name with body.preview
result = result.replace(mode_name, 'body.preview')
return result
@api.onchange('mode_template')
def on_mode_template_changed(self):
"""
compute mode style
:return:
"""
# check id is instance of NewId
if self.mode_template:
scss_vars = self.mode_template.get_color_vars()
template_var_vals = [(5, 0, 0)]
for group_name in scss_vars:
var_items = scss_vars[group_name]
for var_item in var_items:
template_var_vals.append((0, 0, {
"name": var_item["name"],
"group": group_name,
"value": var_item["value"],
"disable_opacity": var_item.get("disable_opacity", False),
"description": var_item["description"]
}))
self.template_var_vals = template_var_vals
else:
self.template_var_vals = [(5, 0, 0)]
def check_template_vars(self):
"""
check style css
:return:
"""
return
@tools.ormcache('self.id')
def get_scss_vars_cache(self):
"""
get scss vars
:return:
"""
records = self.template_var_vals.read(['name', 'value'])
return {record['name']: record['value'] for record in records}
@tools.ormcache('self.id')
def get_scss_var_record_cache(self):
"""
get scss vars
:return:
"""
records = self.template_var_vals
return {record.name: {
"name": record.name,
"value": record.value,
"disable_opacity": record.disable_opacity,
} for record in records}
def _compile_scss(self, alpha=False):
"""
This code will compile valid scss into css.
Simply copied and adapted slightly
"""
self.ensure_one()
scss_source = self.mode_template.template
scss_vars = []
var_val_cache = self.get_scss_var_record_cache()
for var_name in var_val_cache:
var_record = var_val_cache[var_name]
name = var_record.get("name")
if alpha and alpha < 1:
color = apply_alpha(var_record.get('name'), var_record.get('value'), alpha)
else:
color = var_record.get('value')
# new color
scss_vars.append('{name}: {value};'.format(
name=name, value=color))
# origin color, some element disable alpha
scss_vars.append('{name}: {value};'.format(
name=name.replace('$', '$_'), value=var_record.get('value')))
precision = 8
output_style = 'expanded'
bootstrap_scss_path = file_path('web/static/lib/bootstrap/scss')
mode_selectors = "$mode_selectors: 'body.{name}';".format(name=self.name)
extra_vars = [
mode_selectors,
"$compile_normal: 1;",
"$semi_transparent: {semi_trans};".format(semi_trans=True if alpha < 1.0 else False),
]
total_vars = extra_vars + scss_vars
normal_source = '\n'.join(total_vars) + '\n' + scss_source if scss_source else ''
# compile normal
results = []
try:
result = libsass.compile(
string=normal_source,
include_paths=[bootstrap_scss_path],
output_style=output_style,
precision=precision)
# remove the import bootstrap
index = result.find("body.{name}".format(name=self.name))
result = result[index:]
results.append(result)
except libsass.CompileError as e:
_logger.error(e.args[0])
# compile for background
if alpha < 1.0:
scss_vars = []
for var_name in var_val_cache:
var_record = var_val_cache[var_name]
name = var_record.get("name")
# new color
scss_vars.append('{name}: {value};'.format(name=name, value=var_record.get('value')))
# origin color
scss_vars.append('{name}: {value};'.format(name=name.replace('$', '$_'), value=var_record.get('value')))
mode_selectors = "$mode_selectors: 'body.{name} .o_dialog_container';".format(name=self.name)
extra_vars = [
mode_selectors,
"$compile_normal: 0;",
"$semi_transparent: {semi_trans};".format(semi_trans=1 if alpha < 1.0 else 0),
]
total_vars = extra_vars + scss_vars
normal_source = '\n'.join(total_vars) + '\n' + str(scss_source)
try:
result = libsass.compile(
string=normal_source,
include_paths=[bootstrap_scss_path],
output_style=output_style,
precision=precision)
# remove the import bottstrap
index = result.find('body.{name} .o_dialog_container'.format(name=self.name))
result = result[index:]
results.append(result)
except libsass.CompileError as e:
_logger.error(e.args[0])
return '\n'.join(results)
@api.model
def compute_mode_style_url(self):
"""
compute mode style url
:return:
"""
for record in self:
record.mode_style_url = \
'/web/image/anita_theme_setting.anita_theme_setting/{var_id}/icon128x128'.format(
var_id=record.id)
def get_default_mode_style_text(self, theme_mode_info):
"""
get default mode data
:return:
"""
if self.name == 'normal':
return
color_vars = theme_mode_info["template_vars"]
mode_template = theme_mode_info["mode_template"]
if not mode_template:
raise exceptions.ValidationError('Can not find the theme template!')
template = mode_template.template
# css template data
template = template.replace('$mode_name', self.name)
# replace the var
for var_nam in color_vars:
template = str(template).replace(var_nam, color_vars[var_nam])
return template
@api.model
def check_default_mode_data(self, owner=False, force = False):
'''
check default mode data
:return:
'''
modes = self.search([('owner', '=', owner)])
olde_mode_cache = {mode.name: mode for mode in modes}
default_modes = \
self.env["anita_theme_setting.default_mode_data"].search([])
# create new mode if the mode do not exits
for default_mode in default_modes:
if not default_mode.data:
continue
# need to reload the mode data
mode_data = default_mode.get_mode_data()
if default_mode.name not in olde_mode_cache:
mode_data["name"] = default_mode["name"]
# create new mode use the mode data
record = self.create_mode(mode_data, owner)
if not record.mode_template:
continue
# check the templates
templates = self.env["anita_theme_setting.mode_template"].get_templates(
)
template = mode_data.get('mode_template', False)
if template and template not in templates:
raise exceptions.ValidationError(
'{template} do not exits!'.format(template=mode_data["template"]))
template = templates[mode_data["mode_template"]]
record.mode_template = template.id
# check vars are defined in the template vars
record.check_template_vars()
else:
# check version, if the version is different
mode = olde_mode_cache[default_mode.name]
if force or mode.version != mode_data["version"]:
# update template vars
temlate_var_vals = mode_data["template_var_vals"]
mode.update_template_var_values(temlate_var_vals)
# update template
mode_templates = self.env["anita_theme_setting.mode_template"].get_templates(
)
if mode_data.get("mode_template", False):
template_name = mode_data["mode_template"]
mode.mode_template = \
mode_templates.get(template_name, False)
mode.version = mode_data["version"]
mode.check_mode_data()
# recompile the template
mode.compiled_mode_style_css = False
# unlink the invalid mode
for default_mode in default_modes:
if not default_mode.data:
default_mode.unlink()
def create_mode(self, mode_config, owner=False, is_default=True):
'''
create mode
:param mode style txt:
:param mode_data:
:param owner:
:param is_default:
:return:
'''
mode_templates = \
self.env["anita_theme_setting.mode_template"].get_templates()
mode_template = mode_config.get('mode_template', False)
if mode_template and mode_template not in mode_templates:
raise exceptions.ValidationError('Can not find mode template {mode_template}'.format(
mode_template=mode_template))
# convert to cache data
template_var_vals = mode_config.get('template_var_vals', {})
var_val_cache = {}
for group in template_var_vals:
var_items = template_var_vals[group]
for var_item in var_items:
var_val_cache[var_item["name"]] = var_item["value"]
theme_styles = mode_config['theme_styles']
theme_style_array = []
for theme_style in theme_styles:
groups = theme_style["groups"]
group_datas = []
for group_index, var_items in enumerate(groups):
sub_groups = var_items["sub_groups"]
sub_group_array = []
for sub_group_index, sub_group in enumerate(sub_groups):
# create sub group
item_array = []
style_items = sub_group["style_items"]
for item_index, style_item in enumerate(style_items):
var_val_array = []
var_infos = style_item["vars"]
for var_index, var_info in enumerate(var_infos):
color = var_info.get("color", False)
ref_var = var_info.get("ref_var", False)
if ref_var:
if ref_var not in var_val_cache:
color = False
_logger.info(
"Can not find ref var {ref_var}".format(ref_var=ref_var))
else:
color = var_val_cache[ref_var]
var_val_array.append((0, 0, {
"name": var_info["name"],
"type": var_info["type"],
"sequence": var_index,
"is_default": is_default,
"color": color,
"image": var_info.get('image', False),
"image_url": var_info.get('image_url', False),
"svg": var_info.get('svg', False),
"identity": var_info.get('identity', False)
}))
item_array.append((0, 0, {
"name": style_item["name"],
"is_default": is_default,
"sequence": item_index,
"val_template": style_item["val_template"],
"sub_group": sub_group["name"],
"vars": var_val_array,
"selectors": json.dumps(style_item["selectors"])
}))
sub_group_array.append((0, 0, {
"name": sub_group["name"],
"sequence": sub_group_index,
"style_items": item_array,
"is_default": is_default
}))
group_datas.append((0, 0, {
"name": var_items["name"],
"sequence": group_index,
"is_default": is_default,
"sub_groups": sub_group_array
}))
theme_style_array.append((0, 0, {
"name": theme_style["name"],
"is_default": is_default,
"groups": group_datas
}))
vals = []
template_var_vals = mode_config.get('template_var_vals', {})
for group_name in template_var_vals:
var_items = template_var_vals[group_name]
for var_item in var_items:
vals.append((0, 0, {
"name": var_item["name"],
"group": group_name,
"value": var_item["value"],
"disable_opacity": var_item.get("disable_opacity", False),
"description": var_item["description"]
}))
mode_template = mode_config.get("mode_template", False)
return self.env["anita_theme_setting.theme_mode"].create([{
"name": mode_config["name"],
"theme_styles": theme_style_array,
"is_default": is_default,
"version": mode_config["version"],
"mode_template": mode_templates[mode_template].id if mode_template else False,
"owner": owner,
"template_var_vals": vals
}])
def get_mode_data(self):
'''
get the mode data
:return:
'''
self.ensure_theme_style()
rst = []
for record in self:
tmp_data = record.read(
['name', 'is_default', 'sequence', 'version'])[0]
tmp_data['theme_styles'] = record.theme_styles.get_styles()
rst.append(tmp_data)
return rst
def delete_mode(self):
"""
delete the mode
:return:
"""
self.unlink()
def check_style_css(self):
"""
check style css
:return:
"""
if self.mode_template and not self.compiled_mode_style_css:
self.compiled_mode_style_css = self._compile_scss()
def update_template_var_values(self, template_var_vals):
"""
update template var values
:param temlate_var_vals:
:return:
"""
self.ensure_one()
vals = [(5, 0, 0)]
for group_name in template_var_vals:
var_items = template_var_vals[group_name]
for var_item in var_items:
vals.append((0, 0, {
"name": var_item["name"],
"group": group_name,
"value": var_item["value"],
"description": var_item.get("description")
}))
self.template_var_vals = vals
def get_mode_style_css(self):
"""
get mode style css
"""
if config.get('debug', False) or not self.compiled_mode_style_css:
self.compiled_mode_style_css = self._compile_scss()
return self.compiled_mode_style_css
def check_mode_data(self):
'''
check the mode data
:return:
'''
self.ensure_one()
default_modes = self.env[
"anita_theme_setting.default_mode_data"].search([])
default_mode_cache = {
default_mode.name: default_mode for default_mode in default_modes}
mode_name = self.name
if mode_name not in default_mode_cache:
return
theme_styles = self.theme_styles
theme_style_cache = {
theme_style.name: theme_style for theme_style in theme_styles if theme_style.is_default}
default_mode = default_mode_cache[mode_name]
default_mode_data = default_mode.get_mode_data()
theme_styles = default_mode_data['theme_styles']
for theme_style in theme_styles:
# create new style
style_name = theme_style['name']
if style_name not in theme_style_cache:
self.env["anita_theme_setting.theme_style"].create_style(
self.id, theme_style)
else:
# check the style data, may be one more style with the same name
tmp_style = theme_style_cache[style_name]
tmp_style.check_groups(default_mode, theme_style["groups"])
def get_mode_edit_action(self, style_id):
"""
get theme mode edit action
"""
self.ensure_one()
form_id = self.env.ref("anita_theme_setting.edit_theme_mode_form").id
var_vals = []
for var_val in self.template_var_vals:
var_vals.append((0, 0, {
"name": var_val.name,
"group": var_val.group,
"value": var_val.value,
"disable_opacity": var_val.disable_opacity,
"description": var_val.description
}))
return {
"type": "ir.actions.act_window",
"res_model": "anita_theme_setting.theme_mode",
"views": [[form_id, "form"]],
"res_id": self.id,
"view_mode": "form",
"target": "new",
"context": {
"default_name": self.name,
"default_mode_template": self.mode_template.id,
"sequnce": self.sequence,
"template_var_vals": var_vals,
"default_style_id": style_id
}
}
@api.model
def get_new_mode_action(self):
"""
get create new mode action
"""
form_id = self.env.ref("anita_theme_setting.new_theme_mode_form").id
return {
"type": "ir.actions.act_window",
"res_model": "anita_theme_setting.theme_mode",
"views": [[form_id, "form"]],
"view_mode": "form",
"target": "new"
}
def ensure_theme_style(self):
"""
create a empty theme style
"""
if not self.theme_styles:
self.env["anita_theme_setting.theme_style"].create_style(
self.id, {
"name": "style1",
"is_default": False,
"groups": []
})