pengyuthon wechat

This commit is contained in:
李鹏宇
2024-12-16 10:57:02 +08:00
parent ba7922b266
commit 6cc8fe0d9e
259 changed files with 0 additions and 29721 deletions
-4
View File
@@ -1,4 +0,0 @@
# -*- coding: utf-8 -*-
from . import controllers
from . import models
-96
View File
@@ -1,96 +0,0 @@
# -*- coding: utf-8 -*-
{
'name': "anita_theme_setting",
'summary': """
anita theme
advance theme
sunsine theme
mnk theme for odoo
free backend theme
odoo theme
owl theme
multi mode theme
free theme
multi style theme
button style theme
crazy theme
nice theme
mutli tab theme
multi layout theme
mult tab style theme
""",
'description': """
anita theme setting for odoo, it is a powerfull theme
""",
'author': "OpenERPnext",
'website': "https://www.openerpnext.com",
'live_test_url': 'https://www.openerpnext.com',
'images': [
'static/description/screen_shot.png',
'static/description/banner.png',
],
'license': 'OPL-1',
'category': 'Themes/Backend',
'version': '17.0.0.3',
'price': 999,
'depends': ['web', 'web_enterprise'],
'data': [
'security/ir.model.access.csv',
# settings, setting page add by modules
'views/anita_theme_mode.xml',
'views/anita_theme_style.xml',
'views/anita_theme_style_group.xml',
'views/anita_theme_style_item.xml',
'views/anita_theme_style_item_sub_group.xml',
'views/anita_theme_var.xml',
'views/anita_import_theme_style.xml',
'views/anita_web.xml',
'views/anita_bk_image_wizard.xml',
'views/anita_res_setting.xml',
],
'assets': {
'web.assets_backend': [
# aniate.css
'anita_theme_setting/static/libs/animate.css',
# theme customizer
'anita_theme_setting/static/src/components/customizer/*',
'anita_theme_setting/static/src/components/settings/*.js',
'anita_theme_setting/static/src/components/settings/*.xml',
'anita_theme_setting/static/src/anita_theme_setting.js',
# according
'anita_theme_setting/static/src/components/accordion/*',
# dialog
'anita_theme_setting/static/src/components/dialog/*',
# color picker
'anita_theme_setting/static/src/components/color_picker/color_picker.js',
'anita_theme_setting/static/src/components/color_picker/color_picker.xml',
'anita_theme_setting/static/src/components/color_picker/color_picker.scss',
'anita_theme_setting/static/libs/spectrum.js',
'anita_theme_setting/static/libs/spectrum.css',
'anita_theme_setting/static/src/anita_icon.js',
# client
'anita_theme_setting/static/src/components/webclient/webclient.js',
],
'web.assets_web': [
('replace', 'web_enterprise/static/src/main.js', 'anita_theme_setting/static/src/anita_main.js'),
]
}
}
@@ -1,3 +0,0 @@
# -*- coding: utf-8 -*-
from . import anita_home
@@ -1,87 +0,0 @@
# -*- coding: utf-8 -*-
import odoo
import odoo.modules.registry
from odoo.tools.translate import _
from odoo.exceptions import AccessError
from odoo.addons.web.controllers.home import Home, SIGN_UP_REQUEST_PARAMS
from odoo.addons.web.controllers.utils import ensure_db, is_user_internal
from odoo import http
from odoo.http import request
from odoo.tools.mimetypes import guess_mimetype
from odoo.tools.misc import file_path
try:
from werkzeug.utils import send_file
except ImportError:
from odoo.tools._vendor.send_file import send_file
import json
import base64
import functools
import io
import os
import logging
_logger = logging.getLogger(__name__)
BASE_PATH = os.path.dirname(os.path.dirname(__file__))
class anitaHome(Home):
'''
inherit home to extend web.login style
'''
@http.route([
'/web/binary/company_logo',
'/logo',
'/logo.png',
], type='http', auth="none", cors="*")
def company_logo(self, dbname=None, **kw):
imgname = 'logo'
imgext = '.png'
dbname = request.db
uid = (request.session.uid if dbname else None) or odoo.SUPERUSER_ID
if not dbname:
response = http.Stream.from_path(file_path('web/static/img/logo.png')).get_response()
else:
try:
# create an empty registry
registry = odoo.modules.registry.Registry(dbname)
with registry.cursor() as cr:
company = int(kw['company']) if kw and kw.get('company') else False
if company:
cr.execute("""SELECT logo_web, write_date
FROM res_company
WHERE id = %s
""", (company,))
else:
cr.execute("""SELECT c.logo_web, c.write_date
FROM res_users u
LEFT JOIN res_company c
ON c.id = u.company_id
WHERE u.id = %s
""", (uid,))
row = cr.fetchone()
if row and row[0]:
image_base64 = base64.b64decode(row[0])
image_data = io.BytesIO(image_base64)
mimetype = guess_mimetype(image_base64, default='image/png')
imgext = '.' + mimetype.split('/')[1]
if imgext == '.svg+xml':
imgext = '.svg'
response = send_file(
image_data,
request.httprequest.environ,
download_name=imgname + imgext,
mimetype=mimetype,
last_modified=row[1],
response_class=Response,
)
else:
response = http.Stream.from_path(file_path('web/static/img/nologo.png')).get_response()
except Exception:
response = http.Stream.from_path(file_path(f'web/static/img/{imgname}{imgext}')).get_response()
return response
@@ -1,12 +0,0 @@
<odoo>
<data noupdate="0">
<function model="anita_theme_setting.mode_template" name="install_template">
<value eval="'template1'"/>
<value eval="'/anita_theme_setting/static/template/template1.scss'"/>
<value eval="['/anita_theme_setting/static/json/white_mode.json', '/anita_theme_setting/static/json/dark_mode.json']"/>
</function>
</data>
</odoo>
-21
View File
@@ -1,21 +0,0 @@
# -*- coding: utf-8 -*-
from . import anita_setting_base
from . import anita_template_var_vals
from . import anita_theme_mode
from . import anita_theme_style
from . import anita_theme_style_item_group
from . import anita_theme_style_item
from . import anita_company
from . import anita_theme_users
from . import anita_theme_vars
from . import anita_setting_manager
from . import anita_res_setting
from . import anita_default_mode_data
from . import anita_mode_template
from . import anita_theme_style_item_sub_group
from . import anita_import_theme_style
from . import anita_default_menu_icon
from . import anita_menu_extend
from . import anita_favorite_menu
from . import anita_bk_image_wizard
@@ -1,14 +0,0 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api
class AnitaBkImageWizard(models.TransientModel):
'''
background image wizard
'''
_name = 'anita_theme_setting.background_image_wizard'
_description = 'background image wizard'
data = fields.Binary(string='data', required=True)
opacity = fields.Float(string='theme opacity', default=0.8)
-130
View File
@@ -1,130 +0,0 @@
# -*- coding: utf-8 -*-
from odoo import fields, models, exceptions
import base64
from datetime import date, datetime
from odoo.tools.misc import file_path
class AnitaCompany(models.Model):
'''
extend to support current mode and current style
'''
_inherit = "res.company"
_name = 'res.company'
setting_id = fields.Many2one(
comodel_name="anita_theme_setting.setting_base",
ondelete="cascade",
string="setting id")
def _get_default_favicon_icon(self):
"""
get default favicon icon form static path
"""
tmp_path = file_path('anita_theme_setting/static/src/img/favicon.png')
return base64.b64encode(open(tmp_path, 'rb').read())
favicon_icon = fields.Binary(
string='Web Favicon Icon', default='_get_default_favicon_icon')
def _get_default_small_logo(self):
'''
get the default small logo
:return:
'''
tmp_path = file_path('anita_theme_setting/static/src/img/res_small_logo.png')
return base64.b64encode(open(tmp_path, 'rb').read())
logo_small = fields.Binary(
default=_get_default_small_logo, string="Company Small Logo")
def get_mode_data(self):
'''
get mode data
:return:
'''
owner = 'res.company, {company_id}'.format(
company_id=self.env.user.company_id)
self.env["anita_theme_setting.theme_mode"].check_default_mode_data(owner)
return self.env["anita_theme_setting.theme_mode"].get_mode_data()
def get_mode_domain(self):
"""
get mode domain dynamic
:return:
"""
owner = 'res.company, {company_id}'.format(
company_id=self.env.user.company_id.id)
return [('owner', '=', owner)]
def get_company_mode_data(self):
'''
get company mode data
:return:
'''
return self.get_mode_data()
def ensure_setting_id(self):
'''
check setting id
:return:
'''
self.ensure_one()
if self.setting_id:
return
# copy use the global default data
values = self.env['res.config.settings'].get_theme_settings()
# get fields of anita_theme_setting.setting_base
field_names = [name for name, field in self.setting_id._fields.items() if name not in [
'id', 'create_uid', 'create_date', 'write_uid', 'write_date']]
# remove name not in fields
values = {k: v for k, v in values.items() if k in field_names}
del values["current_theme_mode"]
del values["current_theme_style"]
if not self.setting_id:
tmp_record = self.env["anita_theme_setting.setting_base"].create(values)
self.setting_id = tmp_record.id
return self.setting_id
def get_theme_setting(self):
'''
get company setting
:return:
'''
record = self.env.user.company_id
record.ensure_setting_id()
result = record.setting_id.read()[0]
result["current_theme_mode"] = record.setting_id.current_theme_mode.id
result["current_theme_style"] = record.setting_id.current_theme_style.id
for key, item in result.items():
if isinstance(item, datetime):
result[key] = fields.Datetime.to_string(item)
if isinstance(item, date):
result[key] = fields.Date.to_string(item)
return result
def check_default_data(self):
"""
check default data
:return:
"""
# check mode data
owner = 'res.company, {company_id}'.format(company_id=self.id)
self.env["anita_theme_setting.theme_mode"].check_default_mode_data(owner)
def save_theme_setting(self, settings):
'''
save theme setting
:param settings:
:return:
'''
self.ensure_setting_id()
self.setting_id.write(settings)
File diff suppressed because one or more lines are too long
@@ -1,147 +0,0 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api, exceptions
from odoo.tools import ormcache
import json
from odoo.tools.misc import file_path
import logging
_logger = logging.getLogger(__name__)
class AnitaDefaultMode(models.Model):
'''
default mode
'''
_name = 'anita_theme_setting.default_mode_data'
_description = 'anita Theme Setting Mode Template'
name = fields.Char(string="Name", required=True)
data = fields.Text(string="Content", required=True)
template_var_vals = fields.Text(string="Template Var Vals")
version = fields.Char(string="Version", required=True)
config_path = fields.Char(string="Config Path", help="This is the file path of the template")
_sql_constraints = [('name_unique', 'UNIQUE(name)', "Name Must Be Unique!")]
@ormcache('self.id')
def get_mode_data(self):
"""
get mode
:return:
"""
self.ensure_one()
mode_data = json.loads(self.data)
mode_data.update({
"name": self.name,
"version": self.version
})
return mode_data
@ormcache('self.id')
def get_var_val_cache(self):
"""
get var value cache
"""
self.ensure_one()
try:
var_vals = self.template_var_vals
var_vals = var_vals.replace('\'', '\"')
data = json.loads(var_vals)
except Exception as e:
_logger.error(e)
data = {}
var_val_cache = {}
for group_name in data:
group = data[group_name]
for var_item in group:
var_val_cache[var_item['name']] = var_item['value']
return var_val_cache
@ormcache()
def get_default_modes(self):
"""
get default modes
:return:
"""
records = self.search([])
default_models = []
for record in records:
default_models.append(record.get_mode_data())
return default_models
@api.model
def refresh_default_mode(self):
"""
udpate data
"""
records = self.search([])
for record in records:
if not record.config_path:
return
tmp_path = file_path(record.config_path.lstrip('/'))
if not tmp_path:
continue
with open(tmp_path, 'r') as fd:
mode_text = fd.read()
# check the data is valid json
try:
mode_data = json.loads(mode_text)
record.write({
"data": json.dumps(mode_data),
"template_var_vals": json.dumps(mode_data.get('template_var_vals', {}))
})
except Exception as e:
self.data = False
_logger.error(e)
@api.model
def install_mode(self, config_path, template):
"""
install mode
:return:
"""
# trim start '/'
tmp_path = file_path(config_path.lstrip('/'))
if not tmp_path:
raise exceptions.ValidationError('File not found! {}'.format(config_path))
with open(tmp_path, 'r') as fd:
mode_text = fd.read()
# check the data is valid json
try:
mode_data = json.loads(mode_text)
except Exception as e:
raise exceptions.ValidationError(
'Template {template} must be a valid json file!'.format(template=config_path))
name = mode_data.get('name')
if not name:
raise exceptions.ValidationError(
'Template {template} must has a name!'.format(template=config_path))
model_record = self.search([('name', '=', name)])
if not model_record:
self.create([{
"name": name,
"data": json.dumps(mode_data),
"version": mode_data.get('version', '1.0'),
"config_path": config_path,
"template_var_vals": mode_data.get("template_var_vals", "")
}])
else:
model_record.write({
"name": name,
"data": json.dumps(mode_data),
"version": mode_data.get('version', '1.0'),
"config_path": config_path,
"template_var_vals": mode_data.get("template_var_vals", "")
})
return model_record
@@ -1,53 +0,0 @@
# -*- coding: utf-8 -*-
from odoo import api, fields, models
class AnitaFavoriteMenu(models.Model):
'''
user favorite menu
'''
_name = 'anita_theme_setting.favorite_menu'
_order = "sequence"
_description = "user favorite menu"
def _default_sequence(self):
return self.env['ir.sequence'].next_by_code('anita_theme_setting.favorite.menu')
sequence = fields.Integer(
string="sequence",
default=_default_sequence)
menu_id = fields.Many2one(
comodel_name='ir.ui.menu',
string='Favorite Menu',
required=True,
ondelete='cascade')
user_id = fields.Many2one(
comodel_name='res.users',
string='User Name',
default=lambda self: self.env.user.id)
@api.model
def unlink_menu_id(self, user_id, menu_id):
'''
unlink
:param user_id:
:param menu_id:
:return:
'''
value = self.search(
[('menu_id', '=', menu_id), ('user_id', '=', user_id)])
return value.unlink()
@api.model
def update_favorite_menu(self, favorites):
'''
update favoirte
:return:
'''
records = self.search([('user_id', '=', self.env.uid)])
records.unlink()
self.create(favorites)
return self.search_read([('user_id', '=', self.env.uid)])
@@ -1,13 +0,0 @@
# -*- coding: utf-8 -*-
from odoo import models, fields
class AnitaImportThemeStyle(models.TransientModel):
'''
user theme style setting
'''
_name = 'anita_theme_setting.import_theme_style'
_description = 'user setting'
file = fields.Binary(string="Theme File", required=True)
@@ -1,244 +0,0 @@
# -*- coding: utf-8 -*-
from odoo.tools.convert import xml_import, nodeattr2bool
import odoo
from odoo import api, fields, models, tools
from odoo.osv import expression
from .anita_default_menu_icon import svg_icons, font_icons
class AnitaMenuExtend(models.Model):
'''
extend to support font icon and svg icon
'''
_inherit = 'ir.ui.menu'
font_icon = fields.Char(string='font icon')
svg_icon = fields.Text(string='svg icon')
def load_web_menus(self, debug):
""" Loads all menu items (all applications and their sub-menus) and
processes them to be used by the webclient. Mainly, it associates with
each application (top level menu) the action of its first child menu
that is associated with an action (recursively), i.e. with the action
to execute when the opening the app.
:return: the menus (including the images in Base64)
"""
theme_setting = self.env["anita_theme_setting.setting_manager"].sudo().get_theme_setting()
icon_policy = theme_setting.get("icon_policy", "svg_icon")
menus = self.load_menus(debug)
favorite_cache = {}
if theme_setting.get("favorite_mode", False):
favorite_apps = self.env['anita_theme_setting.favorite_menu'].sudo().search(
[('user_id', '=', self.env.uid)])
favorite_cache = {fav.menu_id.id: fav for fav in favorite_apps}
web_menus = {}
for menu in menus.values():
if not menu['id']:
# special root menu case
web_menus['root'] = {
"id": 'root',
"name": menu['name'],
"children": menu['children'],
"appID": False,
"xmlid": "",
"actionID": False,
"actionModel": False,
"webIcon": None,
"webIconData": None,
"backgroundImage": menu.get('backgroundImage'),
"icon_policy": icon_policy,
}
else:
action = menu['action']
if menu['id'] == menu['app_id']:
# if it's an app take action of first (sub)child having one defined
child = menu
while child and not action:
action = child['action']
child = menus[child['children'][0]] if child['children'] else False
action_model, action_id = action.split(',') if action else (False, False)
action_id = int(action_id) if action_id else False
web_menus[menu['id']] = {
"id": menu['id'],
"name": menu['name'],
"children": menu['children'],
"appID": menu['app_id'],
"xmlid": menu['xmlid'],
"actionID": action_id,
"actionModel": action_model,
"svg_icon": menu.get('svg_icon'),
"font_icon": menu.get('font_icon'),
"webIcon": menu['web_icon'],
"webIconData": menu['web_icon_data'],
"is_favorite": True if favorite_cache.get(menu['id']) else False,
"sequence": favorite_cache.get(menu['id'], {}).sequence if favorite_cache.get(menu['id']) else 0
}
return web_menus
@api.model
# @tools.ormcache_context('self._uid', 'debug', keys=('lang',))
def load_menus(self, debug):
""" Loads all menu items (all applications and their sub-menus).
:return: the menu root
:rtype: dict('children': menu_nodes)
"""
theme_setting = self.env["anita_theme_setting.setting_manager"].sudo().get_theme_setting()
icon_policy = theme_setting.get("icon_policy", "svg_icon")
fields = ['name', 'sequence', 'parent_id', 'action', 'web_icon']
if icon_policy == "font_icon":
fields.append('font_icon')
elif icon_policy == "svg_icon":
fields.append('svg_icon')
else:
fields.append('web_icon')
fields.append('web_icon_data')
menu_roots = self.get_user_roots()
menu_roots_data = menu_roots.read(fields) if menu_roots else []
menu_root = {
'id': False,
'name': 'root',
'parent_id': [-1, ''],
'children': [menu['id'] for menu in menu_roots_data],
}
all_menus = {'root': menu_root}
if not menu_roots_data:
return all_menus
# menus are loaded fully unlike a regular tree view, cause there are a
# limited number of items (752 when all 6.1 addons are installed)
menus_domain = [('id', 'child_of', menu_roots.ids)]
blacklisted_menu_ids = self._load_menus_blacklist()
if blacklisted_menu_ids:
menus_domain = expression.AND([menus_domain, [('id', 'not in', blacklisted_menu_ids)]])
menus = self.search(menus_domain)
menu_items = menus.read(fields)
xmlids = (menu_roots + menus)._get_menuitems_xmlids()
# add roots at the end of the sequence, so that they will overwrite
# equivalent menu items from full menu read when put into id:item
# mapping, resulting in children being correctly set on the roots.
menu_items.extend(menu_roots_data)
# set children ids and xmlids
menu_items_map = {menu_item["id"]: menu_item for menu_item in menu_items}
for menu_item in menu_items:
menu_item.setdefault('children', [])
# set the default icons
if icon_policy == "svg_icon":
if not menu_item.get('svg_icon'):
menu_item["svg_icon"] = svg_icons.get(menu_item["name"], False) or False
elif icon_policy == "font_icon":
if not menu_item.get('font_icon'):
menu_item["font_icon"] = font_icons.get(menu_item["name"], False) or "fa fa-circle-o"
parent = menu_item['parent_id'] and menu_item['parent_id'][0]
menu_item['xmlid'] = xmlids.get(menu_item['id'], "")
if parent in menu_items_map:
menu_items_map[parent].setdefault(
'children', []).append(menu_item['id'])
all_menus.update(menu_items_map)
# sort by sequence
for menu_id in all_menus:
all_menus[menu_id]['children'].sort(key=lambda id: all_menus[id]['sequence'])
# recursively set app ids to related children
def _set_app_id(app_id, menu):
menu['app_id'] = app_id
for child_id in menu['children']:
_set_app_id(app_id, all_menus[child_id])
for app in menu_roots_data:
app_id = app['id']
_set_app_id(app_id, all_menus[app_id])
# filter out menus not related to an app (+ keep root menu)
all_menus = {menu['id']: menu for menu in all_menus.values() if menu.get('app_id')}
all_menus['root'] = menu_root
return all_menus
def _tag_menuitem_extend(self, rec, parent=None):
"""
extend tag menu item
"""
rec_id = rec.attrib["id"]
self._test_xml_id(rec_id)
# The parent attribute was specified, if non-empty determine its ID, otherwise
# explicitly make a top-level menu
values = {
'parent_id': False,
'active': nodeattr2bool(rec, 'active', default=True),
}
if rec.get('sequence'):
values['sequence'] = int(rec.get('sequence'))
if parent is not None:
values['parent_id'] = parent
elif rec.get('parent'):
values['parent_id'] = self.id_get(rec.attrib['parent'])
elif rec.get('web_icon'):
values['web_icon'] = rec.attrib['web_icon']
if rec.get('name'):
values['name'] = rec.attrib['name']
if rec.get('action'):
a_action = rec.attrib['action']
if '.' not in a_action:
a_action = '%s.%s' % (self.module, a_action)
act = self.env.ref(a_action).sudo()
values['action'] = "%s,%d" % (act.type, act.id)
if not values.get('name') and act.type.endswith(('act_window', 'wizard', 'url', 'client', 'server')) and act.name:
values['name'] = act.name
if not values.get('name'):
values['name'] = rec_id or '?'
groups = []
for group in rec.get('groups', '').split(','):
if group.startswith('-'):
group_id = self.id_get(group[1:])
groups.append(odoo.Command.unlink(group_id))
elif group:
group_id = self.id_get(group)
groups.append(odoo.Command.link(group_id))
if groups:
values['groups_id'] = groups
# read font icon
if rec.get('font_icon'):
values['font_icon'] = rec.get('font_icon')
if rec.get('svg_icon'):
values['svg_icon'] = rec.get('svg_icon')
data = {
'xml_id': self.make_xml_id(rec_id),
'values': values,
'noupdate': self.noupdate,
}
menu = self.env['ir.ui.menu']._load_records([data], self.mode == 'update')
for child in rec.iterchildren('menuitem'):
self._tag_menuitem(child, parent=menu.id)
xml_import._tag_menuitem = _tag_menuitem_extend
@@ -1,107 +0,0 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api, exceptions
import json
from odoo.tools import ormcache
from odoo.tools.misc import file_path
from odoo.tools.config import config
class AnitaModeTemplate(models.Model):
'''
user theme style setting
'''
_name = 'anita_theme_setting.mode_template'
_description = 'anita Mode Template'
name = fields.Char(string="Name", required=True)
template = fields.Text(string="Template")
color_vars = fields.Text(
string="Color Vars", help="This is the default var values for this template")
template_path = fields.Char(
string="Template Path", help="This is the template path of the template")
_sql_constraints = [('name_unique', 'UNIQUE(name)', "Name Must Be Unique!")]
@ormcache()
def get_templates(self):
"""
get templates
:return:
"""
records = self.search([])
return {record.name: record for record in records}
@ormcache('self.id')
def get_template(self):
"""
get template
:return:
"""
return json.loads(self.template)
@ormcache('self.id')
def get_color_vars(self):
"""
get vars
:return:
"""
return json.loads(self.color_vars)
def refresh_templates(self):
"""
update template content
"""
records = self.search([])
for record in records:
if record.template_path:
tmp_path = file_path(record.template_path.lstrip('/'))
with open(tmp_path, 'r') as fd:
template = fd.read()
record.template = template
def _register_hook(self):
"""
stuff to do right after the registry is built
"""
if config.get('debug', False):
records = self.search([])
records.refresh_templates()
@api.model
def install_template(self, name, template_path, theme_mode_paths):
"""
install mode
:return:
"""
tmp_path = file_path(template_path.lstrip('/'))
if not tmp_path:
raise exceptions.ValidationError("Template Path is not valid!")
with open(tmp_path, 'r') as fd:
template = fd.read()
if not name:
raise exceptions.ValidationError(
'Template {template} must has a name!'.format(template=template_path))
template_record = self.search([('name', '=', name)])
if not template_record:
self.create([{
"name": name,
"template": template,
"template_path": template_path
}])
else:
template_record.write({
"name": name,
"template": template,
"template_path": template_path
})
for index, mode_path in enumerate(theme_mode_paths):
record = self.env['anita_theme_setting.default_mode_data'].install_mode(
mode_path, template_record)
if index == 0:
template_record.write({
"color_vars": record.template_var_vals
})
@@ -1,109 +0,0 @@
# -*- coding: utf-8 -*-
from odoo import api, fields, models
import base64
from odoo.tools.mimetypes import guess_mimetype
from datetime import date, datetime
class AnitaUserSetting(models.TransientModel):
'''
anita user setting
'''
_inherit = 'res.config.settings'
theme_setting_mode = fields.Selection(
string="theme style mode",
config_parameter="anita_theme_setting.theme_setting_mode",
selection=[('system', 'system'),
('company', 'company'),
('user', 'user')],
default='system')
allow_debug = fields.Boolean(
string="allow debug",
config_parameter="anita_theme_setting.allow_debug",
default=False)
window_default_title = fields.Char(
string="login title",
config_parameter="anita_theme_setting.window_default_title",
default="Odoo")
powered_by = fields.Char(
string="powered by",
config_parameter="anita_theme_setting.powered_by",
default="Odoo")
@api.model
def get_theme_setting_mode(self):
'''
get theme setting mode
:return:
'''
config = self.env['ir.config_parameter'].sudo()
theme_setting_mode = config.get_param(
key='anita_theme_setting.theme_setting_mode', default='system')
return theme_setting_mode
def set_values_company_favicon(self):
'''
set the favicon of company
:return:
'''
company = self.sudo().env['res.company']
records = company.search([])
if len(records) > 0:
for record in records:
record.write({'favicon': self._set_web_favicon(original=True)})
return {
'type': 'ir.actions.client',
'tag': 'reload',
}
@api.model
def get_login_style(self):
'''
get login style
:return:
'''
ir_config = self.env['ir.config_parameter'].sudo()
login_style = ir_config.get_param(
key='anita_theme_setting.login_style', default='login_style1')
return login_style
def get_setting_record(self):
'''
get setting record
:return:
'''
record = self.env['anita_theme_setting.setting_base'].search([('owner', '=', False)], limit=1)
if not record:
record = self.env['anita_theme_setting.setting_base'].create({'owner': False})
return record
@api.model
def save_theme_setting(self, settings):
"""
save settings
"""
record = self.get_setting_record()
record.write(settings)
@api.model
def get_theme_setting(self):
"""
get theme settings
"""
record = self.get_setting_record()
result = record.read()[0]
result["current_theme_mode"] = record.current_theme_mode.id
result["current_theme_style"] = record.current_theme_style.id
for key, item in result.items():
if isinstance(item, datetime):
result[key] = fields.Datetime.to_string(item)
if isinstance(item, date):
result[key] = fields.Date.to_string(item)
return result
@@ -1,133 +0,0 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api
class AnitaThemeSettingBase(models.Model):
'''
anita Setting Base
'''
_name = 'anita_theme_setting.setting_base'
_description = 'Anita Setting Base'
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!")
layout_mode = fields.Char(
string="Layout Mode",
default='Layout1')
control_panel_mode = fields.Char(
string="Control Panel Mode",
default='mode1')
login_style = fields.Char(
string="login style",
default='login_style1')
icon_policy = fields.Selection(
string="Menu Icon Policy",
selection=[
('web_icon', 'web_icon'),
('font_icon', 'font_icon'),
('svg_icon', 'svg_icon')],
default='svg_icon')
current_theme_mode = fields.Many2one(
comodel_name='anita_theme_setting.theme_mode',
string="Current Theme Mode")
current_theme_style = fields.Many2one(
string="Current Theme Style",
comodel_name="anita_theme_setting.theme_style",
domain="[('theme_mode', '=', current_theme_mode)]",
help="Just use when theme style mode is system")
dialog_animation_in_style = fields.Char(
string="Dialog Animation In Style",
default="animate__bounceIn")
dialog_animation_out_style = fields.Char(
string="Dialog Animation Out Style",
compute="_compute_dialog_animation_out_style")
button_style = fields.Char(
string="Button Style",
default="style1")
input_style = fields.Char(
string="Input Style",
default="style1")
tab_style = fields.Char(
string="Tab Style",
default="style1")
font_name = fields.Char(
string="Font Name",
default="Roboto", help="Roboto, Helvetica, Verdana, Tahoma, OpenSans, Poppins, NotoSansArabic")
loading_style = fields.Char(
string="Loading Style",
default="style1")
loading_svg = fields.Char(string="Loading SVG", compute="_compute_loading_svg")
checkbox_style = fields.Char(
string="Checkbox Style",
default="style1")
radio_style = fields.Char(
string="Radio Style",
default="style1")
separator_style = fields.Char(
string="Separator Style",
default="style1")
rtl_mode = fields.Boolean(
string="RTL MODE",
default=False)
allow_debug = fields.Boolean(
string="Allow Debug",
default=True)
@api.onchange('current_theme_mode')
def on_current_theme_mode_change(self):
"""
change the style
:return:
"""
if self.current_theme_mode.theme_styles:
self.current_theme_style = \
self.current_theme_mode.theme_styles[0].id
else:
self.current_theme_style = False
@api.depends('loading_style')
def _compute_loading_svg(self):
"""
compute loading svg
:return:
"""
for rec in self:
if rec.loading_style:
rec.loading_svg = "/anita_theme_setting/static/loadings/loading_%s.svg" % rec.loading_style
else:
rec.loading_svg = False
@api.depends('dialog_animation_in_style')
def _compute_dialog_animation_out_style(self):
'''
compute the dialog animation out style
:return:
'''
for record in self:
if record.dialog_animation_in_style:
record.dialog_animation_out_style = record.dialog_animation_in_style.replace('In', 'Out')
else:
record.dialog_animation_out_style = 'animate__bounceOut'
@@ -1,314 +0,0 @@
# -*- coding: utf-8 -*-
from inspect import trace
from math import fabs
from odoo import models, api, tools, fields, _
from odoo.tools.config import config
import traceback
import datetime
from datetime import date, datetime
import logging
_logger = logging.getLogger(__name__)
class AnitaThemeSettingManager(models.AbstractModel):
'''
user theme style setting
'''
_name = 'anita_theme_setting.setting_manager'
_description = 'theme setting manager'
def _register_hook(self):
"""
stuff to do right after the registry is built
"""
# check theme mode data
try:
# check mode data
owner = self.get_current_owner()
force = False
if config.get('debug', False):
self.env['anita_theme_setting.default_mode_data'].sudo().refresh_default_mode()
force = True
self.env["anita_theme_setting.theme_mode"].check_default_mode_data(owner, force)
except Exception as e:
traceback.print_stack(e)
_logger.error(e)
@api.model
def get_user_setting(self, get_style=True):
'''
get user setting
:return:
'''
rst = dict()
# just get the setting data
theme_setting_mode = \
self.env["res.config.settings"].sudo().get_theme_setting_mode()
# check mode data
owner = self.get_current_owner()
# get the setting data
if theme_setting_mode == 'system':
theme_settings = self.env["res.config.settings"].get_theme_setting()
elif theme_setting_mode == 'company':
theme_settings = self.env["res.company"].get_theme_setting()
else:
theme_settings = self.env["res.users"].get_theme_setting()
# deal the datetime data
for key, item in theme_settings.items():
if isinstance(item, datetime):
theme_settings[key] = fields.Datetime.to_string(item)
if isinstance(item, date):
theme_settings[key] = fields.Date.to_string(item)
all_modes = self.env["anita_theme_setting.theme_mode"].search(
[('owner', '=', owner)])
cur_style_id = theme_settings["current_theme_style"]
cur_mode_id = theme_settings["current_theme_mode"]
theme_mode = self.env["anita_theme_setting.theme_mode"].search(
[('id', '=', cur_mode_id)])
theme_style = self.env["anita_theme_setting.theme_style"].search(
[('id', '=', cur_style_id)])
cur_mode_name = theme_mode.name
if not cur_mode_id \
or cur_mode_id not in all_modes.ids:
if all_modes:
cur_mode_id = all_modes[0].id
cur_style_id = all_modes[0].theme_styles[0].id if all_modes[0].theme_styles else False
cur_mode_name = all_modes[0].name
theme_mode = all_modes[0]
theme_style = all_modes[0].theme_styles[0] if all_modes[0].theme_styles else False
else:
cur_mode_id = False
cur_style_id = False
cur_mode_name = False
theme_mode = self.env["anita_theme_setting.theme_mode"]
if not cur_style_id:
theme_styles = theme_mode.theme_styles
theme_style = theme_styles[0] if theme_styles else self.env["anita_theme_setting.theme_style"]
cur_style_id = theme_style.id if theme_style else False
rst['settings'] = theme_settings
rst['cur_mode_id'] = cur_mode_id
rst['cur_style_id'] = cur_style_id
# used to set the body class
rst['cur_mode_name'] = cur_mode_name
rst['theme_setting_mode'] = theme_setting_mode
rst["window_default_title"] = self.env['ir.config_parameter'].sudo().get_param(
"anita_theme_setting.window_default_title", "Odoo")
rst["powered_by"] = self.env['ir.config_parameter'].sudo().get_param(
"anita_theme_setting.powered_by", "Odoo")
# check the user is anita
rst['is_anita'] = self.env.user._is_admin()
# add font info
font_name = theme_settings.get("font_name", False)
if font_name:
font_type_css = "*:not(.fa):not(.oi) {font-family: " + font_name + ", sans-serif !important;}"
else:
font_type_css = ""
rst['theme_modes'] = self.get_all_mode_data(owner)
# get the style
if get_style:
if theme_style:
style_datas = [font_type_css]
if theme_style:
style_datas += theme_style.get_styles_txt()
style_txt = '\n'.join(style_datas)
rst['style_txt'] = style_txt
if not theme_style.background_image:
# get from theme mode
rst['mode_style_css'] = theme_mode.get_mode_style_css()
else:
# get from style
rst['mode_style_css'] = theme_style.get_mode_style_css()
else:
rst['style_txt'] = ""
rst['mode_style_css'] = ""
return rst
@api.model
def get_font_link(self):
"""
get font link
:return:
"""
theme_settings = self.get_theme_setting()
result = ""
if theme_settings.get('font_name', False):
result = '/anita_theme_setting/static/fonts/{font_name}/fonts.css'.format(font_name=theme_settings.get('font_name'))
return result
@api.model
def get_theme_setting(self):
"""
get theme setting
:return:
"""
# just get the setting data
theme_setting_mode = \
self.env["res.config.settings"].sudo().get_theme_setting_mode()
if theme_setting_mode == 'system':
theme_settings = self.env["res.config.settings"].get_theme_setting()
elif theme_setting_mode == 'company':
theme_settings = self.env["res.company"].get_theme_setting()
else:
theme_settings = self.env["res.users"].get_theme_setting()
return theme_settings
@api.model
def get_font_name(self):
"""
get font name
"""
theme_settings = self.get_theme_setting()
return theme_settings.get('font_name')
@api.model
def update_cur_style(self, mode_id, style_id):
'''
update user cur mode
:return:
'''
ir_config = self.env['ir.config_parameter'].sudo()
setting_mode = self.env["res.config.settings"].get_theme_setting_mode()
if setting_mode == "system":
ir_config.set_param("anita_theme_setting.current_theme_mode", mode_id)
ir_config.set_param("anita_theme_setting.current_theme_style", style_id)
elif setting_mode == "company":
company = self.env.user.company_id
company.setting_id.current_theme_mode = mode_id
company.setting_id.current_theme_style = style_id
elif setting_mode == "user":
user_id = self.env.user.id
user = self.env["res.users"].browse(user_id)
user.setting_id.current_theme_mode = mode_id
user.setting_id.current_theme_style = style_id
else:
assert False
@api.model
def save_style_data(self, style_id, style_data, owner):
'''
save style datas
:param style_id:
:param style_data:
:param owner:
:return:
'''
theme_style = self.env["anita_theme_setting.theme_style"].browse(style_id)
theme_style.ensure_one()
theme_mode = theme_style.theme_mode
# save current style just when type is same
mode_id = theme_mode.id
if owner:
if owner.startswith('res.users'):
parts = owner.split(',')
user = self.env["res.users"].browse(int(parts[1].strip()))
if user:
user.ensure_setting_id()
user.setting_id.current_theme_mode = mode_id
user.setting_id.current_theme_style = style_id
elif owner.startswith('res.company'):
parts = owner.split(',')
company = self.env["res.company"].browse(int(parts[1].strip()))
if company:
company.ensure_setting_id()
company.setting_id.current_theme_mode = mode_id
company.setting_id.current_theme_style = style_id
else:
system_setting = self.env["res.config.settings"].get_setting_record()
system_setting.current_theme_mode = mode_id
system_setting.current_theme_style = style_id
# as the image is direct change from database so not need to update it
var_infos = theme_style.mapped('groups.sub_groups.style_items.vars')
var_cache = {var_info.id: var_info for var_info in var_infos}
for data in style_data:
var_id = data["id"]
tmp_var = var_cache[var_id]
tmp_var.write({
"color": data.get('color', False)
})
if theme_style.background_image:
mode_style_cs = theme_style.get_mode_style_css()
else:
mode_style_cs = theme_mode.get_mode_style_css()
# return the style data
return {
'theme_style': theme_style.get_style(),
'mode_style_css': mode_style_cs
}
def get_current_owner(self):
"""
get current owner
:return:
"""
theme_setting_mode = \
self.env["res.config.settings"].sudo().get_theme_setting_mode()
# check mode data
owner = False
if theme_setting_mode == 'system':
owner = False
elif theme_setting_mode == 'company':
owner = 'res.company, {company_id}'.format(
company_id=self.env.user.company_id.id)
elif theme_setting_mode == 'user':
owner = 'res.users, {user_id}'.format(user_id=self.env.user.id)
return owner
@tools.ormcache('owner')
@api.model
def get_all_mode_data(self, owner):
"""
get all mode data
"""
all_modes = self.env["anita_theme_setting.theme_mode"].search([('owner', '=', owner)])
result = []
for mode in all_modes:
result.append(mode.get_mode_data()[0])
return result
@api.model
def save_settings(self, settings):
"""
save the settings
"""
# get the setting mode
setting_mode = self.env["res.config.settings"].get_theme_setting_mode()
if setting_mode == "system":
self.env["res.config.settings"].save_theme_setting(settings)
elif setting_mode == "company":
self.env["res.company"].save_theme_setting(settings)
elif setting_mode == "user":
self.env["res.users"].save_theme_setting(settings)
else:
assert False
return self.get_theme_setting()
@@ -1,21 +0,0 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api, exceptions
import json
from odoo.tools import ormcache
class AnitaTemplateVarVal(models.Model):
'''
user theme style setting
'''
_name = 'anita_theme_setting.template_var_val'
_description = 'anita Mode Template'
theme_mode = fields.Many2one(string="Theme Mode", comodel_name="anita_theme_setting.theme_mode")
name = fields.Char(string="Name", required=True)
group = fields.Char(string="Group", required=True)
value = fields.Char(string="Value", required=True)
disable_opacity = fields.Boolean(string="Disable Opacity", default=False)
description = fields.Char(string="Description")
@@ -1,588 +0,0 @@
# -*- 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": []
})
@@ -1,506 +0,0 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api, _, exceptions, tools
import json
import base64
from odoo.tools.config import config
from .anita_utility import apply_alpha
import traceback
import logging
_logger = logging.getLogger(__name__)
class AnitaThemeStyle(models.Model):
'''
Theme style
'''
_name = 'anita_theme_setting.theme_style'
_description = 'awesome theme style'
_order = 'sequence asc, id asc'
name = fields.Char(string="style name", required=True)
theme_mode = fields.Many2one(
comodel_name="anita_theme_setting.theme_mode",
string="theme mode",
ondelete="cascade")
mode_css = fields.Text(string="mode style scss")
style_css = fields.Text(string="style css")
is_default = fields.Boolean(string="is default", default=False)
background_image = fields.Binary(string="background image")
opacity = fields.Float(string="opacity", default=1.0)
owner = fields.Reference(
string="owner",
selection=[
('res.company', 'res.company'),
('res.users', 'res.users')],
default=False,
help="Owner which this theme is create by")
sequence = fields.Integer(string="style sequence", default=0)
groups = fields.One2many(
string="Groups",
comodel_name="anita_theme_setting.style_item_group",
inverse_name="theme_style")
side_bar_style = fields.Selection(
string="side bar style",
selection=[
('style1', 'style1'),
('style2', 'style2'),
('style3', 'style3')])
sidebar_width = fields.Integer(string="sidebar width", default=80)
sidebar_menu_width = fields.Integer(
string="sidebar menu width", default=220)
@tools.ormcache('self.id')
def get_style(self):
'''
get simple style data
:return:
'''
self.ensure_one()
result = self.read(['id', 'name', 'is_default'])[0]
result["groups"] = self.groups.get_group_data()
result["theme_mode"] = self.theme_mode.name
# get background image ulr
if self.background_image:
result["background_image"] = self.get_background_image_url()
else:
result["background_image"] = False
result["opacity"] = self.opacity
identities = dict()
groups = result["groups"]
# collect identity
for group in groups:
sub_groups = group["sub_groups"]
for sub_group in sub_groups:
style_items = sub_group["style_items"]
for item in style_items:
if not item["vars"]:
continue
# collect the identity
var_infos = item["vars"]
for var in var_infos:
if var.get('identity', False):
if self.is_semi_transparent():
identities[var['identity']] = apply_alpha(var['name'], var['color'], self.opacity, var.get('color_type'))
else:
identities[var['identity']] = var['color']
result["identities"] = identities
return result
def get_styles(self):
'''
get mode datas
:return:
'''
results = []
for record in self:
results.append(record.get_style())
return results
def _get_style_data(self):
'''
get mode datas
:return:
'''
self.ensure_one()
results = []
result = self.read(['name', 'is_default', 'sequence'])[0]
if self.background_image:
result["background_image"] = self.get_background_image_url()
result["groups"] = self.groups.get_group_data()
results.append(result)
return results
def get_styles_txt(self):
'''
get style txt
:return:
'''
self.ensure_one()
mode_prefix = 'body.' + self.theme_mode.name + ' '
result = []
groups = self.groups
for group in groups:
for sub_group in group.sub_groups:
style_items = sub_group.style_items
for style_item in style_items:
val = style_item.val
# if the val is same as the ref var, then ignore it
if not val or val == "" or not style_item.selectors:
continue
try:
selector = mode_prefix + \
','.join(json.loads(style_item["selectors"]))
tmp_txt = selector + " {" + val + "}"
result.append(tmp_txt)
except Exception as error:
traceback.print_stack(e)
_logger.info(error)
# append the background image
if self.background_image:
background_image_url = self.get_background_image_url()
if background_image_url:
result.append(
mode_prefix + " {background-image: url('" +
background_image_url + "') !important;")
return result
def is_semi_transparent(self):
'''
is semi transparent
:return:
'''
self.ensure_one()
return self.opacity < 1.0
def get_mode_style_css(self):
'''
get mode styles
:return:
'''
self.ensure_one()
if not self.is_semi_transparent():
return self.theme_mode.get_mode_style_css()
else:
if config.get('debug', False) or not self.mode_css:
self.mode_css = self.theme_mode._compile_scss(self.opacity or 1)
return self.mode_css
def _compute_var_val(self, style_item_info):
'''
compute the val, use by the
:return:
'''
self.ensure_one()
background_image = self.background_image
scss_var_cache = self.theme_mode.get_scss_vars_cache()
opacity = self.opacity
vars_cache = dict()
all_color = True
all_ref = True
all_same = True
for var in style_item_info.get('vars', []):
name = var.get('name', False)
type = var.get('type', False)
if type == 'color':
# check if has opacity
color = var.get('color')
ref_var = var.get('ref_var', False)
if ref_var:
ref_color = scss_var_cache[ref_var]
if ref_color != color:
all_same = False
else:
all_ref = False
try:
if background_image:
if not var.get('disable_opacity', False):
color = apply_alpha(name, color, opacity, var.get('color_type'))
except Exception as e:
traceback.print_stack(e)
_logger.error("apply alpha error {e}".format(e=e))
vars_cache[name] = color
elif var.type == 'image':
vars_cache[name] = var.get('image_file_url')
all_color = False
elif var.type == 'image_url':
vars_cache[name] = var.get('image_url')
all_color = False
else:
vars_cache[name] = var.get('svg')
all_color = False
# if all the color are the original color, them ignore it
if all_color and all_ref and all_same:
style_item_info['val'] = False
else:
try:
val_template = style_item_info.get('val_template', False)
style_item_info['val'] = val_template.format(**vars_cache)
except Exception as error:
style_item_info['val'] = False
traceback.print_stack(e)
_logger.info('parse var value error, item name {item_name}->{error}'.format(
item_name=style_item_info['name'], error=error))
def post_deal_preview_data(self, style_item_infos, font_info, preview = False):
'''
get mode datas
:return:
'''
self.ensure_one()
if preview:
mode_prefix = 'body.preview '
else:
mode_prefix = 'body.' + self.theme_mode.name + ' '
result = [font_info]
groups = self.groups
for group in groups:
for sub_group in group.sub_groups:
style_items = sub_group.style_items
for style_item in style_items:
if str(style_item.id) not in style_item_infos:
val = style_item.val
if val == "":
continue
if not style_item.selectors:
continue
try:
selector = mode_prefix + \
','.join(json.loads(style_item["selectors"]))
tmp_txt = selector + " {" + val + "}"
result.append(tmp_txt)
except Exception as error:
traceback.print_stack(e)
_logger.info(error)
else:
style_item = style_item_infos[str(style_item.id)]
# compute the val
self._compute_var_val(style_item)
val = style_item.get('val', False)
# if the val is same as the ref var, then ignore it
selectors = style_item.get('selectors', False)
if not val or val == "" or not selectors:
continue
try:
# check selector is a array
if not isinstance(selectors, list):
selectors = [selectors]
selector = mode_prefix + ','.join(selectors)
tmp_txt = selector + " {" + val + "}"
result.append(tmp_txt)
except Exception as error:
traceback.print_stack(error)
_logger.info(error)
# append the background image
if self.background_image:
background_image_url = self.get_background_image_url()
if background_image_url:
result.append(
mode_prefix + " {background-image: url('" +
background_image_url + "') !important;")
return "\n".join(result)
@api.model
def delete_style(self, style_id):
'''
delete styel of user
:return:
'''
record = self.search([('id', '=', style_id)])
record.unlink()
def check_groups(self, default_mode, groups):
'''
check the style group
:param groups:
:return:
'''
self.ensure_one()
new_group_cache = {group["name"]: group for group in groups}
old_group_cache = {group.name: group for group in self.groups}
for group_index, name in enumerate(new_group_cache):
default_group = new_group_cache[name]
if name not in old_group_cache:
self.env['anita_theme_setting.style_item_group']\
.create_group_from_default_data(self, default_group, group_index)
else:
# update the style item
tmp_group = old_group_cache[name]
tmp_group.write({
"name": name,
"is_default": True,
"sequence": group_index,
})
# check group item data
sub_group_items = default_group["sub_groups"]
tmp_group.check_sub_group(default_mode, sub_group_items)
# delete the style item
for tmp_group_name in old_group_cache:
if tmp_group_name not in new_group_cache \
and old_group_cache[tmp_group_name].is_default:
old_group_cache[tmp_group_name].unlink()
def create_style(
self, model_id, style_data, owner=False, is_default=True):
'''
create theme style from default info
:return:
'''
default_groups = style_data['groups']
mode = self.env['anita_theme_setting.theme_mode'].browse(model_id)
scss_vars_cache = mode.get_scss_vars_cache()
group_datas = []
for group_index, group in enumerate(default_groups):
sub_groups = group["sub_groups"]
sub_group_array = []
for sub_group_index, sub_group in enumerate(sub_groups):
style_items = sub_group["style_items"]
style_item_datas = []
for style_item_index, style_item in enumerate(style_items):
selectors = json.dumps(style_item['selectors'])
var_vals = []
var_infos = style_item['vars']
for var_info in var_infos:
ref_var = var_info.get('ref', False)
if ref_var:
if ref_var in scss_vars_cache:
raise exceptions.ValidationError(
_("The variable %s has been used") % ref_var)
else:
color = scss_vars_cache[ref_var]
else:
color = var_info.get('color', False)
var_vals.append((0, 0, {
"name": var_info["name"],
"is_default": is_default,
"type": var_info["type"],
"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)
}))
tmp_style_item = dict()
tmp_style_item['selectors'] = selectors
tmp_style_item['sequence'] = style_item_index
tmp_style_item['name'] = style_item["name"]
tmp_style_item["is_default"] = is_default
tmp_style_item["val_template"] = style_item["val_template"]
tmp_style_item["vars"] = var_vals
style_item_datas.append((0, 0, tmp_style_item))
sub_group_array.append((0, 0, {
"name": sub_group["name"],
"sequence": sub_group_index,
"style_items": style_item_datas,
"is_default": True
}))
group_datas.append((0, 0, {
"name": group["name"],
"sequence": group_index,
"sub_groups": sub_group_array,
"is_default": True
}))
return self.env['anita_theme_setting.theme_style'].create({
"name": style_data["name"],
"theme_mode": model_id,
"is_default": is_default,
"groups": group_datas,
"owner": owner
})
@api.model
def add_new_style(
self, mode_id, style_data, owner=False, is_default=False):
'''
add new style, get the first style as the default, call from the front web
:return:
'''
# if not owner, use the current owner
if not owner:
owner = self.env["anita_theme_setting.setting_manager"].get_current_owner()
theme_style = self.create_style(
mode_id, style_data, owner, is_default)
# return the new style data
style_data = theme_style.get_styles()[0]
return style_data
def clone_style(self, owner=False):
'''
clone style
:return:
'''
self.ensure_one()
if not owner:
owner = self.env["anita_theme_setting.setting_manager"].get_current_owner()
style_data = self.get_styles()[0]
return self.add_new_style(self.theme_mode.id, style_data, owner)
@api.model
def import_new_style(self, mode_id, wizard_id):
"""
import new style
:param mode_id:
:param wizard_id:
:return:
"""
wizard = self.env["anita_theme_setting.import_theme_style"].browse(
wizard_id)
style_data = json.loads(
base64.decodebytes(wizard.file).decode('utf-8'))
return self.add_new_style(mode_id, style_data)
def set_bk_image(self, wizard_id):
"""
set background image
"""
self.ensure_one()
wizard = self.env["anita_theme_setting.background_image_wizard"].browse(
wizard_id)
self.background_image = wizard.data
self.opacity = wizard.opacity
# reutrn the image url
url = self.get_background_image_url()
# clear cache
self.clear_caches()
return url
def get_background_image_url(self):
"""
get image url
"""
self.ensure_one()
if self.background_image:
return 'web/image/anita_theme_setting.theme_style/' + str(self.id) + '/background_image'
else:
return False
def clear_style_css(self):
"""
clear style css when style change
"""
self.ensure_one()
self.style_css = False
@@ -1,332 +0,0 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api, exceptions
import logging
import json
from .anita_utility import apply_alpha
_logger = logging.getLogger(__name__)
class AnitaThemeStyleItem(models.Model):
'''
user theme style setting
'''
_name = 'anita_theme_setting.style_item'
_order = 'sequence asc'
_description = "style item"
sub_group = fields.Many2one(
comodel_name="anita_theme_setting.style_item_sub_group", string="sub group")
theme_mode = fields.Many2one(
comodel_name="anita_theme_setting.theme_mode",
related="sub_group.group.theme_style.theme_mode",
store=True,
string="theme mode")
theme_style = fields.Many2one(
comodel_name="anita_theme_setting.theme_style",
related = "sub_group.group.theme_style",
string="theme style")
name = fields.Char(string="name", required=True, default="var1")
val = fields.Char(string="val", compute="_compute_val")
sequence = fields.Integer(string="sequence", default=0)
mode = fields.Selection(
selection=[('override', 'override'), ('primary', 'primary')],
string="mode",
default="override",
help="override or primary, override is override the default style and primary is create a new style")
val_template = fields.Char(
string="val template",
required=True,
default="background: {var1}", help="Save the last val")
vars = fields.One2many(
string="vars",
comodel_name="anita_theme_setting.theme_var",
inverse_name="style_item")
selectors = fields.Char(string="selectors", required=True)
is_default = fields.Boolean(string="Is Default", default=True)
type = fields.Selection(
string="type",
selection=[('normal', 'normal'),
('login', 'login')],
default='normal')
login_style = fields.Char(string="login style", default="login_style1")
def check_vars(self, default_mode, sub_group_id, default_vars):
'''
check vars
:return:
'''
# check vars
self.ensure_one()
var_val_cache = default_mode.get_var_val_cache()
# get current var cache
old_var_cache = {var.name: var for var in self.vars}
default_var_cache = \
{default_var["name"]: default_var for default_var in default_vars}
tmp_vals = []
for default_var in default_vars:
color = default_var.get("color", False)
ref_var = default_var.get("ref_var",False)
if ref_var:
if ref_var not in var_val_cache:
color = False
_logger.info("ref var {ref_var} not in var cache".format(ref_var=ref_var))
else:
color = var_val_cache[ref_var]
# item exits must keep
if default_var["name"] not in old_var_cache:
tmp_vals.append({
"sub_group": sub_group_id,
"name": default_var["name"],
"style_item": self.id,
"type": default_var["type"],
"color": color,
"image": default_var.get('image', False),
"image_url": default_var.get('image_url', False),
"svg": default_var.get('svg', False),
"is_default": default_var.get('is_default', True),
"identity": default_var.get('identity', False),
"ref_var": ref_var,
"disable_opacity": default_var.get('disable_opacity', False),
})
else:
# keep the val, maybe user will
tmp_var = old_var_cache[default_var["name"]]
tmp_var.write({
"type": default_var["type"],
"is_default": default_var.get('is_default', True),
"identity": default_var.get('identity', False),
"color": color,
"image": default_var.get('image', False),
"image_url": default_var.get('image_url', False),
"svg": default_var.get('svg', False),
"ref_var": ref_var,
"disable_opacity": default_var.get('disable_opacity', False),
})
# create the missing vars
self.env["anita_theme_setting.theme_var"].create(tmp_vals)
# unlink the old var
for name in old_var_cache:
if name not in default_var_cache:
tmp_var = old_var_cache[name]
tmp_var.unlink()
@api.depends('selectors', 'val_template', "vars")
def _compute_val(self):
'''
compute the val
:return:
'''
# check if mode has background image
for record in self:
# if every var is color and the color val is same as the ref var
background_image = record.theme_style.background_image
scss_var_cache = record.theme_mode.get_scss_var_record_cache()
opacity = record.theme_style.opacity
vars_cache = dict()
all_color = True
all_ref = True
all_same = True
for var in record.vars:
if var.type == 'color':
# check if has opacity
color = var.color
if var.ref_var and var.ref_var in scss_var_cache:
ref_color = scss_var_cache[var.ref_var]
if ref_color != color:
all_same = False
else:
all_ref = False
try:
if background_image:
if not var.disable_opacity:
color = apply_alpha(var.name, color, opacity, var.color_type)
except Exception as e:
_logger.error("apply alpha error {e}".format(e=e))
vars_cache[var.name] = color
elif var.type == 'image':
vars_cache[var.name] = var.image_file_url
all_color = False
elif var.type == 'image_url':
vars_cache[var.name] = var.image_url
all_color = False
else:
vars_cache[var.name] = var.svg
all_color = False
# if all the color are the original color, them ignore it
if all_color and all_ref and all_same:
record.val = False
else:
if not record.vars:
record.val = False
else:
try:
record.val = str(record.val_template).format(**vars_cache)
except Exception as error:
record.val = False
_logger.info('parse var value error, item name {item_name}->{error}'.format(
item_name=record['name'], error=error))
def get_style_item_data(self):
'''
get the style item data
:return:
'''
export_style = self.env.context.get('export_style', False)
style_items = []
for record in self:
style_item = record.read(
fields=["id", "name", "is_default", "val_template", "val", "selectors"])[0]
try:
style_item['selectors'] = json.loads(style_item['selectors'])
except Exception as error:
_logger.info('try to decode selectors error!', error)
if not export_style:
# maybe the image is very large, so do not read the image
style_item["vars"] = \
record.vars.read(
["name", "identity", "type", "image_file_url", "image_url", "color", "svg", "color_type"])
else:
style_item["vars"] = \
record.vars.read(
["name", "identity", "type", "image", "image_url", "color", "svg", "color_type"])
for tmp_var in style_item["vars"]:
if tmp_var["image"]:
tmp_var["image"] = str(tmp_var["image"])
style_items.append(style_item)
return style_items
def write(self, vals):
"""
rewrite to check if the selectors are right
:param vals:
:return:
"""
super(AnitaThemeStyleItem, self).write(vals)
vars_cache = dict()
for var in self.vars:
if var.type == 'color':
vars_cache[var.name] = var.color
elif var.type == 'image':
vars_cache[var.name] = var.image_file_url
elif var.type == 'image_url':
vars_cache[var.name] = var.image_url
else:
vars_cache[var.name] = var.svg
# check the var
try:
str(self.val_template).format(**vars_cache)
except Exception as error:
_logger.info('parse var value error, item name {item_name}->{error}'.format(
item_name=self['name'], error=error))
def get_login_style_data(
self, theme_style_id, login_style):
"""
get login style data
:return:
"""
result = []
theme_style = self.env["anita_theme_setting.theme_style"].browse(theme_style_id)
sub_group_ids = theme_style.mapped('groups.sub_groups.id')
style_items = self.search(
[('type', '=', 'login'),
('login_style', '=', login_style),
('sub_group', 'in', sub_group_ids)])
for style_item in style_items:
val = style_item.val
if val == "":
continue
if not style_item.selectors:
continue
try:
selector = ','.join(json.loads(style_item["selectors"]))
tmp_txt = selector + " {" + val + "}"
result.append(tmp_txt)
except Exception as error:
_logger.info(error)
return ";".join(result)
def delete_style_item(self):
'''
delete style item
:return:
'''
self.ensure_one()
self.unlink()
def create_style_item(
self, default_mode, sub_group, style_item, style_item_index):
'''
create group from default data
:return:
'''
selectors = json.dumps(style_item["selectors"])
var_infos = style_item["vars"]
var_val_cache = default_mode.get_var_val_cache()
var_vals = []
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("ref var not in var cache", ref_var)
else:
color = var_val_cache[ref_var]
var_vals.append((0, 0, {
"name": var_info["name"],
"type": var_info["type"],
"sequence": var_index,
"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),
"ref_var": ref_var
}))
style_item_val = {
"name": style_item["name"],
"sequence": style_item_index,
"val_template": style_item["val_template"],
"selectors": selectors,
"vars": var_vals,
"sub_group": sub_group
}
return self.create([style_item_val])
def resert_var(self):
"""
reset var, resut to the derfault value
:return:
"""
if not self.ref_var and self.type != 'color':
return False
# get mode first
var_cache = self.sub_group.group.theme_style.theme_mode.get_scss_vars_cache()
for var in self.vars.filtered(lambda x: x.type == 'color'):
if var.ref_var:
var.color = var_cache.get(var.ref_var, False)
return True
@@ -1,199 +0,0 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api, exceptions, _, exceptions
import json
class AnitaStyleGroup(models.Model):
'''
user theme style group
'''
_name = 'anita_theme_setting.style_item_group'
_order = 'sequence asc'
_description = "style item group"
name = fields.Char(string="name", required=True)
is_default = fields.Boolean(string="is default", default=False)
theme_style = fields.Many2one(
string="theme style",
comodel_name="anita_theme_setting.theme_style",
ondelete="cascade")
sub_groups = fields.One2many(
string="sub groups",
comodel_name="anita_theme_setting.style_item_sub_group",
inverse_name="group")
sequence = fields.Integer(string="sequence", default=0)
def check_sub_group(self, default_mode, sub_groups):
'''
check group items, if not exsits create it else update it
need fixed
:return:0
'''
self.ensure_one()
old_sub_group_cache = {
sub_group.name: sub_group for sub_group in self.sub_groups}
default_sub_group_cache = {
default_group["name"]: default_group for default_group in sub_groups}
for sub_group_index, sub_group in enumerate(sub_groups):
if sub_group["name"] not in old_sub_group_cache:
self.env['anita_theme_setting.style_item_sub_group']\
.create_sub_group(default_mode, self.id, sub_group, sub_group_index)
else:
# update the style item
tmp_group = old_sub_group_cache[sub_group["name"]]
tmp_group.write({
"name": sub_group["name"],
"is_default": True,
"sequence": sub_group_index,
})
# check group item data
style_items = sub_group["style_items"]
tmp_group.check_style_items(default_mode, style_items)
# delete the style item (just not default, user may add item)
for sub_group_name in old_sub_group_cache:
if sub_group_name not in default_sub_group_cache \
and not old_sub_group_cache[sub_group_name].is_default:
old_sub_group_cache[sub_group_name].unlink()
def create_group_from_default_data(self, theme_style, group, group_index):
'''
create group from default data
:return:
'''
val = {
"name": group["name"],
"theme_style": theme_style.id,
"sequence": group_index,
}
mode = theme_style.theme_mode # get theme mode
scss_vars_cache = mode.get_scss_vars_cache()
sub_group_array = []
sub_groups = group["sub_groups"]
for sub_group_index, sub_group in enumerate(sub_groups):
style_item_vals = []
items = sub_group["style_items"]
for item_index, item in enumerate(items):
selectors = json.dumps(item["selectors"])
var_infos = item["vars"]
var_vals = []
for var_index, var_info in enumerate(var_infos):
ref_var = var_info.get('ref', False)
if ref_var:
if ref_var in scss_vars_cache:
raise exceptions.ValidationError(
_("The variable %s has been used") % ref_var)
else:
color = scss_vars_cache[ref_var]
else:
color = var_info.get('color', False)
var_vals.append((0, 0, {
"name": var_info["name"],
"type": var_info["type"],
"sequence": var_index,
"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)
}))
style_item_vals.append((0, 0, {
"name": item["name"],
"sequence": item_index,
"sub_group": sub_group["name"],
"selectors": selectors,
"vars": var_vals
}))
sub_group_array.append((0, 0, {
"name": sub_group["name"],
"sequence": sub_group_index,
"style_items": style_item_vals,
"is_default": True
}))
val["sub_groups"] = sub_group_array
return self.create([val])
@api.model
def get_add_new_group_action(self, style_id):
"""
get add new group action
:param style_id:
:return:
"""
theme_style = self.env["anita_theme_setting.theme_style"].browse(style_id)
old_groups = theme_style.groups
return {
"type": "ir.actions.act_window",
"res_model": "anita_theme_setting.style_item_group",
"view_mode": "form",
"target": "new",
"context": {
"default_theme_style": style_id,
"default_sequence": len(old_groups) + 1
},
"views": [[self.env.ref('anita_theme_setting.style_item_group_form').id, "form"]]
}
def get_group_data(self):
"""
get sub group data
:return:
"""
group_datas = []
for record in self:
group_datas.append({
"id": record.id,
"name": record.name,
"is_default": record.is_default,
"sequence": record.sequence,
"sub_groups": record.sub_groups.get_sub_group_data()
})
return group_datas
def get_add_sub_group_action(self):
"""
get add sub group action
:return:
"""
return {
"type": "ir.actions.act_window",
"res_model": "anita_theme_setting.style_item_sub_group",
"view_mode": 'form',
"target": "new",
"context": {
"default_group": self.id,
},
"views": [[self.env.ref('anita_theme_setting.style_item_sub_group_form').id, "form"]]
}
def delete_item_group(self):
"""
delete item group
:return:
"""
if len(self.sub_groups) > 0:
raise exceptions.ValidationError(
'there has some sub group left, please delete them first!')
self.unlink()
def get_sub_group_data(self):
"""
get sub group data
:return:
"""
sub_group_datas = []
for record in self:
sub_group_datas.append(record.get_sub_group_data())
return sub_group_datas
@@ -1,147 +0,0 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, exceptions
import logging, json
_logger = logging.getLogger(__name__)
class AnitaStyleSubGroup(models.Model):
'''
user theme style group
'''
_name = 'anita_theme_setting.style_item_sub_group'
_order = 'sequence asc'
_description = "style item group"
name = fields.Char(string="name", required=True)
is_default = fields.Boolean(string="is default", default=False)
group = fields.Many2one(string="group",
comodel_name="anita_theme_setting.style_item_group",
ondelete="cascade")
sequence = fields.Integer(string="sequence", default=0)
style_items = fields.One2many(
string="style items",
comodel_name="anita_theme_setting.style_item",
inverse_name="sub_group")
def check_style_items(self, default_mode, style_items):
'''
check group items, if not exsits create it else update it
need fixed
:return:0
'''
self.ensure_one()
old_style_item_cache = {
style_item.name: style_item for style_item in self.style_items if style_item.is_default}
new_style_item_cache = {
style_item["name"]: style_item for style_item in style_items}
for item_index, style_item in enumerate(style_items):
if style_item["name"] not in old_style_item_cache:
self.env["anita_theme_setting.style_item"].create_style_item(
default_mode, self.id, style_item, item_index)
else:
old_style_item = old_style_item_cache[style_item["name"]]
old_style_item.write({
"selectors": json.dumps(style_item["selectors"]),
"name": style_item["name"],
"val_template": style_item["val_template"],
"sequence": item_index,
})
item_vars = style_item["vars"]
old_style_item.check_vars(default_mode, self.id, item_vars)
# delete the style item (just not default, user may add item)
for tmp_style_item in self.style_items:
if tmp_style_item.name not in new_style_item_cache:
old_style_item_cache[tmp_style_item.name].unlink()
def create_sub_group(self, default_mode, group, sub_group, sub_group_index):
'''
create sub group from default data
:return:
'''
val = {
"name": sub_group["name"],
"group": group,
"sequence": sub_group_index,
"is_default": True
}
var_val_cache = default_mode.get_var_val_cache()
style_item_vals = []
style_items = sub_group["style_items"]
for item_index, item in enumerate(style_items):
try:
selectors = json.dumps(item["selectors"])
except Exception as error:
_logger.info('parse selectors error:', error)
continue
var_infos = item["vars"]
var_vals = []
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('ref_var not in var_val_cache:', ref_var)
else:
color = var_val_cache[ref_var]
var_vals.append((0, 0, {
"name": var_info["name"],
"type": var_info["type"],
"sequence": var_index,
"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),
"ref_var": ref_var
}))
style_item_vals.append((0, 0, {
"name": item["name"],
"sequence": item_index,
"sub_group": sub_group["name"],
"selectors": selectors,
"vars": var_vals
}))
val["style_items"] = style_item_vals
return self.create([val])
def get_sub_group_data(self):
'''
get group data
:return:
'''
sub_groups = []
for record in self:
sub_groups.append({
"id": record.id,
"name": record.name,
"style_items": record.style_items.get_style_item_data(),
"sequence": record.sequence,
"is_default": record.is_default,
"expanded": False
})
return sub_groups
def delete_sub_group(self):
"""
delete sub group
:return:
"""
self.ensure_one()
if len(self.style_items) > 0:
raise exceptions.ValidationError(
'there has some style item left, please delete the style item first!')
self.unlink()
@@ -1,124 +0,0 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api, exceptions
import datetime
from datetime import date, datetime
class AnitaThemeUsers(models.Model):
'''
user theme style setting
'''
_inherit = "res.users"
_description = 'user setting'
setting_id = fields.Many2one(
comodel_name="anita_theme_setting.setting_base",
ondelete="cascade",
string="settings")
@api.model
def save_theme_setting(self, settings):
'''
save settings, create if there is no user info
just use full when the mode is user wide mode
:param settings:
:return:
'''
self.ensure_setting_id()
self.setting_id.write(settings)
def ensure_setting_id(self):
'''
check setting id
:return:
'''
self.ensure_one()
if self.setting_id:
return
# copy use the global default data
values = self.env['res.config.settings'].get_theme_values()
# get fields of anita_theme_setting.setting_base
field_names = [name for name, field in self.setting_id._fields.items() if name not in [
'id', 'create_uid', 'create_date', 'write_uid', 'write_date']]
# remove name not in fields
values = {k: v for k, v in values.items() if k in field_names}
del values["current_theme_mode"]
del values["current_theme_style"]
if not self.setting_id:
tmp_record = self.env["anita_theme_setting.setting_base"].create(values)
self.setting_id = tmp_record.id
def get_theme_setting(self):
'''
get settings
:return:
'''
record = self.env.user
record.ensure_setting_id()
field_names = [name for name, field in record.setting_id._fields.items() if name not in [
"icon128x128", "icon144x144", "icon152x152", "icon192x192", "icon256x256", "icon512x512"]]
result = record.setting_id.read(fields=field_names)[0]
result["current_theme_mode"] = record.setting_id.current_theme_mode.id
result["current_theme_style"] = record.setting_id.current_theme_style.id
for key, item in result.items():
if isinstance(item, datetime):
result[key] = fields.Datetime.to_string(item)
if isinstance(item, date):
result[key] = fields.Date.to_string(item)
return result
def check_default_data(self):
"""
check default data
:return:
"""
# check mode data
owner = 'res.users, {user_id}'.format(user_id=self.id)
self.env["anita_theme_setting.theme_mode"].check_default_mode_data(owner)
@api.model
def get_user_xml_groups(self):
'''
:return:
'''
group_ids = self.env.user.groups_id.ids
records = self.env['ir.model.data']\
.sudo() \
.search_read([('model', '=', 'res.groups'),
('res_id', 'in', group_ids)], fields=["complete_name"])
return {record["complete_name"]: True for record in records}
def get_mode_domain(self):
"""
get mode domain dynamic
:return:
"""
owner = 'res.users, {user_id}'.format(user_id=self.id)
return [('owner', '=', owner)]
@api.model
def get_group_infos(self):
'''
:return:
'''
group_ids = self.env.user.groups_id.ids
records = self.env['ir.model.data']\
.sudo() \
.search_read(
[('model', '=', 'res.groups'),
('res_id', 'in', group_ids)],
fields=["complete_name"])
groups = {record["complete_name"]: True for record in records}
return {
"groups": groups,
"group_ids": group_ids,
"user_id": self.env.user.id
}
@@ -1,96 +0,0 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api
class AnitaThemeVar(models.Model):
'''
user theme style setting
'''
_name = 'anita_theme_setting.theme_var'
_order = 'sequence asc'
_description = "Theme Var"
sequence = fields.Integer(string="sequence", default=0)
style_item = fields.Many2one(
comodel_name="anita_theme_setting.style_item",
string="style_item",
required=True,
ondelete="cascade")
type = fields.Selection(
selection=[('color', 'color'),
('image', 'image'),
('image_url', 'image_url'),
('svg', 'svg')],
string="type",
default="color")
name = fields.Char(string="Name", default="var1")
color = fields.Char(string="Val")
color_type = fields.Selection(
selection=[
('background', 'background'),
('text', 'text'),
('border', 'border'),
('shadow', 'shadow')],
default='background', string="color type", help="Type use to apply aphla!")
disable_opacity = fields.Boolean(string="Disable Opacity", default=False)
image = fields.Binary(string="image", attachment=True)
image_file_url = fields.Char(string="Image File Url", compute="_compute_image_file_url")
image_url = fields.Char(string="Image Url")
svg = fields.Text(string="Svg")
ref_var = fields.Char(string="Ref Var Name")
opacity = fields.Float(string="Opacity", default=1.0)
identity = fields.Char(string="identify")
is_default = fields.Boolean(string="Is Default", default=False)
@api.depends('image')
def _compute_image_file_url(self):
"""
compute image file url
:return:
"""
for record in self:
if record.type == 'image' and record.image:
record.image_file_url = \
'/web/image/anita_theme_setting.theme_var/{var_id}/image'.format(
var_id=record.id)
else:
record.image_file_url = False
def get_edit_var_action(self):
"""
get edit action
:return:
"""
return {
"type": "ir.actions.act_window",
"res_model": "anita_theme_setting.theme_var",
"res_id": self.id,
"view_mode": "form",
"target": "new",
"views": [[self.env.ref(
'anita_theme_setting.edit_theme_var_form').id, "form"]]
}
def get_var_data(self):
"""
get var data
:return:
"""
return self.read(
fields=["name", "identity", "type",
"image_file_url", "image_url", "color", "svg"])
def reset_var(self):
"""
reset var
:return:
"""
if not self.ref_var and self.type != 'color':
return False
# get mode first
var_cache = self.style_item.sub_group.group.theme_style.theme_mode.get_scss_vars_cache()
if self.ref_var in var_cache:
self.color = var_cache.get(self.ref_var, False)
return True
-132
View File
@@ -1,132 +0,0 @@
# -*- coding: utf-8 -*-
from math import fabs
from odoo import exceptions
def apply_alpha(var_name, color, alpha, color_type = False):
"""
safe alpha
:param color:
:param alpha:
:return:
"""
if not color:
return ''
color.strip()
# check var_name endwith text_color
if var_name.endswith("text_color") or color_type == "text_color":
return color
# replace white to fff and black to 000 etc
if color == "white":
color = "fff"
elif color == "black":
color = "000"
elif color == "red":
color = "f00"
elif color == "green":
color = "0f0"
elif color == "blue":
color = "00f"
elif color == "yellow":
color = "ff0"
# check if start with rgba
if color.startswith("rgba"):
# get the content between ( and )
color = color[color.find("(") + 1:color.find(")")]
# get the alpha
rgba_color = color.split(",")
if len(rgba_color) == 4:
# convert to float
tmp = float(rgba_color[3])
# check var_name has shadow in it
tmp = alpha * tmp
# make rgba string
tmp = "rgba({rgba_color[0]},{rgba_color[1]},{rgba_color[2]},{tmp})".format(
rgba_color=rgba_color,
tmp=tmp)
return tmp
else:
return color
# check if start with # and length is 9
elif color.startswith("#"):
if len(color) == 9 or len(color) == 7:
# get red color
red = color[1:3]
# get green color
green = color[3:5]
# get blue color
blue = color[5:7]
# get the old alpha
if len(color) == 9:
old_alpha = color[7:9]
# convert to int
old_alpha = int(old_alpha, 16) / 255.0
new_alpha = int(old_alpha * alpha)
else:
new_alpha = alpha
# make rgba string
return "rgba({red},{green},{blue},{new_alpha})".format(
red=int(red, 16),
green=int(green, 16),
blue=int(blue, 16),
new_alpha=new_alpha)
# color like #ffff
elif len(color) == 5:
# get red color
red = color[1:2]
# get green color
green = color[2:3]
# get blue color
blue = color[3:4]
# alpha
old_alpha = color[4:5]
# convert to int
old_alpha = int(old_alpha + old_alpha, 16) / 255.0
new_alpha = old_alpha * alpha
# make rgba string
return "rgba({red},{green},{blue},{alpha})".format(
red=int(red + red, 16),
green=int(green + green, 16),
blue=int(blue + blue, 16),
alpha=new_alpha)
# color like #fff
elif len(color) == 4:
# get red color
red = color[1:2]
# get green color
green = color[2:3]
# get blue color
blue = color[3:4]
# make rgba string
return "rgba({red},{green},{blue},{alpha})".format(
red=int(red + red, 16),
green=int(green +green, 16),
blue=int(blue + blue, 16),
alpha=alpha)
else:
return color
else:
# check if start with rgb
if color.startswith("rgb"):
# get the content between ( and )
color = color[color.find("(") + 1:color.find(")")]
# get the alpha
rgb_color = color.split(",")
if len(rgb_color) == 3:
# make rgba string
tmp = "rgba({rgb_color[0]},{rgb_color[1]},{rgb_color[2]},{alpha})".format(
rgb_color=rgb_color,
alpha=alpha)
return tmp
else:
return color
else:
return 'rgba({color},{alpha})'.format(color=color, alpha=alpha)
@@ -1,16 +0,0 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
anita_theme_setting_style_item,anita_theme_setting.style_item,model_anita_theme_setting_style_item,,1,1,1,1
anita_theme_setting_theme_style,anita_theme_setting.theme_style,model_anita_theme_setting_theme_style,,1,1,1,1
anita_theme_setting_theme_mode,anita_theme_setting.theme_mode,model_anita_theme_setting_theme_mode,,1,1,1,1
anita_theme_setting_style_item_group,anita_theme_setting.style_item_group,model_anita_theme_setting_style_item_group,,1,1,1,1
anita_theme_setting_style_item_sub_group,anita_theme_setting.style_item_sub_group,model_anita_theme_setting_style_item_sub_group,,1,1,1,1
anita_theme_setting_setting_base,anita_theme_setting.setting_base,model_anita_theme_setting_setting_base,,1,1,1,1
anita_theme_setting_theme_var,anita_theme_setting.theme_var,model_anita_theme_setting_theme_var,,1,1,1,1
anita_theme_setting_setting_manager,anita_theme_setting.setting_manager,model_anita_theme_setting_setting_manager,,1,1,1,1
anita_theme_setting_default_mode_data,anita_theme_setting.default_mode_data,model_anita_theme_setting_default_mode_data,,1,1,1,1
anita_theme_setting_mode_template,anita_theme_setting.mode_template,model_anita_theme_setting_mode_template,,1,1,1,1
anita_theme_setting_import_theme_style,anita_theme_setting_import_theme_style,model_anita_theme_setting_import_theme_style,,1,1,1,1
anita_theme_setting_template_var_val,anita_theme_setting.template_var_val,model_anita_theme_setting_template_var_val,,1,1,1,1
access_anita_theme_setting_template_var_val,anita_theme_setting.template_var_val,model_anita_theme_setting_template_var_val,,1,1,1,1
access_anita_theme_setting_background_image_wizard,anita_theme_setting.background_image_wizard,model_anita_theme_setting_background_image_wizard,,1,1,1,1
access_anita_theme_setting_favorite_menu',anita_theme_setting.favorite_menu,model_anita_theme_setting_favorite_menu,,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 anita_theme_setting_style_item anita_theme_setting.style_item model_anita_theme_setting_style_item 1 1 1 1
3 anita_theme_setting_theme_style anita_theme_setting.theme_style model_anita_theme_setting_theme_style 1 1 1 1
4 anita_theme_setting_theme_mode anita_theme_setting.theme_mode model_anita_theme_setting_theme_mode 1 1 1 1
5 anita_theme_setting_style_item_group anita_theme_setting.style_item_group model_anita_theme_setting_style_item_group 1 1 1 1
6 anita_theme_setting_style_item_sub_group anita_theme_setting.style_item_sub_group model_anita_theme_setting_style_item_sub_group 1 1 1 1
7 anita_theme_setting_setting_base anita_theme_setting.setting_base model_anita_theme_setting_setting_base 1 1 1 1
8 anita_theme_setting_theme_var anita_theme_setting.theme_var model_anita_theme_setting_theme_var 1 1 1 1
9 anita_theme_setting_setting_manager anita_theme_setting.setting_manager model_anita_theme_setting_setting_manager 1 1 1 1
10 anita_theme_setting_default_mode_data anita_theme_setting.default_mode_data model_anita_theme_setting_default_mode_data 1 1 1 1
11 anita_theme_setting_mode_template anita_theme_setting.mode_template model_anita_theme_setting_mode_template 1 1 1 1
12 anita_theme_setting_import_theme_style anita_theme_setting_import_theme_style model_anita_theme_setting_import_theme_style 1 1 1 1
13 anita_theme_setting_template_var_val anita_theme_setting.template_var_val model_anita_theme_setting_template_var_val 1 1 1 1
14 access_anita_theme_setting_template_var_val anita_theme_setting.template_var_val model_anita_theme_setting_template_var_val 1 1 1 1
15 access_anita_theme_setting_background_image_wizard anita_theme_setting.background_image_wizard model_anita_theme_setting_background_image_wizard 1 1 1 1
16 access_anita_theme_setting_favorite_menu' anita_theme_setting.favorite_menu model_anita_theme_setting_favorite_menu 1 1 1 1
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 550 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 358 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 760 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 MiB

@@ -1,128 +0,0 @@
<div class="container">
<div class="oe_styling_v8">
<div class="shadow row my-5 p-3 pb-2 overflow-hidden">
<div class="container">
<div class="row">
<p style="width: 100%; font-size: 18px; text-indent:1em;" class="px-sm-4 p-2">
Anita theme setting is theme configer for our themes, it support to change theme color, font, background, header, footer, menu, etc. and also support to change theme layout, it is very easy to use and very useful for our themes.
</p>
</div>
<img src="./banner.png" style="width:100%; " />
</div>
</div>
<h2 class="w-100 text-center mb-3">(Current Release V16.0.0.1 -- 2022.11.22)</h2>
<div class="overflow-hidden mb-5 my-3 p-4 deep-3" style="background-color:#fbfcfd; border-radius:20px; border: 1px solid #d2caca66;">
<div class="container">
<h3 style="opacity:1; padding:10px 0px; color:#201111; letter-spacing:0.2em; text-transform:uppercase">
<b>Theme Settings</b>
</h3>
<hr class="mb-4 mt-0">
<h5>
there has a theme setting configer, it can set the button style\ input style \ radio style \ checkbox style etc
</h5>
<img src="./theme_setting.png" class="img img-fluid mt-3" loading="lazy">
</div>
</div>
<div class="overflow-hidden mb-5 my-3 p-4 deep-3" style="background-color:#fbfcfd; border-radius:20px; border: 1px solid #d2caca66;">
<div class="container">
<h3 style="opacity:1; padding:10px 0px; color:#201111; letter-spacing:0.2em; text-transform:uppercase">
<b>Theme Style</b>
</h3>
<hr class="mb-4 mt-0">
<h5>
there has a theme configer, it can set the theme color\ font\ background\ header\ footer\ menu etc
</h5>
<img src="./theme_style.png" class="img img-fluid mt-3" loading="lazy">
</div>
</div>
<div class="overflow-hidden mb-5 my-3 p-4 deep-3" style="background-color:#fbfcfd; border-radius:20px; border: 1px solid #d2caca66;">
<div class="container">
<h3 style="opacity:1; padding:10px 0px; color:#201111; letter-spacing:0.2em; text-transform:uppercase">
<b>Semi Trans Support</b>
</h3>
<hr class="mb-4 mt-0">
<h5>
user can set a bk image and set the alpha for elements, it will be very beautiful
</h5>
<img src="./transparent.png" class="img img-fluid mt-3" loading="lazy">
</div>
</div>
<div class="overflow-hidden mb-5 my-3 p-4 deep-3" style="background-color:#fbfcfd; border-radius:20px; border: 1px solid #d2caca66;">
<div class="container">
<h3 style="opacity:1; padding:10px 0px; color:#201111; letter-spacing:0.2em; text-transform:uppercase">
<b>Back Image Support</b>
</h3>
<hr class="mb-4 mt-0">
<h5>
user can set a bk image and set the alpha for elements, it will be very beautiful
</h5>
<img src="./transparent2.png" class="img img-fluid mt-3" loading="lazy">
</div>
</div>
<h2 style="text-align: center">
Our Other Popular Apps
</h2>
<div class="shadow row my-5 p-3 pb-2 overflow-hidden"
style="background-color:#fbfcfd; border-radius: 5px; border: 1px solid #d2caca66;">
<div class="container">
<h3 style="opacity:1; padding:10px 0px; color:#201111; letter-spacing:0.2em; text-transform:uppercase">
<b>Awesome theme Pro</b>
</h3>
<hr class="mb-4 mt-0">
<a href="https://apps.odoo.com/apps/themes/15.0/awesome_theme_pro/">
<img src="./awesome_theme.gif" class="img img-fluid mt-3" loading="lazy">
</a>
</div>
</div>
<div class="overflow-hidden mb-5 my-3 p-4 deep-3" style="background-color:#fbfcfd; border-radius:20px; border: 1px solid #d2caca66;">
<div class="container">
<h3 style="opacity:1; padding:10px 0px; color:#201111; letter-spacing:0.2em; text-transform:uppercase">
<b>Config Theme Styles</b>
</h3>
<hr class="mb-4 mt-0">
<h5>
there has a theme configer, you can change the color, image and icons of the theme
</h5>
<img src="./change_background.png" class="img img-fluid mt-3" loading="lazy">
</div>
</div>
<div class="shadow row my-5 p-3 pb-2 overflow-hidden"
style="background-color:#fbfcfd; border-radius: 5px; border: 1px solid #d2caca66;">
<div class="container">
<h3 style="opacity:1; padding:10px 0px; color:#201111; letter-spacing:0.2em; text-transform:uppercase">
<b>Anita Modeler</b>
</h3>
<hr class="mb-4 mt-0">
<a href="https://apps.odoo.com/apps/modules/14.0/anita_modeler/">
<img src="./anita_modeler.png" class="img img-fluid mt-3" loading="lazy">
</a>
</div>
</div>
<div class="shadow row my-5 p-3 pb-2 overflow-hidden"
style="background-color:#fbfcfd; border-radius: 5px; border: 1px solid #d2caca66;">
<div class="container">
<h3 style="opacity:1; padding:10px 0px; color:#201111; letter-spacing:0.2em; text-transform:uppercase">
<b>Anita List</b>
</h3>
<hr class="mb-4 mt-0">
<a href="https://apps.odoo.com/apps/modules/15.0/anita_list/">
<img src="./anita_list.gif" class="img img-fluid mt-3" loading="lazy">
</a>
</div>
</div>
</div>
</div>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 284 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 716 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 253 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 453 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 740 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 460 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 294 KiB

File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,507 +0,0 @@
/***
Spectrum Colorpicker v1.8.1
https://github.com/bgrins/spectrum
Author: Brian Grinstead
License: MIT
***/
.sp-container {
position:absolute;
top:0;
left:0;
display:inline-block;
*display: inline;
*zoom: 1;
/* https://github.com/bgrins/spectrum/issues/40 */
z-index: 9999994;
overflow: hidden;
}
.sp-container.sp-flat {
position: relative;
}
/* Fix for * { box-sizing: border-box; } */
.sp-container,
.sp-container * {
-webkit-box-sizing: content-box;
-moz-box-sizing: content-box;
box-sizing: content-box;
}
/* http://ansciath.tumblr.com/post/7347495869/css-aspect-ratio */
.sp-top {
position:relative;
width: 100%;
display:inline-block;
}
.sp-top-inner {
position:absolute;
top:0;
left:0;
bottom:0;
right:0;
}
.sp-color {
position: absolute;
top:0;
left:0;
bottom:0;
right:20%;
}
.sp-hue {
position: absolute;
top:0;
right:0;
bottom:0;
left:84%;
height: 100%;
}
.sp-clear-enabled .sp-hue {
top:33px;
height: 77.5%;
}
.sp-fill {
padding-top: 80%;
}
.sp-sat, .sp-val {
position: absolute;
top:0;
left:0;
right:0;
bottom:0;
}
.sp-alpha-enabled .sp-top {
margin-bottom: 18px;
}
.sp-alpha-enabled .sp-alpha {
display: block;
}
.sp-alpha-handle {
position:absolute;
top:-4px;
bottom: -4px;
width: 6px;
left: 50%;
cursor: pointer;
border: 1px solid black;
background: white;
opacity: .8;
}
.sp-alpha {
display: none;
position: absolute;
bottom: -14px;
right: 0;
left: 0;
height: 8px;
}
.sp-alpha-inner {
border: solid 1px #333;
}
.sp-clear {
display: none;
}
.sp-clear.sp-clear-display {
background-position: center;
}
.sp-clear-enabled .sp-clear {
display: block;
position:absolute;
top:0px;
right:0;
bottom:0;
left:84%;
height: 28px;
}
/* Don't allow text selection */
.sp-container, .sp-replacer, .sp-preview, .sp-dragger, .sp-slider, .sp-alpha, .sp-clear, .sp-alpha-handle, .sp-container.sp-dragging .sp-input, .sp-container button {
-webkit-user-select:none;
-moz-user-select: -moz-none;
-o-user-select:none;
user-select: none;
}
.sp-container.sp-input-disabled .sp-input-container {
display: none;
}
.sp-container.sp-buttons-disabled .sp-button-container {
display: none;
}
.sp-container.sp-palette-buttons-disabled .sp-palette-button-container {
display: none;
}
.sp-palette-only .sp-picker-container {
display: none;
}
.sp-palette-disabled .sp-palette-container {
display: none;
}
.sp-initial-disabled .sp-initial {
display: none;
}
/* Gradients for hue, saturation and value instead of images. Not pretty... but it works */
.sp-sat {
background-image: -webkit-gradient(linear, 0 0, 100% 0, from(#FFF), to(rgba(204, 154, 129, 0)));
background-image: -webkit-linear-gradient(left, #FFF, rgba(204, 154, 129, 0));
background-image: -moz-linear-gradient(left, #fff, rgba(204, 154, 129, 0));
background-image: -o-linear-gradient(left, #fff, rgba(204, 154, 129, 0));
background-image: -ms-linear-gradient(left, #fff, rgba(204, 154, 129, 0));
background-image: linear-gradient(to right, #fff, rgba(204, 154, 129, 0));
-ms-filter: "progid:DXImageTransform.Microsoft.gradient(GradientType = 1, startColorstr=#FFFFFFFF, endColorstr=#00CC9A81)";
filter : progid:DXImageTransform.Microsoft.gradient(GradientType = 1, startColorstr='#FFFFFFFF', endColorstr='#00CC9A81');
}
.sp-val {
background-image: -webkit-gradient(linear, 0 100%, 0 0, from(#000000), to(rgba(204, 154, 129, 0)));
background-image: -webkit-linear-gradient(bottom, #000000, rgba(204, 154, 129, 0));
background-image: -moz-linear-gradient(bottom, #000, rgba(204, 154, 129, 0));
background-image: -o-linear-gradient(bottom, #000, rgba(204, 154, 129, 0));
background-image: -ms-linear-gradient(bottom, #000, rgba(204, 154, 129, 0));
background-image: linear-gradient(to top, #000, rgba(204, 154, 129, 0));
-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#00CC9A81, endColorstr=#FF000000)";
filter : progid:DXImageTransform.Microsoft.gradient(startColorstr='#00CC9A81', endColorstr='#FF000000');
}
.sp-hue {
background: -moz-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
background: -ms-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
background: -o-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
background: -webkit-gradient(linear, left top, left bottom, from(#ff0000), color-stop(0.17, #ffff00), color-stop(0.33, #00ff00), color-stop(0.5, #00ffff), color-stop(0.67, #0000ff), color-stop(0.83, #ff00ff), to(#ff0000));
background: -webkit-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
background: linear-gradient(to bottom, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
}
/* IE filters do not support multiple color stops.
Generate 6 divs, line them up, and do two color gradients for each.
Yes, really.
*/
.sp-1 {
height:17%;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0000', endColorstr='#ffff00');
}
.sp-2 {
height:16%;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffff00', endColorstr='#00ff00');
}
.sp-3 {
height:17%;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ff00', endColorstr='#00ffff');
}
.sp-4 {
height:17%;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ffff', endColorstr='#0000ff');
}
.sp-5 {
height:16%;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0000ff', endColorstr='#ff00ff');
}
.sp-6 {
height:17%;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff00ff', endColorstr='#ff0000');
}
.sp-hidden {
display: none !important;
}
/* Clearfix hack */
.sp-cf:before, .sp-cf:after { content: ""; display: table; }
.sp-cf:after { clear: both; }
.sp-cf { *zoom: 1; }
/* Mobile devices, make hue slider bigger so it is easier to slide */
@media (max-device-width: 480px) {
.sp-color { right: 40%; }
.sp-hue { left: 63%; }
.sp-fill { padding-top: 60%; }
}
.sp-dragger {
border-radius: 5px;
height: 5px;
width: 5px;
border: 1px solid #fff;
background: #000;
cursor: pointer;
position:absolute;
top:0;
left: 0;
}
.sp-slider {
position: absolute;
top:0;
cursor:pointer;
height: 3px;
left: -1px;
right: -1px;
border: 1px solid #000;
background: white;
opacity: .8;
}
/*
Theme authors:
Here are the basic themeable display options (colors, fonts, global widths).
See http://bgrins.github.io/spectrum/themes/ for instructions.
*/
.sp-container {
border-radius: 0;
background-color: #ECECEC;
border: solid 1px #f0c49B;
padding: 0;
}
.sp-container, .sp-container button, .sp-container input, .sp-color, .sp-hue, .sp-clear {
font: normal 12px "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
box-sizing: border-box;
}
.sp-top {
margin-bottom: 3px;
}
.sp-color, .sp-hue, .sp-clear {
border: solid 1px #666;
}
/* Input */
.sp-input-container {
float:right;
width: 100px;
margin-bottom: 4px;
}
.sp-initial-disabled .sp-input-container {
width: 100%;
}
.sp-input {
font-size: 12px !important;
border: 1px inset;
padding: 4px 5px;
margin: 0;
width: 100%;
background:transparent;
border-radius: 3px;
color: #222;
}
.sp-input:focus {
border: 1px solid orange;
}
.sp-input.sp-validation-error {
border: 1px solid red;
background: #fdd;
}
.sp-picker-container , .sp-palette-container {
float:left;
position: relative;
padding: 10px;
padding-bottom: 300px;
margin-bottom: -290px;
}
.sp-picker-container {
width: 172px;
border-left: solid 1px #fff;
}
/* Palettes */
.sp-palette-container {
border-right: solid 1px #ccc;
}
.sp-palette-only .sp-palette-container {
border: 0;
}
.sp-palette .sp-thumb-el {
display: block;
position:relative;
float:left;
width: 24px;
height: 15px;
margin: 3px;
cursor: pointer;
border:solid 2px transparent;
}
.sp-palette .sp-thumb-el:hover, .sp-palette .sp-thumb-el.sp-thumb-active {
border-color: orange;
}
.sp-thumb-el {
position:relative;
}
/* Initial */
.sp-initial {
float: left;
border: solid 1px #333;
}
.sp-initial span {
width: 30px;
height: 25px;
border:none;
display:block;
float:left;
margin:0;
}
.sp-initial .sp-clear-display {
background-position: center;
}
/* Buttons */
.sp-palette-button-container,
.sp-button-container {
float: right;
}
/* Replacer (the little preview div that shows up instead of the <input>) */
.sp-replacer {
margin:0;
overflow:hidden;
cursor:pointer;
padding: 4px;
display:inline-block;
*zoom: 1;
*display: inline;
border: solid 1px #91765d;
background: #eee;
color: #333;
vertical-align: middle;
}
.sp-replacer:hover, .sp-replacer.sp-active {
border-color: #F0C49B;
color: #111;
}
.sp-replacer.sp-disabled {
cursor:default;
border-color: silver;
color: silver;
}
.sp-dd {
padding: 2px 0;
height: 16px;
line-height: 16px;
float:left;
font-size:10px;
}
.sp-preview {
position:relative;
width:25px;
height: 20px;
border: solid 1px #222;
margin-right: 5px;
float:left;
z-index: 0;
}
.sp-palette {
*width: 220px;
max-width: 220px;
}
.sp-palette .sp-thumb-el {
width:16px;
height: 16px;
margin:2px 1px;
border: solid 1px #d0d0d0;
}
.sp-container {
padding-bottom:0;
}
/* Buttons: http://hellohappy.org/css3-buttons/ */
.sp-container button {
background-color: #eeeeee;
background-image: -webkit-linear-gradient(top, #eeeeee, #cccccc);
background-image: -moz-linear-gradient(top, #eeeeee, #cccccc);
background-image: -ms-linear-gradient(top, #eeeeee, #cccccc);
background-image: -o-linear-gradient(top, #eeeeee, #cccccc);
background-image: linear-gradient(to bottom, #eeeeee, #cccccc);
border: 1px solid #ccc;
border-bottom: 1px solid #bbb;
border-radius: 3px;
color: #333;
font-size: 14px;
line-height: 1;
padding: 5px 4px;
text-align: center;
text-shadow: 0 1px 0 #eee;
vertical-align: middle;
}
.sp-container button:hover {
background-color: #dddddd;
background-image: -webkit-linear-gradient(top, #dddddd, #bbbbbb);
background-image: -moz-linear-gradient(top, #dddddd, #bbbbbb);
background-image: -ms-linear-gradient(top, #dddddd, #bbbbbb);
background-image: -o-linear-gradient(top, #dddddd, #bbbbbb);
background-image: linear-gradient(to bottom, #dddddd, #bbbbbb);
border: 1px solid #bbb;
border-bottom: 1px solid #999;
cursor: pointer;
text-shadow: 0 1px 0 #ddd;
}
.sp-container button:active {
border: 1px solid #aaa;
border-bottom: 1px solid #888;
-webkit-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
-moz-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
-ms-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
-o-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
}
.sp-cancel {
font-size: 11px;
color: #d93f3f !important;
margin:0;
padding:2px;
margin-right: 5px;
vertical-align: middle;
text-decoration:none;
}
.sp-cancel:hover {
color: #d93f3f !important;
text-decoration: underline;
}
.sp-palette span:hover, .sp-palette span.sp-thumb-active {
border-color: #000;
}
.sp-preview, .sp-alpha, .sp-thumb-el {
position:relative;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==);
}
.sp-preview-inner, .sp-alpha-inner, .sp-thumb-inner {
display:block;
position:absolute;
top:0;left:0;bottom:0;right:0;
}
.sp-palette .sp-thumb-inner {
background-position: 50% 50%;
background-repeat: no-repeat;
}
.sp-palette .sp-thumb-light.sp-thumb-active .sp-thumb-inner {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAIVJREFUeNpiYBhsgJFMffxAXABlN5JruT4Q3wfi/0DsT64h8UD8HmpIPCWG/KemIfOJCUB+Aoacx6EGBZyHBqI+WsDCwuQ9mhxeg2A210Ntfo8klk9sOMijaURm7yc1UP2RNCMbKE9ODK1HM6iegYLkfx8pligC9lCD7KmRof0ZhjQACDAAceovrtpVBRkAAAAASUVORK5CYII=);
}
.sp-palette .sp-thumb-dark.sp-thumb-active .sp-thumb-inner {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAMdJREFUOE+tkgsNwzAMRMugEAahEAahEAZhEAqlEAZhEAohEAYh81X2dIm8fKpEspLGvudPOsUYpxE2BIJCroJmEW9qJ+MKaBFhEMNabSy9oIcIPwrB+afvAUFoK4H0tMaQ3XtlrggDhOVVMuT4E5MMG0FBbCEYzjYT7OxLEvIHQLY2zWwQ3D+9luyOQTfKDiFD3iUIfPk8VqrKjgAiSfGFPecrg6HN6m/iBcwiDAo7WiBeawa+Kwh7tZoSCGLMqwlSAzVDhoK+6vH4G0P5wdkAAAAASUVORK5CYII=);
}
.sp-clear-display {
background-repeat:no-repeat;
background-position: center;
background-image: url(data:image/gif;base64,R0lGODlhFAAUAPcAAAAAAJmZmZ2dnZ6enqKioqOjo6SkpKWlpaampqenp6ioqKmpqaqqqqurq/Hx8fLy8vT09PX19ff39/j4+Pn5+fr6+vv7+wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAP8ALAAAAAAUABQAAAihAP9FoPCvoMGDBy08+EdhQAIJCCMybCDAAYUEARBAlFiQQoMABQhKUJBxY0SPICEYHBnggEmDKAuoPMjS5cGYMxHW3IiT478JJA8M/CjTZ0GgLRekNGpwAsYABHIypcAgQMsITDtWJYBR6NSqMico9cqR6tKfY7GeBCuVwlipDNmefAtTrkSzB1RaIAoXodsABiZAEFB06gIBWC1mLVgBa0AAOw==);
}
File diff suppressed because it is too large Load Diff
@@ -1,70 +0,0 @@
<svg version="1.1" id="L1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve">
<circle fill="none" stroke="#fff" stroke-width="6" stroke-miterlimit="15" stroke-dasharray="14.2472,14.2472" cx="50" cy="50" r="47" >
<animateTransform
attributeName="transform"
attributeType="XML"
type="rotate"
dur="5s"
from="0 50 50"
to="360 50 50"
repeatCount="indefinite" />
</circle>
<circle fill="none" stroke="#fff" stroke-width="1" stroke-miterlimit="10" stroke-dasharray="10,10" cx="50" cy="50" r="39">
<animateTransform
attributeName="transform"
attributeType="XML"
type="rotate"
dur="5s"
from="0 50 50"
to="-360 50 50"
repeatCount="indefinite" />
</circle>
<g fill="#fff">
<rect x="30" y="35" width="5" height="30">
<animateTransform
attributeName="transform"
dur="1s"
type="translate"
values="0 5 ; 0 -5; 0 5"
repeatCount="indefinite"
begin="0.1"/>
</rect>
<rect x="40" y="35" width="5" height="30" >
<animateTransform
attributeName="transform"
dur="1s"
type="translate"
values="0 5 ; 0 -5; 0 5"
repeatCount="indefinite"
begin="0.2"/>
</rect>
<rect x="50" y="35" width="5" height="30" >
<animateTransform
attributeName="transform"
dur="1s"
type="translate"
values="0 5 ; 0 -5; 0 5"
repeatCount="indefinite"
begin="0.3"/>
</rect>
<rect x="60" y="35" width="5" height="30" >
<animateTransform
attributeName="transform"
dur="1s"
type="translate"
values="0 5 ; 0 -5; 0 5"
repeatCount="indefinite"
begin="0.4"/>
</rect>
<rect x="70" y="35" width="5" height="30" >
<animateTransform
attributeName="transform"
dur="1s"
type="translate"
values="0 5 ; 0 -5; 0 5"
repeatCount="indefinite"
begin="0.5"/>
</rect>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB

@@ -1,14 +0,0 @@
<svg version="1.1" id="L9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 100 100" enable-background="new 0 0 0 0" xml:space="preserve">
<path fill="#fff" d="M73,50c0-12.7-10.3-23-23-23S27,37.3,27,50 M30.9,50c0-10.5,8.5-19.1,19.1-19.1S69.1,39.5,69.1,50">
<animateTransform
attributeName="transform"
attributeType="XML"
type="rotate"
dur="1s"
from="0 50 50"
to="360 50 50"
repeatCount="indefinite" />
</path>
</svg>

Before

Width:  |  Height:  |  Size: 557 B

@@ -1,23 +0,0 @@
<svg version="1.1" id="L2" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve">
<circle fill="none" stroke="#fff" stroke-width="4" stroke-miterlimit="10" cx="50" cy="50" r="48"/>
<line fill="none" stroke-linecap="round" stroke="#fff" stroke-width="4" stroke-miterlimit="10" x1="50" y1="50" x2="85" y2="50.5">
<animateTransform
attributeName="transform"
dur="2s"
type="rotate"
from="0 50 50"
to="360 50 50"
repeatCount="indefinite" />
</line>
<line fill="none" stroke-linecap="round" stroke="#fff" stroke-width="4" stroke-miterlimit="10" x1="50" y1="50" x2="49.5" y2="74">
<animateTransform
attributeName="transform"
dur="15s"
type="rotate"
from="0 50 50"
to="360 50 50"
repeatCount="indefinite" />
</line>
</svg>

Before

Width:  |  Height:  |  Size: 929 B

@@ -1,15 +0,0 @@
<svg version="1.1" id="L3" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 100 100" enable-background="new 0 0 0 0" xml:space="preserve">
<circle fill="none" stroke="#fff" stroke-width="4" cx="50" cy="50" r="44" style="opacity:0.5;"/>
<circle fill="#fff" stroke="#e74c3c" stroke-width="3" cx="8" cy="54" r="6" >
<animateTransform
attributeName="transform"
dur="2s"
type="rotate"
from="0 50 48"
to="360 50 52"
repeatCount="indefinite" />
</circle>
</svg>

Before

Width:  |  Height:  |  Size: 564 B

@@ -1,28 +0,0 @@
<svg version="1.1" id="L4" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 100 100" enable-background="new 0 0 0 0" xml:space="preserve">
<circle fill="#fff" stroke="none" cx="6" cy="50" r="6">
<animate
attributeName="opacity"
dur="1s"
values="0;1;0"
repeatCount="indefinite"
begin="0.1"/>
</circle>
<circle fill="#fff" stroke="none" cx="26" cy="50" r="6">
<animate
attributeName="opacity"
dur="1s"
values="0;1;0"
repeatCount="indefinite"
begin="0.2"/>
</circle>
<circle fill="#fff" stroke="none" cx="46" cy="50" r="6">
<animate
attributeName="opacity"
dur="1s"
values="0;1;0"
repeatCount="indefinite"
begin="0.3"/>
</circle>
</svg>

Before

Width:  |  Height:  |  Size: 826 B

@@ -1,31 +0,0 @@
<svg version="1.1" id="L5" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 100 100" enable-background="new 0 0 0 0" xml:space="preserve">
<circle fill="#fff" stroke="none" cx="6" cy="50" r="6">
<animateTransform
attributeName="transform"
dur="1s"
type="translate"
values="0 15 ; 0 -15; 0 15"
repeatCount="indefinite"
begin="0.1"/>
</circle>
<circle fill="#fff" stroke="none" cx="30" cy="50" r="6">
<animateTransform
attributeName="transform"
dur="1s"
type="translate"
values="0 10 ; 0 -10; 0 10"
repeatCount="indefinite"
begin="0.2"/>
</circle>
<circle fill="#fff" stroke="none" cx="54" cy="50" r="6">
<animateTransform
attributeName="transform"
dur="1s"
type="translate"
values="0 5 ; 0 -5; 0 5"
repeatCount="indefinite"
begin="0.3"/>
</circle>
</svg>

Before

Width:  |  Height:  |  Size: 983 B

@@ -1,26 +0,0 @@
<svg version="1.1" id="L6" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve">
<rect fill="none" stroke="#fff" stroke-width="4" x="25" y="25" width="50" height="50">
<animateTransform
attributeName="transform"
dur="0.5s"
from="0 50 50"
to="180 50 50"
type="rotate"
id="strokeBox"
attributeType="XML"
begin="rectBox.end"/>
</rect>
<rect x="27" y="27" fill="#fff" width="46" height="50">
<animate
attributeName="height"
dur="1.3s"
attributeType="XML"
from="50"
to="0"
id="rectBox"
fill="freeze"
begin="0s;strokeBox.end"/>
</rect>
</svg>

Before

Width:  |  Height:  |  Size: 756 B

@@ -1,37 +0,0 @@
<svg version="1.1" id="L7" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve">
<path fill="#fff" d="M31.6,3.5C5.9,13.6-6.6,42.7,3.5,68.4c10.1,25.7,39.2,38.3,64.9,28.1l-3.1-7.9c-21.3,8.4-45.4-2-53.8-23.3
c-8.4-21.3,2-45.4,23.3-53.8L31.6,3.5z">
<animateTransform
attributeName="transform"
attributeType="XML"
type="rotate"
dur="2s"
from="0 50 50"
to="360 50 50"
repeatCount="indefinite" />
</path>
<path fill="#fff" d="M42.3,39.6c5.7-4.3,13.9-3.1,18.1,2.7c4.3,5.7,3.1,13.9-2.7,18.1l4.1,5.5c8.8-6.5,10.6-19,4.1-27.7
c-6.5-8.8-19-10.6-27.7-4.1L42.3,39.6z">
<animateTransform
attributeName="transform"
attributeType="XML"
type="rotate"
dur="1s"
from="0 50 50"
to="-360 50 50"
repeatCount="indefinite" />
</path>
<path fill="#fff" d="M82,35.7C74.1,18,53.4,10.1,35.7,18S10.1,46.6,18,64.3l7.6-3.4c-6-13.5,0-29.3,13.5-35.3s29.3,0,35.3,13.5
L82,35.7z">
<animateTransform
attributeName="transform"
attributeType="XML"
type="rotate"
dur="2s"
from="0 50 50"
to="360 50 50"
repeatCount="indefinite" />
</path>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

@@ -1,49 +0,0 @@
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve">
<rect fill="#fff" width="3" height="100" transform="translate(0) rotate(180 3 50)">
<animate
attributeName="height"
attributeType="XML"
dur="1s"
values="30; 100; 30"
repeatCount="indefinite"/>
</rect>
<rect x="17" fill="#fff" width="3" height="100" transform="translate(0) rotate(180 20 50)">
<animate
attributeName="height"
attributeType="XML"
dur="1s"
values="30; 100; 30"
repeatCount="indefinite"
begin="0.1s"/>
</rect>
<rect x="40" fill="#fff" width="3" height="100" transform="translate(0) rotate(180 40 50)">
<animate
attributeName="height"
attributeType="XML"
dur="1s"
values="30; 100; 30"
repeatCount="indefinite"
begin="0.3s"/>
</rect>
<rect x="60" fill="#fff" width="3" height="100" transform="translate(0) rotate(180 58 50)">
<animate
attributeName="height"
attributeType="XML"
dur="1s"
values="30; 100; 30"
repeatCount="indefinite"
begin="0.5s"/>
</rect>
<rect x="80" fill="#fff" width="3" height="100" transform="translate(0) rotate(180 76 50)">
<animate
attributeName="height"
attributeType="XML"
dur="1s"
values="30; 100; 30"
repeatCount="indefinite"
begin="0.1s"/>
</rect>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

@@ -1,22 +0,0 @@
<svg version="1.1" id="L9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 100 100" enable-background="new 0 0 0 0" xml:space="preserve">
<rect x="20" y="50" width="4" height="10" fill="#fff">
<animateTransform attributeType="xml"
attributeName="transform" type="translate"
values="0 0; 0 20; 0 0"
begin="0" dur="0.6s" repeatCount="indefinite" />
</rect>
<rect x="30" y="50" width="4" height="10" fill="#fff">
<animateTransform attributeType="xml"
attributeName="transform" type="translate"
values="0 0; 0 20; 0 0"
begin="0.2s" dur="0.6s" repeatCount="indefinite" />
</rect>
<rect x="40" y="50" width="4" height="10" fill="#fff">
<animateTransform attributeType="xml"
attributeName="transform" type="translate"
values="0 0; 0 20; 0 0"
begin="0.4s" dur="0.6s" repeatCount="indefinite" />
</rect>
</svg>

Before

Width:  |  Height:  |  Size: 978 B

@@ -1,20 +0,0 @@
/** @odoo-module alias=anita_theme_setting.icons **/
const { markup, Component, xml } = owl;
const clone_style = '<svg class="tool-bar-icon clone_style" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path d="M551.9 831.5H280.1c-48.4 0-87.9-39.4-87.9-87.9V471.9c0-48.4 39.4-87.9 87.9-87.9h271.8c48.4 0 87.9 39.4 87.9 87.9v271.8c0 48.4-39.5 87.8-87.9 87.8zM280.1 448c-13.2 0-23.9 10.7-23.9 23.9v271.8c0 13.2 10.7 23.9 23.9 23.9h271.8c13.2 0 23.9-10.7 23.9-23.9V471.9c0-13.2-10.7-23.9-23.9-23.9H280.1z" fill="#9ea4aa"></path><path d="M745 192.5H473.2c-48.5 0-87.8 39.3-87.8 87.8v36.1c0 17.7 14.3 32 32 32s32-14.3 32-32v-36.1c0-13.2 10.7-23.9 23.9-23.9H745c13.2 0 23.9 10.7 23.9 23.9v271.8c0 13.2-10.7 23.8-23.8 23.8h-37.6c-17.7 0-32 14.3-32 32s14.3 32 32 32H745c48.4 0 87.9-39.4 87.9-87.8V280.3c-0.1-48.5-39.4-87.8-87.9-87.8z" fill="#9ea4aa" /></svg>'
const edit_style = '<svg class="tool-bar-icon edit_style" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="18" height="18"><path d="M883.323539 875.027084c23.223934 0 41.978067 18.803251 41.978067 41.978067 0 23.175839-18.754133 41.97909-41.978067 41.97909L140.676461 958.984241c-23.176862 0-41.97909-18.803251-41.97909-41.97909 0-23.174815 18.803251-41.978067 41.97909-41.978067L883.323539 875.027084z" fill="#9ea4aa"></path><path d="M415.094438 743.309156c-41.105187 0-79.147618-12.997012-110.48637-34.837436L172.839998 840.189647l-48.440246 0 0-48.440246 131.767046-131.767046 0 0L705.7881 210.362079 229.152539 602.602507c-4.859681-16.861016-7.871271-34.618449-7.871271-53.105498 0-106.987686 522.841929-484.482273 629.828592-484.482273 48.490388 0 48.490388 0 48.490388 48.464805C899.599224 220.468251 522.10566 743.309156 415.094438 743.309156z" fill="#9ea4aa"></path></svg>'
const edit_image = '<svg class="tool-bar-icon set_bk_image" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="20" height="20"><path d="M509.887896 135.642307c120.842229 0 241.684458-0.038886 362.526687 0.016373 40.700981 0.021489 74.195839 26.924209 82.769099 66.542532 1.329275 6.166443 1.819438 12.627598 1.823532 18.945491 0.093121 185.126324 0.127913 370.247532 0.047072 555.372833-0.020466 48.373731-36.548404 85.290525-84.897575 85.315084-241.300718 0.13917-482.59632 0.135076-723.897038 0-48.357358-0.029676-84.931345-36.953633-84.951811-85.297688-0.076748-185.122231-0.068562-370.246509-0.008186-555.36874 0.017396-49.007157 36.746926-85.518722 85.801155-85.531002 120.264061-0.013303 240.528122 0 360.79116 0l0 0L509.887896 135.642307zM510.517229 191.376679L303.759235 191.376679c-51.355645 0-102.703103 0.037862-154.058748-0.037862-9.831926-0.016373-18.139126 2.981914-24.490787 10.744715-5.233188 6.389524-6.052856 13.873985-6.052856 21.779026 0.059352 183.208649 0.046049 366.413204 0.051165 549.621852 0 2.124384 0.013303 4.247744 0.130983 6.363941 0.485047 8.890484 4.620227 15.791661 11.69025 20.990057 6.241145 4.587482 13.467733 5.207605 20.934798 5.207605 219.883943-0.037862 439.771979-0.029676 659.655921-0.029676 19.882839 0 39.766702-0.030699 59.653635 0.007163 7.379061 0.017396 14.141068-1.731434 19.883863-6.575766 7.876388-6.631024 10.220782-15.364943 10.220782-25.326828-0.071631-183.209672-0.051165-366.413204-0.055259-549.620829 0-22.370497-10.67206-33.123398-32.887015-33.128515L510.521323 191.371562l0 0L510.517229 191.376679zM510.517229 191.376679M753.7346 498.974335c-18.72241 0-37.453007-0.397043-56.167231 0.089028-31.966039 0.837064-59.256592 13.481036-83.756589 33.55421-18.329461 15.019065-36.722366 30.134321-56.298214 43.423999-25.098631 17.041118-52.92642 28.478612-83.245959 32.828686-6.664793 0.957815-13.446243 1.490957-20.178575 1.537006-25.479301 0.177032-50.979068 0.603751-76.44609-0.089028-22.180162-0.599657-42.258453 5.438872-61.283762 16.074094-15.09479 8.429996-28.566616 18.87386-40.413432 31.586392-17.506722 18.780739-35.319413 37.274952-53.28253 55.620785-10.971889 11.20418-28.037567 11.360745-39.111786 0.937348-10.879792-10.242272-12.222369-27.523867-1.744737-38.60525 22.932292-24.26259 45.561685-48.931432 69.972655-71.64883 29.018917-27.004027 63.633273-43.943838 103.526865-48.653093 6.305613-0.743944 12.703323-0.916882 19.064194-0.945535 24.709775-0.101307 49.433876-0.469698 74.131371 0.100284 18.021445 0.418532 34.770921-3.656273 50.958602-10.997472 23.51967-10.663874 45.274136-24.00574 64.312748-41.645492 25.196869-23.350824 52.990888-42.774199 86.667895-51.504024 15.566534-4.03285 31.806403-6.997368 47.824215-7.445576 38.774095-1.081635 77.595262-0.490164 116.40415-0.38374 12.307304 0.033769 21.606087 5.621021 26.539446 17.07898 4.6366 10.751878 2.676969 20.85498-4.975315 29.626761-5.819542 6.67298-13.497409 9.207709-22.261003 9.16473-20.077268-0.101307-40.154535-0.030699-60.231803-0.030699l0 0.334621 0 0L753.7346 498.974335zM753.7346 498.974335M342.600869 303.278281c30.919197 0.030699 56.005548 25.125237 55.917544 55.939033-0.089028 30.800493-25.285896 55.755862-56.216349 55.667857-30.547737-0.089028-55.490825-25.061792-55.638181-55.689347C286.515502 328.441381 311.719534 303.248605 342.600869 303.278281L342.600869 303.278281 342.600869 303.278281zM342.600869 303.278281" fill="#9ea4aa"></path></svg>'
const delete_style = '<svg class="tool-bar-icon delete_style" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path d="M784 288.96l-56.56-56.576L512.464 447.36 297.504 232.384l-56.56 56.576 214.96 214.96L240.96 718.896l56.56 56.56L512.464 560.48l214.976 214.976L784 718.88 569.04 503.92z" fill="#9ea4aa"></path></svg>'
export default {
icons: {
clone_style: markup(clone_style),
edit_style: markup(edit_style),
edit_image: markup(edit_image),
delete_style: markup(delete_style)
}
}
@@ -1,6 +0,0 @@
/** @odoo-module **/
import { startWebClient } from "@web/start";
import { AnitaWebClient } from "./components/webclient/webclient";
startWebClient(AnitaWebClient);
@@ -1,7 +0,0 @@
/** @odoo-module alias=anita_theme_setting.theme_setting**/
let settings = {
...odoo.__theme_setting__
};
export default settings
@@ -1,29 +0,0 @@
/** @odoo-module **/
import { Component, useState } from "@odoo/owl";
export class AnitaAccordion extends Component {
setup() {
this.state = useState({
'collapse': true
})
}
toggleDisplay() {
this.state.collapse = !this.state.collapse;
}
};
AnitaAccordion.props = {
title: {
type: String
},
slots: {
type: Object,
optional: true,
},
};
AnitaAccordion.template = "anita_theme_setting.accordion";
AnitaAccordion.components = {};
@@ -1,59 +0,0 @@
.anita_accordion {
display: flex;
flex-direction: column;
.card-title {
border: 1px solid #dee2e6 !important;
}
.accordion_button {
position: relative;
display: flex;
align-items: center;
width: 100%;
padding: .75rem 1.25rem;
font-size: 1rem;
color: #212529;
text-align: left;
background-color: #fff;
border: 0;
border-radius: 0;
overflow-anchor: none;
transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out,box-shadow .15s ease-in-out,border-radius .15s ease;
&::after {
flex-shrink: 0;
width: 0.75rem;
height: 0.75em;
margin-left: auto;
content: "";
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23212529'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
background-repeat: no-repeat;
background-size: 0.75rem;
transition: transform .2s ease-in-out;
}
&:focus {
outline: 1px dotted;
}
}
&.collapsed {
.accordion-body {
display: none !important;
}
}
&:not(.collapsed) {
.accordion_button {
color: #0c63e4;
background-color: #e7f1ff;
&::after {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%230c63e4'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
transform: rotate(-180deg);
}
}
}
}
@@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<t t-name="anita_theme_setting.accordion" owl="1">
<div class="anita_accordion" t-att-class="{'collapsed': state.collapse}">
<div class="card-title">
<button class="accordion_button" type="button" t-on-click.prevent="toggleDisplay">
<t t-esc="props.title"/>
</button>
</div>
<div class="accordion-body" >
<t t-slot="default"/>
</div>
</div>
</t>
</templates>
@@ -1,79 +0,0 @@
/** @odoo-module alias=anita_theme_setting.color_picker **/
import { _lt } from "@web/core/l10n/translation";
import { Component, useEffect, useState, useRef, onMounted } from "@odoo/owl";
export default class anitaColorPicker extends Component {
setup() {
this.colorPickerInputRef = useRef("color_picker_input");
this.colorValueInputRef = useRef("color_value_input");
this.state = useState({
color_value: this.props.color,
});
onMounted(this.onMounted);
// when color changed nee reset the color picker val
useEffect(
() => {
window.$(this.colorPickerInputRef.el).spectrum("set", this.state.color_value);
},
() => [this.state.color_value]
);
}
onMounted() {
$(this.colorPickerInputRef.el).spectrum({
showAlpha: true,
showInput: true,
preferredFormat: "rgb",
color: this.props.color_value,
palette: [
["#000","#444","#666","#999","#ccc","#eee","#f3f3f3","#fff"],
["#f00","#f90","#ff0","#0f0","#0ff","#00f","#90f","#f0f"],
["#f4cccc","#fce5cd","#fff2cc","#d9ead3","#d0e0e3","#cfe2f3","#d9d2e9","#ead1dc"],
["#ea9999","#f9cb9c","#ffe599","#b6d7a8","#a2c4c9","#9fc5e8","#b4a7d6","#d5a6bd"],
["#e06666","#f6b26b","#ffd966","#93c47d","#76a5af","#6fa8dc","#8e7cc3","#c27ba0"],
["#c00","#e69138","#f1c232","#6aa84f","#45818e","#3d85c6","#674ea7","#a64d79"],
["#900","#b45f06","#bf9000","#38761d","#134f5c","#0b5394","#351c75","#741b47"],
["#600","#783f04","#7f6000","#274e13","#0c343d","#073763","#20124d","#4c1130"]
],
change: (color) => {
let current_color = undefined;
if (color._a !== 1) {
current_color = color.toRgbString();
} else {
current_color = color.toHexString(); ;
}
if (this.state.color_value !== current_color) {
this.state.color_value = current_color;
// notify change the color
this.props.onChange(current_color);
}
}
});
}
};
// props
anitaColorPicker.props = {
color: {
type: String,
optional: 1,
},
onChange: {
type: Function,
optional: true,
},
};
// default props
anitaColorPicker.defaultProps = {
color: '#ffffff',
};
anitaColorPicker.template = "anita_theme_setting.color_picker";
@@ -1,27 +0,0 @@
.style_color_item {
.sp-preview {
margin-right: 0px;
}
.sp-dd {
display: none;
}
.sp-preview {
width: 18px;
height: 12px;
}
.sp-replacer {
border-color: #dee2e6;
&:hover {
border-color: #dee2e6;
}
}
}
.sp-container {
border-color: #dee2e6;
}
@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<!-- template for color picker -->
<t t-name="anita_theme_setting.color_picker" owl="1">
<div class="style-item-ver input-group style_color_item mr-3">
<input type="text" class="form-control style_item_var" t-model="state.color_value" t-ref="color_value_input" />
<span class="input-group-append">
<span class="input-group-text colorpicker-input-addon">
<input t-ref="color_picker_input" />
</span>
</span>
</div>
</t>
</templates>
File diff suppressed because it is too large Load Diff
@@ -1,447 +0,0 @@
.customizer {
width: 415px;
right: -500px;
padding: 0;
background-color: #FFF;
z-index: 1001;
position: fixed;
top: 0;
bottom: 0;
height: 100vh;
-webkit-transition: right .4s cubic-bezier(.05, .74, .2, .99);
transition: right .4s cubic-bezier(.05, .74, .2, .99);
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
border-left: 1px solid rgba(0, 0, 0, .05);
box-shadow: 0 15px 30px 0 rgba(0, 0, 0, .11), 0 5px 15px 0 rgba(0, 0, 0, .08);
color: #212529;
a,
label,
span,
i {
color: #212529;
}
i {
&:hover {
color: #71639e !important;
}
}
@include media-breakpoint-down(sm) {
right: -550px;
width: 100%;
}
&.show {
right: 0
}
.dropdown {
height: 100% !important;
.dropdown-toggle {
height: 100% !important;
}
}
svg {
&:hover {
path {
fill: #71639e;
}
}
}
.o_input {
line-height: 30px;
}
.config_container {
overflow: hidden;
.tabs {
height: 100%;
overflow: hidden;
width: 30px;
background-color: #e6e6e6;
border-left: 1px solid #e6e6e6;
padding-top: 15px;
flex-shrink: 0 !important;
.tab_item {
padding: 5px 0px;
height: 85px;
-webkit-writing-mode: vertical-lr;
writing-mode: vertical-lr;
display: flex;
align-items: center;
justify-content: center;
user-select: none !important;
cursor: pointer;
span {
padding-top: 5px;
}
&.active {
border-left: 1px solid rgb(73, 80, 87);
}
}
}
.tab_panes {
overflow: hidden;
.scroller {
height: 100%;
margin-right: -16px;
padding: 5px 15px 5px 5px;
overflow-y: auto;
.settings_pane, .styles_pane {
flex: 1 1 auto;
overflow-y: auto;
overflow-x: hidden;
}
}
}
}
.styles_pane {
position: relative;
ul.navbar-nav {
li.nav-item {
&:focus {
outline: none;
}
a.nav-link {
padding: 5px;
}
}
}
.theme_mode_tool_bar {
height: 30px;
line-height: 30px;
border-top: 1px solid #dee2e6;
border-bottom: 1px solid #dee2e6;
background-color: #eee;
text-align: center;
margin-top: -1px;
.create_new_theme_mode {
font-size: 1.1rem;
}
}
.add_new_style_icon {
fill: #dee2e6;
}
.operation-toolbar {
height: 30px;
background-color: #f2f2f2;
border-bottom: 1px solid #dee2e6;
.tool-bar-icon {
margin: 0px 5px;
}
}
.mode_tools {
position: absolute;
right: 1rem;
}
.style-preview-box {
position: relative;
width: 100px;
height: 60px;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
overflow: hidden;
background-size: cover;
&:hover {
transform: translate3d(0px, 0px, -2px);
}
.preview-sidebar-box {
width: 30px;
border-right: 1px solid #dee2e6;
}
.preview-main-box {
.anita_header-box {
height: 30px
}
.footer-box {
height: 30px;
}
}
.preview-header {
border-bottom: 1px solid #ebebeb;
}
.preview-footer {
border-top: 1px solid #ebebeb;
}
.nav-pills {
.nav-link.active,
.nav-pills .show {
>.nav-link {
background-color: transparent !important;
border: 1px solid #e3e3e3 !important
}
}
}
}
.theme-style-contianer {
.theme_style_tab {
padding: 5px;
box-sizing: border-box;
background: transparent;
border: 1px solid;
border-color: transparent;
margin: 1px;
&:hover {
border-color: #5A8DEE !important;
.operation-toolbar {
top: 0px;
}
}
&.active {
background: transparent;
border: 1px solid darken($color: #5A8DEE, $amount: 10) !important;
}
}
.theme_style_tool_bar {
.theme_style_tab {
.tool_bar_item {
font-size: 16px;
&:hover {
color: black;
border-color: #5A8DEE !important;
}
}
}
}
}
.style_tool_bar {
background-color: #eee;
height: 30px;
}
.style_item_container {
.style_item_group {
.style_items {
.style_item {
margin-left: 10px;
width: 220px;
.title {
height: 30px;
line-height: 30px;
display: flex;
}
.color-container {
.style_color_item {
margin-left: 5px;
}
.pickr {
margin: 5px;
.pcr-button:before {
background: transparent !important;
}
}
.color_value {
display: flex;
align-items: center;
justify-items: left;
}
}
.colorpicker-input-addon {
border: 1px solid #dee2e6;
}
}
}
.style_item_group_name {
height: 30px;
line-height: 30px;
border-top: 1px solid #dee2e6;
border-bottom: 1px solid #dee2e6;
background-color: #eee;
text-align: center;
margin-top: -1px;
.style_group_dropdown {
&.dropdown-toggle {
font-size: 0.75rem;
&::after {
display: none !important;
}
}
}
}
.style_sub_group_name {
color: #182139;
background: rgba(238, 238, 238, 0.51);
height: 30px;
line-height: 30px;
padding-left: 10px;
border-bottom: 1px solid #dee2e6;
.sub_group_toggler i {
font-size: 0.75rem;
}
}
.sub-group-body {
.style_group_dropdown {
&.dropdown-toggle {
font-size: 0.75rem;
&::after {
display: none !important;
}
}
}
.sub_group_panel {
background: #f6f6f6;
}
}
.add_style_item {
float: right;
}
.add_item_group_container {
margin-bottom: -1.5rem;
border-bottom: none;
}
}
.add_new_group {
height: 30px;
line-height: 30px;
border-top: none;
border-bottom: 1px solid #dee2e6;
background-color: #eee;
text-align: center;
}
}
}
.customizer-close {
position: absolute;
right: 10px;
top: 10px;
padding: 7px;
width: auto;
z-index: 10;
color: #727E8C;
i {
font-size: 1.3rem
}
.customizer-toggle {
background: #5A8DEE;
color: #FFF;
display: block;
box-shadow: -3px 0 8px rgba(0, 0, 0, .1);
border-top-left-radius: 6px;
border-bottom-left-radius: 6px;
position: absolute;
top: 50%;
width: 38px;
height: 38px;
left: -39px;
text-align: center;
line-height: 44px;
cursor: pointer
}
.color-box {
height: 30px;
width: 30px;
margin: .5rem;
border-radius: .3rem;
cursor: pointer;
&.selected {
box-shadow: 0 0 0 3px rgba(52, 144, 220, .5)
}
}
ul.list-inline li {
display: inline-block;
}
h5 {
margin-bottom: .5rem;
font-family: Rubik, Helvetica, Arial, serif;
font-weight: 400;
line-height: 1.2;
color: #475F7B;
}
.custom-control-label {
padding-top: 2px;
}
}
.top_title {
height: 60px;
flex-shrink: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
border-bottom: 1px solid #ebebeb;
box-shadow: -1px 2px 25px 0 rgba(0, 0, 0, 0.1);
}
.customizer-footer {
align-self: flex-end;
flex-shrink: 0;
border-top: 1px solid rgb(235, 235, 235);
background-color: rgba(255, 255, 255, 0.733);
box-shadow: 0 -4px 25px 0 rgba(0, 0, 0, .1);
height: 50px;
width: 100%;
button {
margin-left: 0.5rem;
}
}
}
@@ -1,298 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<!-- style setting -->
<div t-name="anita_theme_setting.customizer" class="dropdown" owl="1">
<a role="button" href="#" class="customizer-toggle dropdown-toggle o-no-caret o-dropdown--narrow" t-on-click="_on_toggle_click">
<span class="fa fa-cog fa-fw fa-lg"/>
</a>
<t t-if="state.inited">
<t t-call="anita_theme_setting.customizer.panel"/>
</t>
</div>
<!-- customizer -->
<div t-name="anita_theme_setting.customizer.panel" class="customizer d-flex flex-column" t-att-class="{'show': state.show_panel}" owl='1' ref="panel">
<a class="customizer-close" href="#" t-on-click="_on_close_click">
<i class="fa fa-close"/>
</a>
<div class="d-flex flex-column flex-fill" style="overflow: hidden;">
<div class="top_title">
<h4 class="text-uppercase mb-0">Theme Customizer</h4>
</div>
<div class="config_container flex-fill d-flex flex-row">
<!-- tab panes -->
<div class="tab_panes flex-fill">
<div class="scroller">
<!-- themem settings -->
<div class="settings_pane flex-fill d-flex flex-column" style="overflow-y: hidden" t-att-class="{'d-none': state.acitve_item == 'style'}">
<ThemeSettings bus="setting_component_bus" />
</div>
<!-- theme style -->
<div class="styles_pane flex-fill d-flex flex-column" style="overflow-y: hidden" t-att-class="{'d-none': state.acitve_item == 'setting'}">
<div class="theme_modes d-flex flex-column flex-fill" style="overflow-y: auto">
<t t-set="theme_modes" t-value="user_setting.theme_modes"/>
<div class="theme_modes d-flex flex-column">
<!-- tab item -->
<!-- <div class="custom-controls-stacked theme_mode_name_container p-4">
<t t-call="anita_theme_setting.theme_mode_tab" />
</div> -->
<!-- create new theme mode -->
<!-- <div class="theme_mode_tool_bar">
<a class="create_new_theme_mode" href="#" t-on-click.prevent="_create_new_mode">
<i class="fa fa-plus-square ml-1 mr-1"/>
Create New Mode
</a>
</div> -->
<!-- theme mode content -->
<div class="theme-modes-container d-flex flex-column flex-fill">
<t t-foreach="theme_modes || []" t-as="theme_mode" t-key="theme_mode.id">
<t t-call="anita_theme_setting.theme_mode" />
</t>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- tab items -->
<!-- <div class="tabs d-flex flex-column">
<div class="tab_item" data-tab-id="columns" t-att-class="{'active': state.acitve_item == 'style'}" t-on-click.prevent="() => { this.state.acitve_item = 'style' }">
<i class="fa fa-list-ul"></i>
<span>Sytle</span>
</div>
<div class="tab_item" data-tab-id="settings" t-att-class="{'active': state.acitve_item == 'setting'}" t-on-click.prevent="() => { this.state.acitve_item = 'setting' }">
<i class="fa fa-filter"></i>
<span>Settings</span>
</div>
</div> -->
</div>
<!-- button for theme style -->
<div class="customizer-footer d-flex align-items-center justify-content-left" >
<t t-if="state.acitve_item == 'style'">
<button class="preview-btn btn btn-primary ml-2" t-on-click="_on_preview_click">Preview</button>
<button class="reset-preview-btn btn btn-primary ml-2" t-on-click='_on_reset_click' >Reset</button>
<button class="save-btn btn btn-primary ml-2" t-on-click='_on_save_click'>Save</button>
</t>
<t t-if="state.acitve_item == 'setting'">
<button class="save-btn btn btn-primary ml-2" t-on-click='_on_save_setting_click'>Save</button>
<button class="reset-btn btn btn-primary ml-2" t-on-click='_on_cancel_setting_click'>Cancel</button>
</t>
</div>
</div>
</div>
<!-- Theme mode tab -->
<t t-name="anita_theme_setting.theme_mode_tab" owl="1">
<div class="form-check my-3" t-foreach="theme_modes || []" t-as="theme_mode" t-key="theme_mode.id">
<input t-if="theme_mode.id == state.cur_mode_id" type="radio" name="theme_mode_radio" t-on-change.prevent="() => this._on_change_mode(theme_mode)" class="form-check-input" t-attf-id="theme-mode-{{theme_mode.id}}" checked="true" />
<input t-else="" type="radio" name="theme_mode_radio" t-on-change.prevent="() => this._on_change_mode(theme_mode)" class="form-check-input" t-attf-id="theme-mode-{{theme_mode.id}}" />
<label class="form-check-label" t-attf-for="theme-mode-{{theme_mode.id}}">
<t t-esc="theme_mode.name" />
</label>
</div>
</t>
<!-- theme mode tab pane -->
<t t-name="anita_theme_setting.theme_mode" owl="1">
<div class="theme-mode-container flex-column flex-fill" t-att-class="{'d-none': theme_mode.id != state.cur_mode_id}">
<div class="theme_styles d-flex flex-column">
<t t-set="theme_styles" t-value="theme_mode.theme_styles"/>
<!-- <t t-call="anita_theme_setting.theme_style_tool_bar" /> -->
<ul class="nav nav-pills theme-style-contianer p-3">
<t t-foreach="theme_styles" t-as="theme_style" t-key="theme_style.id">
<t t-call="anita_theme_setting.theme_style_tab" />
</t>
<!-- add new theme style -->
<li class="nav-item theme-style theme_style_tool_bar">
<div class="theme_style_tab">
<div class="style-preview-box d-flex flex-column align-items-center justify-content-center">
<div class="d-flex align-items-center">
<a class="add_new_style tool_bar_item" t-on-click="(event)=>this._on_add_new_style(theme_mode.id, event)" href="#">
<i class="fa fa-plus-circle" />
</a>
</div>
</div>
</div>
</li>
</ul>
<div class="tab-content flex-fill" style="margin:10px 0px; border-color: #3b425c">
<t t-foreach="theme_styles" t-as="theme_style" t-key="theme_style.id">
<t t-call="anita_theme_setting.theme_style_tab_pane" />
</t>
</div>
</div>
</div>
</t>
<t t-name="anita_theme_setting.theme_style_tab_pane" owl="1">
<div class="tab-pane theme-style-tab-pane fade p-15" t-att-class="{'active': theme_style.id == state.cur_style_id, 'show': theme_style.id == state.cur_style_id}">
<div class="style_item_container d-flex justify-content-between flex-wrap flex-column">
<!-- style item group -->
<div class="style_item_group d-flex flex-column" t-foreach="theme_style.groups || []" t-as="group" t-key="group.id">
<t t-call="anita_theme_setting.style_item_group" />
</div>
<!-- <div class="style_item_group_name d-flex">
<button type="button" t-on-click="() => this._add_new_group(theme_style)" class="btn btn-default btn-sm add_new_group d-flex flex-fill align-items-center justify-content-center">
<i class="fa fa-plus-square mr-1"/>
Add Group
</button>
</div> -->
</div>
</div>
</t>
<t t-name="anita_theme_setting.style_item_group" owl="1">
<div class="style_item_group_name d-flex align-items-center">
<span class="flex-fill">
<t t-esc="group.name"/>
</span>
<!-- <Dropdown>
<t t-set-slot="toggler">
<i class="fa fa-caret-square-o-down mr-1" />
</t>
<DropdownItem class="'o_menu_item'" onSelected="() => this._add_sub_group(group)">
Add Sub Group
</DropdownItem>
<DropdownItem class="'o_menu_item'" onSelected="() => this._delete_group(theme_style, group)">
Delete Group
</DropdownItem>
</Dropdown> -->
</div>
<t t-foreach="group.sub_groups or []" t-as="sub_group" t-key="sub_group.id">
<t t-call="anita_theme_setting.sub_group" />
</t>
</t>
<!-- do not render the body initial -->
<t t-name='anita_theme_setting.sub_group' owl='1'>
<div class="sub-group-body d-flex flex-column flex-wrap style_items">
<div class="style_sub_group_name d-flex align-items-center">
<a type="button" class="btn btn-default btn-sm p-1 sub_group_toggle" t-on-click="() => this._toggle_sub_group(sub_group)">
<i class="fa mr-1" t-att-class="{'fa-plus-square': !sub_group.expanded, 'fa-minus-square': sub_group.expanded}" />
</a>
<t t-esc="sub_group.name"/>
<span class="flex-fill"/>
<!-- <Dropdown>
<t t-set-slot="toggler">
<i class="fa fa-caret-square-o-down mr-1" />
</t>
<DropdownItem class="'o_menu_item'" onSelected="() => this._add_style_item(theme_style, group, sub_group)">
Add
</DropdownItem>
<DropdownItem class="'o_menu_item'" onSelected="() => this._delete_sub_group(group, sub_group)">
Delete
</DropdownItem>
</Dropdown> -->
</div>
<div t-if="sub_group.expanded" class="flex-wrap p-4 sub_group_panel">
<t t-foreach="sub_group.style_items or []" t-as="style_item" t-key="style_item.id">
<t t-call="anita_theme_setting.style_item" />
</t>
</div>
</div>
</t>
<t t-name="anita_theme_setting.theme_style_tab" owl="1">
<li class="nav-item theme-style-nav-item">
<a class="nav-link theme_style_tab" data-toggle="tab" t-attf-href="#theme_style_tab_{{theme_style.id}}" role="tab" t-att-class="{'active': state.cur_style_id == theme_style.id}" t-on-click.prevent="() => this._on_click_style_nav_item(theme_style)">
<div class="style-preview-box d-flex flex-row" t-attf-style="{{theme_style.background_image? 'background-image:url('+ theme_style.background_image +')': ''}}">
<div class="sidebar-box" t-attf-style="background-color: {{theme_style.identities.side_bar_background}}; width:20px" />
<div class="main-box d-flex flex-column flex-fill">
<div class="preview-body-box flex-fill" t-attf-style="background-color: {{theme_style.identities.body_background}}"/>
</div>
</div>
</a>
</li>
</t>
<t t-name="anita_theme_setting.style_item" owl='1'>
<div class="style_item">
<div class="title">
<t t-esc="style_item.name" />
<!-- <button t-if="!style_item.is_default" type="button" class="btn btn-default btn-sm p-1 delete_style_item" t-on-click='() => this._on_delete_style_item(style_ite.id)'>
<i class="fa fa-trash-o"/>
</button>
<button type="button" class="btn btn-default btn-sm p-1 edit_style_item" t-on-click="() => this._on_edit_style_item()">
<i class="fa fa-pencil-square-o"/>
</button> -->
</div>
<div class="color-container d-flex flex-row">
<t t-foreach="style_item.vars or []" t-as="style_item_var" t-key="style_item_var.id">
<t t-call="anita_theme_setting.style_item_var" />
</t>
</div>
</div>
</t>
<t t-name="anita_theme_setting.style_item_var" owl='1'>
<AnitaColorPicker t-if="style_item_var.type == 'color'" color="style_item_var.color" onChange="(color) => this._on_change_color(theme_style, style_item, style_item_var, color)" />
<div t-if="style_item_var.type == 'image'" class="style-item-var input-group mr-3">
<input type="text" t-model="style_item_var.image" class="form-control style_item_var" />
<span class="input-group-append">
<span class="input-group-text fa fa-picture-o edit_item_var" t-on-click="()=>this._on_edit_item_var(style_item, style_item_var)">
<i/>
</span>
</span>
</div>
<div t-elif="style_item_var.type == 'svg'" class="style-item-var input-group mr-3">
<input type="text" t-model="style_item_var.svg" class="form-control style_item_var"/>
<span class="input-group-append">
<span class="input-group-text fa fa-gg edit_item_var" t-on-click="()=>this._on_edit_item_var(style_item, style_item_var)">
<i/>
</span>
</span>
</div>
<div t-elif="style_item_var.type == 'image_url'" class="style-item-var input-group mr-3">
<input type="text" t-model="style_item_var.image_url" class="form-control style_item_var"/>
<span class="input-group-append">
<span class="input-group-text fa fa-link edit_item_var" t-on-click="()=>this._on_edit_item_var(style_item, style_item_var)">
<i/>
</span>
</span>
</div>
</t>
<t t-name="anita_theme_setting.theme_style_tool_bar" owl="1">
<div class="operation-toolbar d-flex align-items-center justify-content-center">
<!-- colone_style -->
<span class="clone_style" t-on-click="() => this._clone_style()">
<t t-out="icons.clone_style"/>
</span>
<!-- eidt -->
<span class="edit_style" t-on-click="() => this._edit_style()">
<t t-out="icons.edit_style"/>
</span>
<!-- delete -->
<span t-if="state.show_delete_style" class="delete_style" t-on-click="() => this._delete_style()">
<t t-out="icons.delete_style"/>
</span>
<!-- image -->
<span class="set_bk_image" t-on-click="() => this._set_bk_image()">
<t t-out="icons.edit_image"/>
</span>
</div>
</t>
</templates>
@@ -1,42 +0,0 @@
/** @odoo-module **/
import { patch } from "@web/core/utils/patch";
import { Dialog } from "@web/core/dialog/dialog"
import theme_setting from "anita_theme_setting.theme_setting";
const settings = theme_setting.settings
// patch dialog
// patch(Dialog.prototype, {
// setup() {
// super.setup(...arguments);
// this.dialog_animation_in_style = settings.dialog_animation_in_style;
// if (this.dialog_animation_in_style) {
// this.dialog_animation_in_style = 'animate__animated' + ' ' + this.dialog_animation_in_style;
// this.props.contentClass = this.props.contentClass + ' ' + this.dialog_animation_in_style;
// }
// // wrap close
// let _close = this.data.close;
// this.data.close = async () => {
// if (this.modalRef && this.modalRef.el) {
// let dialog_animation_out_style = settings.dialog_animation_out_style;
// if (dialog_animation_out_style) {
// // find modal-content
// const modalContent = this.modalRef.el.querySelector(".modal-content");
// if (modalContent) {
// // add out animation
// modalContent.classList.add('animate__animated', dialog_animation_out_style);
// // wait for animation end
// await new Promise((resolve) => {
// modalContent.addEventListener("animationend", resolve);
// });
// }
// }
// }
// // call original close
// _close.call(this);
// }
// }
// });
@@ -1,11 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<!-- extend web.Dialog -->
<t t-name="web.ActionDialog" t-inherit="web.ActionDialog" t-inherit-mode="extension" owl="1">
<xpath expr="//div[hasclass('modal-content')]" position="attributes">
<attribute name="t-attf-class">modal-content crax {{dialog_animation_in_style? dialog_animation_in_style: ''}}</attribute>
</xpath>
</t>
</templates>
@@ -1,31 +0,0 @@
/** @odoo-module **/
import { BlockUI } from "@web/core/ui/block_ui";
import { patch } from "@web/core/utils/patch";
import { xml } from "@odoo/owl";
import theme_setting from "anita_theme_setting.theme_setting";
patch(BlockUI.prototype, {
setup() {
super.setup(...arguments);
this.user_setting = theme_setting.settings;
this.loading_style = this.user_setting.loading_svg || '/web/static/img/spin.svg';
}
});
BlockUI.template = xml`
<div t-att-class="state.blockUI ? 'o_blockUI fixed-top d-flex justify-content-center align-items-center flex-column vh-100 bg-black-50' : ''">
<t t-if="state.blockUI">
<div class="o_spinner mb-4">
<img t-att-src="this.loading_style" alt="Loading..."/>
</div>
<div class="o_message text-center px-4">
<t t-esc="state.line1"/> <br/>
<t t-esc="state.line2"/>
</div>
</t>
</div>`;
@@ -1,49 +0,0 @@
.theme_settings {
button.button_style1 {
border-radius: inherit;
}
}
// rounded style
body.button_style2:not(.setting_preview), .theme_settings .button_style2 {
button {
border-radius: 50px;
}
}
// slant style
body.button_style3:not(.setting_preview), .theme_settings .button_style3 {
/* CSS */
button {
border-radius: 0;
border-bottom-left-radius: 50px;
border-top-right-radius: 50px;
}
}
// slant style
body.button_style4:not(.setting_preview), .theme_settings .button_style4 {
button {
border-radius: 0;
border-bottom-right-radius: 50px;
border-top-left-radius: 50px;
}
}
body.button_style5:not(.setting_preview), .theme_settings .button_style5 {
button {
border-radius: 0;
border-bottom-right-radius: 10px;
border-top-left-radius: 10px;
}
}
body.button_style6:not(.setting_preview), .theme_settings .button_style6 {
button {
border-radius: 0;
border-bottom-left-radius: 10px;
border-top-right-radius: 10px;
}
}
@@ -1,57 +0,0 @@
// check box style2
body.checkbox_style1:not(.setting_preview),
.theme_settings .checkbox_style2 {
.form-check-input[type=checkbox] {
&:checked {
background-color: transparent;
background-image: url("data:image/svg+xml,%3Csvg class='icon' viewBox='0 0 1024 1024' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' %3E%3Cpath d='M822.464 265.344a28.256 28.256 0 0 0-43.072 1.312l-352.96 417.664-181.92-212.992a28.288 28.288 0 0 0-43.104-1.088 37.12 37.12 0 0 0-0.96 48.256l204.096 238.944c5.76 6.752 13.696 10.56 22.016 10.56h0.096a29.088 29.088 0 0 0 22.048-10.656L823.68 313.6c11.52-13.728 11.008-35.328-1.216-48.256' p-id='3837'%3E%3C/path%3E%3C/svg%3E");
}
}
}
// check box style3
body.checkbox_style3:not(.setting_preview),
.theme_settings .checkbox_style3 {
.form-check-input[type=checkbox] {
border-radius: 50%;
}
}
// check box style4
body.checkbox_style4:not(.setting_preview),
.theme_settings .checkbox_style4 {
.form-check-input[type=checkbox] {
border-radius: 50%;
&:checked {
background-color: transparent;
background-image: url("data:image/svg+xml,%3Csvg class='icon' viewBox='0 0 1024 1024' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' %3E%3Cpath d='M822.464 265.344a28.256 28.256 0 0 0-43.072 1.312l-352.96 417.664-181.92-212.992a28.288 28.288 0 0 0-43.104-1.088 37.12 37.12 0 0 0-0.96 48.256l204.096 238.944c5.76 6.752 13.696 10.56 22.016 10.56h0.096a29.088 29.088 0 0 0 22.048-10.656L823.68 313.6c11.52-13.728 11.008-35.328-1.216-48.256' p-id='3837'%3E%3C/path%3E%3C/svg%3E");
}
}
}
// check box style
body.checkbox_style5:not(.setting_preview),
.theme_settings .checkbox_style5 {
.form-check-input[type=checkbox] {
border-radius: 0;
}
}
// check box style6
// check box style4
body.checkbox_style6:not(.setting_preview),
.theme_settings .checkbox_style6 {
.form-check-input[type=checkbox] {
border-radius: 0;
&:checked {
background-color: transparent;
background-image: url("data:image/svg+xml,%3Csvg class='icon' viewBox='0 0 1024 1024' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' %3E%3Cpath d='M822.464 265.344a28.256 28.256 0 0 0-43.072 1.312l-352.96 417.664-181.92-212.992a28.288 28.288 0 0 0-43.104-1.088 37.12 37.12 0 0 0-0.96 48.256l204.096 238.944c5.76 6.752 13.696 10.56 22.016 10.56h0.096a29.088 29.088 0 0 0 22.048-10.656L823.68 313.6c11.52-13.728 11.008-35.328-1.216-48.256' p-id='3837'%3E%3C/path%3E%3C/svg%3E");
}
}
}
@@ -1,84 +0,0 @@
// input style1 normal
body.input_style1:not(.setting_preview), .theme_settings .input_style1 {
input.o_input, .o_input {
border-width: 1px;
border-radius: 5px;
&:focus {
box-shadow: none;
}
}
}
// input style1 square
body.input_style2:not(.setting_preview), .theme_settings .input_style2 {
input.o_input, .o_input {
border-width: 1px;
border-radius: 0;
&:focus {
box-shadow: none;
}
}
}
// input style3 line
body.input_style3:not(.setting_preview), .theme_settings .input_style3 {
input.o_input, .o_input {
border: 0;
border-bottom: 1px solid #e6e6e6;
background: transparent;
&:focus {
box-shadow: none;
}
}
}
// input style4 slant
body.input_style4:not(.setting_preview), .theme_settings .input_style4 {
input.o_input, .o_input {
border-bottom-left-radius: 50px;
border-top-right-radius: 50px;
padding-left: 15px;
padding-right: 15p;
}
}
// input style5 slant reverse
body.input_style5:not(.setting_preview), .theme_settings .input_style5 {
input.o_input, .o_input {
border-bottom-right-radius: 50px;
border-top-left-radius: 50px;
padding-left: 15px;
padding-right: 15p;
}
}
// input style6 round
body.input_style6:not(.setting_preview), .theme_settings .input_style6 {
input.o_input, .o_input {
border-radius: 50px;
padding-left: 15px;
padding-right: 15px;
}
}
// input style7 line
body.input_style7:not(.setting_preview), .theme_settings .input_style7 {
input.o_input, .o_input {
border-bottom-right-radius: 10px;
border-top-left-radius: 10px;
padding-left: 15px;
padding-right: 15p;
}
}
// input style4 slant
body.input_style8:not(.setting_preview), .theme_settings .input_style8 {
input.o_input, .o_input {
border-bottom-left-radius: 10px;
border-top-right-radius: 10px;
padding-left: 15px;
padding-right: 15p;
}
}
@@ -1,62 +0,0 @@
/** @odoo-module **/
import { Component, useState, useRef } from "@odoo/owl";
export class MaskChecker extends Component {
setup() {
super.setup();
this.root = useRef('root');
}
/**
* on mask click
* @param {*} ev
*/
_on_mask_click() {
this.props.onChecked();
}
_on_mask_hover() {
if (this.props.onEnter) {
this.props.onEnter(this.root.el);
}
}
_on_mask_leave(event) {
if (this.props.onLeave) {
this.props.onLeave(this.root.el);
}
}
};
MaskChecker.props = {
onChecked: {
type: Function,
optional: 1,
},
checked: {
type: Boolean,
optional: 1,
},
slots: {
type: Object,
optional: true,
},
extraClass: {
type: String,
optional: true,
},
onEnter: {
type: Function,
optional: 1,
},
onLeave: {
type: Function,
optional: 1,
},
};
MaskChecker.template = "anita_theme_setting.mask_checker";
@@ -1,34 +0,0 @@
.mask_checker {
position: relative;
border: 1px dashed #dee2e6;
background-color: #fff;
padding: 10px;
display: flex;
justify-content: center;
margin-bottom: 5px;
&:hover {
border-color: #006fe6;
background: #006fe60d;
}
.checker_mask {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: transparent;
z-index: 1;
svg {
width: 24px;
height: 24px;
position: absolute;
right: 5px;
top: 5px;
}
}
}
@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<!-- style setting -->
<t t-name="anita_theme_setting.mask_checker" owl="1">
<div t-attf-class="mask_checker {{props.extraClass}}" t-ref="root">
<t t-slot="default"/>
<div class="checker_mask d-flex align-items-center justify-content-center" t-on-click.prevent="_on_mask_click" t-on-mouseenter="(event) => this._on_mask_hover(event)" t-on-mouseleave="()=>this._on_mask_leave()">
<svg t-if="props.checked" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M512 85.504c-235.552 0-426.496 190.944-426.496 426.496s190.976 426.496 426.496 426.496c235.552 0 426.496-190.944 426.496-426.496s-190.944-426.496-426.496-426.496zM512 904.384c-216.352 0-392.384-176.032-392.384-392.384s176-392.384 392.384-392.384c216.352 0 392.384 176.032 392.384 392.384s-176 392.384-392.384 392.384z" p-id="2681" fill="#13227a"></path><path d="M725.344 359.712l-273.888 265.568-95.936-95.936c-9.984-9.984-26.176-9.984-36.192 0s-9.984 26.176 0 36.192l113.76 113.76c4.992 4.992 11.552 7.488 18.08 7.488 6.4 0 12.832-2.4 17.792-7.2l291.968-283.136c10.144-9.856 10.4-26.048 0.576-36.192-9.888-10.176-26.048-10.4-36.192-0.576z" p-id="2682" fill="#13227a"></path></svg>
</div>
</div>
</t>
</templates>
@@ -1,33 +0,0 @@
// radio box style2
body.radio_style2, .theme_settings .radio_style2 {
.form-check-input[type=radio] {
border: 1px solid #e6e6e6;
border-radius: 30%;
background: #fff;
&:checked {
background-image: url("data:image/svg+xml,%3Csvg class='icon' viewBox='0 0 1024 1024' version='1.1' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath d='M692.071703 329.86173c-48.372707-48.387034-112.705921-75.023694-181.146687-75.023694-68.440765 0-132.747373 26.63666-181.148733 75.023694-48.400337 48.374754-75.022671 112.707968-75.022671 181.123151s26.623357 132.74635 75.022671 181.146687c48.374754 48.402383 112.707968 75.023694 181.148733 75.023694 68.441788 0 132.745327-26.647917 181.146687-75.023694 48.427966-48.373731 75.024717-112.706945 75.024717-181.146687C767.096421 442.544115 740.447481 378.237507 692.071703 329.86173z' fill='%231296db'%3E%3C/path%3E%3C/svg%3E");
}
}
}
// radio box style3
body.radio_style3, .theme_settings .radio_style3 {
.form-check-input[type=radio] {
border-width: 1px;
border-radius: 30%;
}
}
// radio box style4
body.radio_style4, .theme_settings .radio_style4 {
.form-check-input[type=radio] {
border-width: 1px;
border-radius: 50%;
background-color: #fff;
&:checked {
background-image: url("data:image/svg+xml,%3Csvg class='icon' viewBox='0 0 1024 1024' version='1.1' xmlns='http://www.w3.org/2000/svg' %3E%3Cpath d='M692.071703 329.86173c-48.372707-48.387034-112.705921-75.023694-181.146687-75.023694-68.440765 0-132.747373 26.63666-181.148733 75.023694-48.400337 48.374754-75.022671 112.707968-75.022671 181.123151s26.623357 132.74635 75.022671 181.146687c48.374754 48.402383 112.707968 75.023694 181.148733 75.023694 68.441788 0 132.745327-26.647917 181.146687-75.023694 48.427966-48.373731 75.024717-112.706945 75.024717-181.146687C767.096421 442.544115 740.447481 378.237507 692.071703 329.86173z' fill='%231296db'%3E%3C/path%3E%3C/svg%3E");
}
}
}
@@ -1,43 +0,0 @@
// separator style2
body.separator_style2, .theme_settings .separator_style2 {
hr {
display: block;
position: relative;
width: 100vw;
height: 2px;
border: none;
border-bottom: 2px dotted rgba($purple, .25);
float: left;
width: 100vw;
~ * {
clear: both;
}
}
}
// separator style3
body.separator_style3, .theme_settings .separator_style3 {
hr {
border-top: 1px solid #8c8b8b;
border-bottom: 1px solid #fff;
width: 100%;
}
}
// separator style
body.separator_style4, .theme_settings .separator_style4 {
hr {
display: block;
position: relative;
width: 100vw;
height: 2px;
border: none;
border-bottom: 2px dashed rgba($purple, .25);
float: left;
~ * {
clear: both;
}
}
}

Some files were not shown because too many files have changed in this diff Show More