主题
@@ -0,0 +1,50 @@
|
||||
# sphinx build directories
|
||||
_build/
|
||||
|
||||
# dotfiles
|
||||
.*
|
||||
!.gitignore
|
||||
!.github
|
||||
!.mailmap
|
||||
# compiled python files
|
||||
*.py[co]
|
||||
__pycache__/
|
||||
# setup.py egg_info
|
||||
*.egg-info
|
||||
# emacs backup files
|
||||
*~
|
||||
# hg stuff
|
||||
*.orig
|
||||
status
|
||||
# odoo filestore
|
||||
odoo/filestore
|
||||
# maintenance migration scripts
|
||||
odoo/addons/base/maintenance
|
||||
|
||||
# generated for windows installer?
|
||||
install/win32/*.bat
|
||||
install/win32/meta.py
|
||||
|
||||
# needed only when building for win32
|
||||
setup/win32/static/less/
|
||||
setup/win32/static/wkhtmltopdf/
|
||||
setup/win32/static/postgresql*.exe
|
||||
|
||||
# js tooling
|
||||
node_modules
|
||||
package-lock.json
|
||||
package.json
|
||||
.husky
|
||||
|
||||
# various virtualenv
|
||||
/bin/
|
||||
/build/
|
||||
/dist/
|
||||
/include/
|
||||
/lib/
|
||||
/man/
|
||||
/share/
|
||||
/src/
|
||||
|
||||
# avoid adding it after generation from tsconfig command
|
||||
tsconfig.json
|
||||
@@ -0,0 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import models
|
||||
@@ -0,0 +1,64 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
'name': "anita_theme_base",
|
||||
|
||||
'summary': """
|
||||
Base for themes, it support free login for odoo
|
||||
""",
|
||||
|
||||
'description': """
|
||||
Odoo Login,
|
||||
Odoo login page,
|
||||
Odoo login theme
|
||||
Login,
|
||||
Anita Theme Base,
|
||||
Anita Theme,
|
||||
Awesome Theme,
|
||||
Multi tab theme,
|
||||
Pop form theme
|
||||
""",
|
||||
|
||||
'author': "openErpNext",
|
||||
|
||||
'website': "https://base.openerpnext.com",
|
||||
'live_test_url': 'https://base.openerpnext.com',
|
||||
|
||||
'license': 'OPL-1',
|
||||
'images': ['static/description/screen_shot.png', 'static/description/banner.png'],
|
||||
'support': 'codercrax@gmail.com',
|
||||
'maintainer': 'openErpNext',
|
||||
'category': 'Theme/Backend',
|
||||
'version': '17.0.0.2',
|
||||
|
||||
'installable': True,
|
||||
'application': True,
|
||||
'auto_install': False,
|
||||
|
||||
'depends': ['base', 'web'],
|
||||
|
||||
'data': [],
|
||||
|
||||
'assets': {
|
||||
'web.assets_backend': [
|
||||
'anita_theme_base/static/css/anita_variables.scss',
|
||||
|
||||
'anita_theme_base/static/css/mixins/_flex.scss',
|
||||
'anita_theme_base/static/css/mixins/_box_shadow.scss',
|
||||
'anita_theme_base/static/css/mixins/_clearfix.scss',
|
||||
'anita_theme_base/static/css/mixins/_float.scss',
|
||||
'anita_theme_base/static/css/mixins/_hover.scss',
|
||||
'anita_theme_base/static/css/mixins/_gradients.scss',
|
||||
# 'anita_theme_base/static/css/mixins/_buttons.scss',
|
||||
|
||||
'anita_theme_base/static/css/anita_functions.scss',
|
||||
|
||||
'anita_theme_base/static/css/anita_form_controls.scss',
|
||||
# 'anita_theme_base/static/css/anita_buttons.scss',
|
||||
|
||||
# 'anita_theme_base/static/css/anita_form_view.scss',
|
||||
# 'anita_theme_base/static/css/anita_list_view.scss',
|
||||
'anita_theme_base/static/css/anita_scroll.scss',
|
||||
'anita_theme_base/static/css/anita_misc.scss',
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import anita_view_extend
|
||||
@@ -0,0 +1,32 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import inspect
|
||||
|
||||
from odoo import models, _
|
||||
from odoo.exceptions import AccessError
|
||||
from odoo.models import check_method_name
|
||||
|
||||
|
||||
class AnitaViewExtend(models.Model):
|
||||
'''
|
||||
anita view extend current_theme_mode
|
||||
'''
|
||||
_inherit = 'ir.ui.view'
|
||||
|
||||
def _render_template(self, template, values=None):
|
||||
"""
|
||||
rewrite to change the title
|
||||
:param template:
|
||||
:param values:
|
||||
:param engine:
|
||||
:return:
|
||||
"""
|
||||
|
||||
if template in ['web.login', 'web.webclient_bootstrap']:
|
||||
if not values:
|
||||
values = {}
|
||||
values["title"] = self.env['ir.config_parameter'].sudo().get_param(
|
||||
"anita_theme_base.window_default_title", "openErpNext")
|
||||
|
||||
return super(AnitaViewExtend, self)._render_template(
|
||||
template, values=values)
|
||||
@@ -0,0 +1 @@
|
||||
this is base for anita themes
|
||||
@@ -0,0 +1,89 @@
|
||||
|
||||
// Make the div behave like a button
|
||||
.btn-group {
|
||||
// Prevent double borders when buttons are next to each other
|
||||
.btn + .btn,
|
||||
.btn + .btn-group,
|
||||
.btn-group + .btn,
|
||||
.btn-group + .btn-group {
|
||||
margin-left: -$btn-border-width;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-group {
|
||||
// Reset rounded corners
|
||||
> .btn:not(:last-child):not(.dropdown-toggle),
|
||||
> .btn-group:not(:last-child) > .btn {
|
||||
@include border-right-radius(0);
|
||||
}
|
||||
|
||||
> .btn:not(:first-child),
|
||||
> .btn-group:not(:first-child) > .btn {
|
||||
@include border-left-radius(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Sizing
|
||||
//
|
||||
// Remix the default button sizing classes into new ones for easier manipulation.
|
||||
|
||||
.btn-group-sm > .btn { @extend .btn-sm; }
|
||||
.btn-group-lg > .btn { @extend .btn-lg; }
|
||||
|
||||
|
||||
//
|
||||
// Split button dropdowns
|
||||
//
|
||||
|
||||
.dropdown-toggle-split {
|
||||
padding-right: $btn-padding-x * .75;
|
||||
padding-left: $btn-padding-x * .75;
|
||||
}
|
||||
|
||||
.btn-sm + .dropdown-toggle-split {
|
||||
padding-right: $btn-padding-x-sm * .75;
|
||||
padding-left: $btn-padding-x-sm * .75;
|
||||
}
|
||||
|
||||
.btn-lg + .dropdown-toggle-split {
|
||||
padding-right: $btn-padding-x-lg * .75;
|
||||
padding-left: $btn-padding-x-lg * .75;
|
||||
}
|
||||
|
||||
|
||||
// The clickable button for toggling the menu
|
||||
// Set the same inset shadow as the :active state
|
||||
.btn-group.show .dropdown-toggle {
|
||||
@include box-shadow($btn-active-box-shadow);
|
||||
|
||||
// Show no shadow for `.btn-link` since it has no other button styles.
|
||||
&.btn-link {
|
||||
@include box-shadow(none);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Vertical button groups
|
||||
//
|
||||
|
||||
.btn-group-vertical {
|
||||
> .btn + .btn,
|
||||
> .btn + .btn-group,
|
||||
> .btn-group + .btn,
|
||||
> .btn-group + .btn-group {
|
||||
margin-top: -$btn-border-width;
|
||||
}
|
||||
|
||||
// Reset rounded corners
|
||||
> .btn:not(:last-child):not(.dropdown-toggle),
|
||||
> .btn-group:not(:last-child) > .btn {
|
||||
@include border-bottom-radius(0);
|
||||
}
|
||||
|
||||
> .btn:not(:first-child),
|
||||
> .btn-group:not(:first-child) > .btn {
|
||||
@include border-top-radius(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
.btn {
|
||||
font-weight: $btn-font-weight;
|
||||
// font-family: $btn-font-family;
|
||||
border: $input-btn-border-width solid transparent;
|
||||
|
||||
@include button-size(
|
||||
$btn-padding-y,
|
||||
$btn-padding-x,
|
||||
$btn-font-size,
|
||||
$input-btn-line-height,
|
||||
$btn-border-radius
|
||||
);
|
||||
|
||||
@include transition($btn-transition);
|
||||
|
||||
&:hover,
|
||||
&.hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&.focus {
|
||||
@include box-shadow(none);
|
||||
}
|
||||
|
||||
&:not([disabled]):not(.disabled):active,
|
||||
&:not([disabled]):not(.disabled).active {
|
||||
background-image: none;
|
||||
@include box-shadow(none);
|
||||
}
|
||||
|
||||
&.btn-squared {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
&.btn-pill {
|
||||
// border-radius: $btn-pill-border-radius;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Alternate buttons
|
||||
//
|
||||
|
||||
@each $color, $value in $theme-colors {
|
||||
.btn-#{$color} {
|
||||
@include button-variant($value);
|
||||
}
|
||||
}
|
||||
|
||||
// White buttons
|
||||
.btn-white {
|
||||
@include button-variant($white);
|
||||
}
|
||||
|
||||
// Black buttons
|
||||
.btn-black {
|
||||
@include button-variant($black);
|
||||
}
|
||||
|
||||
// Outlined default buttons
|
||||
@each $color, $value in $theme-colors {
|
||||
.btn-outline-#{$color} {
|
||||
@include button-outline-variant($value);
|
||||
}
|
||||
}
|
||||
|
||||
// White outline buttons
|
||||
.btn-outline-white {
|
||||
@include button-outline-variant($white);
|
||||
color: $white;
|
||||
|
||||
&:not(:disabled):not(.disabled):active,
|
||||
&:not(:disabled):not(.disabled).active {
|
||||
color: $black;
|
||||
}
|
||||
}
|
||||
|
||||
// Black outline buttons
|
||||
.btn-outline-black {
|
||||
@include button-outline-variant($black);
|
||||
color: $black;
|
||||
|
||||
&:not(:disabled):not(.disabled):active,
|
||||
&:not(:disabled):not(.disabled).active {
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
|
||||
// Make a button look and behave like a link
|
||||
.btn-link {
|
||||
font-weight: $font-weight-normal;
|
||||
color: $link-color;
|
||||
|
||||
@include hover {
|
||||
color: $link-hover-color;
|
||||
text-decoration: $link-hover-decoration;
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&.focus {
|
||||
text-decoration: $link-hover-decoration;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
color: $btn-link-disabled-color;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Button Sizes
|
||||
//
|
||||
|
||||
.btn-lg {
|
||||
@include button-size(
|
||||
$btn-padding-y-lg,
|
||||
$btn-padding-x-lg,
|
||||
$btn-font-size-lg,
|
||||
$btn-line-height-lg,
|
||||
$btn-border-radius-lg
|
||||
);
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
@include button-size(
|
||||
$btn-padding-y-sm,
|
||||
$btn-padding-x-sm,
|
||||
$btn-font-size-sm,
|
||||
$btn-line-height-sm,
|
||||
$btn-border-radius-sm
|
||||
);
|
||||
}
|
||||
|
||||
// Vertically space out multiple block buttons
|
||||
.btn-block + .btn-block {
|
||||
margin-top: $btn-block-spacing-y;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,382 @@
|
||||
// Checkboxes and radios
|
||||
|
||||
.custom-control {
|
||||
min-height: (1 * $line-height-base);
|
||||
padding-left: $custom-control-gutter;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.custom-control-label:before {
|
||||
pointer-events: all;
|
||||
}
|
||||
}
|
||||
|
||||
.custom-control-inline {
|
||||
margin-right: $custom-control-spacer-x;
|
||||
}
|
||||
|
||||
.custom-control-input {
|
||||
&:checked~.custom-control-label::before {
|
||||
color: $custom-control-indicator-checked-color;
|
||||
border-color: transparent;
|
||||
// @include gradient-bg($custom-control-indicator-checked-bg);
|
||||
box-shadow: $custom-control-indicator-checked-box-shadow;
|
||||
}
|
||||
|
||||
&:focus~.custom-control-label::before {
|
||||
// the mixin is not used here to make sure there is feedback
|
||||
// box-shadow: $custom-control-indicator-focus-box-shadow;
|
||||
}
|
||||
|
||||
&:active~.custom-control-label::before {
|
||||
color: $custom-control-indicator-active-color;
|
||||
background-color: $custom-control-indicator-active-bg;
|
||||
box-shadow: $custom-control-indicator-active-box-shadow;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
~.custom-control-label {
|
||||
color: $custom-control-description-disabled-color; // $custom-control-label-disabled-color
|
||||
|
||||
&:hover {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
&::before {
|
||||
background-color: $custom-control-indicator-disabled-bg;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Custom control indicators
|
||||
// Build the custom controls out of psuedo-elements.
|
||||
|
||||
.custom-control-label {
|
||||
position: static;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
// Background-color and (when enabled) gradient
|
||||
&::before {
|
||||
top: ($line-height-base - $custom-control-indicator-size) / 2.0;
|
||||
left: 0 !important;
|
||||
width: $custom-control-indicator-size;
|
||||
height: $custom-control-indicator-size;
|
||||
background-color: $custom-control-indicator-bg;
|
||||
border: $custom-control-indicator-border;
|
||||
transition: all 250ms cubic-bezier(.27, .01, .38, 1.06);
|
||||
box-shadow: $custom-control-indicator-box-shadow;
|
||||
}
|
||||
|
||||
// Foreground (icon)
|
||||
&::after {
|
||||
top: ($line-height-base - $custom-control-indicator-size) / 2.0;
|
||||
width: $custom-control-indicator-size;
|
||||
height: $custom-control-indicator-size;
|
||||
background-size: $custom-control-indicator-bg-size;
|
||||
}
|
||||
}
|
||||
|
||||
// Custom Switches (core)
|
||||
// Adjustments for the default Bootstrap custom switches.
|
||||
|
||||
.custom-switch {
|
||||
padding-left: $custom-switch-padding-left;
|
||||
|
||||
.custom-control-label {
|
||||
position: relative;
|
||||
|
||||
::before {
|
||||
left: -2.25rem !important;
|
||||
width: 1.75rem !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Checkboxes
|
||||
.custom-checkbox {
|
||||
.custom-control-label::before {
|
||||
@include border-radius($custom-checkbox-indicator-border-radius);
|
||||
}
|
||||
|
||||
.custom-control-label::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: ($custom-control-indicator-size - 0.9166rem) / 2.0;
|
||||
left: ($custom-control-indicator-size - 0.4166rem) / 2.0;
|
||||
width: 5px;
|
||||
height: 11px;
|
||||
opacity: 0;
|
||||
transform: rotate(45deg) scale(0);
|
||||
border-right: 2px solid $white;
|
||||
border-bottom: 2px solid $white;
|
||||
transition: $custom-checkbox-indicator-transition;
|
||||
transition-delay: 100ms;
|
||||
}
|
||||
|
||||
.custom-control-input:checked~.custom-control-label {
|
||||
// &::before {
|
||||
// background-image: none;
|
||||
// }
|
||||
|
||||
&::after {
|
||||
opacity: 1;
|
||||
transform: rotate(45deg) scale(1);
|
||||
background-image: $custom-checkbox-indicator-icon-checked;
|
||||
}
|
||||
}
|
||||
|
||||
.custom-control-input:indeterminate~.custom-control-label {
|
||||
&::before {
|
||||
border: none;
|
||||
// @include gradient-bg($custom-checkbox-indicator-indeterminate-bg);
|
||||
box-shadow: $custom-checkbox-indicator-indeterminate-box-shadow;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
transform: scale(1);
|
||||
background-image: $custom-checkbox-indicator-icon-indeterminate;
|
||||
background-color: $white;
|
||||
border: none;
|
||||
width: 10px;
|
||||
height: 2px;
|
||||
top: 11px;
|
||||
left: 4px;
|
||||
opacity: 1;
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
|
||||
.custom-control-input:disabled:checked~.custom-control-label {
|
||||
&::before {
|
||||
background: $custom-control-indicator-disabled-bg;
|
||||
border-color: $border-color;
|
||||
}
|
||||
|
||||
&::after {
|
||||
border-color: $border-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Radios
|
||||
.custom-radio {
|
||||
.custom-control-label::before {
|
||||
border-radius: $custom-radio-indicator-border-radius;
|
||||
}
|
||||
|
||||
.custom-control-label::after {
|
||||
content: '';
|
||||
border-radius: 50%;
|
||||
transform: scale(0);
|
||||
background-image: none !important;
|
||||
position: absolute;
|
||||
background: #fff;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
top: (($line-height-base - 8 / 12.0) / 2.0);
|
||||
left: (($custom-control-indicator-size - 8 / 12.0) / 2.0);
|
||||
transition: all 250ms cubic-bezier(.27, .01, .38, 1.06);
|
||||
transition-delay: .1s;
|
||||
opacity: 0;
|
||||
transform: scale(0);
|
||||
}
|
||||
|
||||
.custom-control-input:checked~.custom-control-label {
|
||||
&::before {
|
||||
//@include gradient-bg($custom-control-indicator-checked-bg);
|
||||
}
|
||||
|
||||
&::after {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.custom-control-input:disabled {
|
||||
&:checked~.custom-control-label::before {
|
||||
background-color: lighten($blueish-grey, 30%);
|
||||
}
|
||||
}
|
||||
|
||||
.custom-control-input:disabled:checked~.custom-control-label {
|
||||
&::before {
|
||||
background: $custom-control-indicator-disabled-bg;
|
||||
border-color: $border-color;
|
||||
}
|
||||
|
||||
&::after {
|
||||
background: $border-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Custom toggle
|
||||
.custom-toggle {
|
||||
position: relative;
|
||||
padding-left: ($custom-toggle-width + 0.625rem);
|
||||
|
||||
.custom-control-label {
|
||||
|
||||
// Background
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: block;
|
||||
width: $custom-toggle-width;
|
||||
height: $custom-toggle-height;
|
||||
background: $custom-toggle-background-color;
|
||||
border-radius: $custom-toggle-border-radius;
|
||||
border: $custom-toggle-border;
|
||||
}
|
||||
|
||||
// Knob
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0.25rem;
|
||||
left: 0.25rem;
|
||||
width: $custom-toggle-knob-width;
|
||||
height: $custom-toggle-knob-height;
|
||||
background: $input-border-color;
|
||||
border-radius: $custom-toggle-knob-border-radius;
|
||||
transition: 350ms;
|
||||
}
|
||||
}
|
||||
|
||||
// Checked state
|
||||
.custom-control-input:checked~.custom-control-label {
|
||||
|
||||
// Background
|
||||
&::before {
|
||||
background: $custom-toggle-checked-background;
|
||||
border-color: $custom-toggle-checked-border-color;
|
||||
}
|
||||
|
||||
// Knob
|
||||
&::after {
|
||||
left: $custom-toggle-width - $custom-toggle-border-width - 0.1875rem;
|
||||
transform: translateX(-100%);
|
||||
background: $custom-toggle-checked-knob-background;
|
||||
}
|
||||
}
|
||||
|
||||
// Disabled and checked state
|
||||
.custom-control-input:checked:disabled~.custom-control-label {
|
||||
&::before {
|
||||
background: $custom-control-indicator-disabled-bg;
|
||||
border-color: $border-color;
|
||||
}
|
||||
|
||||
&::after {
|
||||
background: $border-color;
|
||||
}
|
||||
}
|
||||
|
||||
// Active state
|
||||
.custom-control-input:active:not(:disabled)~.custom-control-label {
|
||||
|
||||
// Knob
|
||||
&::after {
|
||||
width: $custom-toggle-knob-active-width;
|
||||
}
|
||||
}
|
||||
|
||||
// Active, but not checked.
|
||||
.custom-control-input:active:not(:checked)~.custom-control-label {
|
||||
&::before {
|
||||
background-color: $custom-toggle-background-color;
|
||||
}
|
||||
}
|
||||
|
||||
// Disabled and active.
|
||||
.custom-control-input:disabled:active~.custom-control-label {
|
||||
&::before {
|
||||
background-color: $custom-control-indicator-disabled-bg;
|
||||
}
|
||||
}
|
||||
|
||||
// Focus state
|
||||
.custom-control-input:focus {
|
||||
&~.custom-control-label {
|
||||
&::before {
|
||||
// box-shadow: $custom-toggle-focus-box-shadow;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(:checked)~.custom-control-label {
|
||||
&::before {
|
||||
// box-shadow: $input-focus-box-shadow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.custom-toggle-sm {
|
||||
padding-left: 2.625rem;
|
||||
|
||||
.custom-control-label {
|
||||
|
||||
// Background
|
||||
&::before {
|
||||
top: .1875rem;
|
||||
position: absolute;
|
||||
display: block;
|
||||
width: $custom-toggle-width-sm;
|
||||
height: $custom-toggle-height-sm;
|
||||
background: $custom-toggle-background-color;
|
||||
border-radius: $custom-toggle-border-radius;
|
||||
border: $custom-toggle-border;
|
||||
}
|
||||
|
||||
// Knob
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: .375rem;
|
||||
left: 0.1875rem;
|
||||
width: $custom-toggle-knob-width-sm;
|
||||
height: $custom-toggle-knob-height-sm;
|
||||
}
|
||||
}
|
||||
|
||||
// Checked state
|
||||
.custom-control-input:checked~.custom-control-label {
|
||||
|
||||
// Knob
|
||||
&::after {
|
||||
left: $custom-toggle-width-sm - $custom-toggle-border-width - 0.1875rem;
|
||||
}
|
||||
}
|
||||
|
||||
// Active state
|
||||
.custom-control-input:active:not(:disabled)~.custom-control-label {
|
||||
|
||||
// Knob
|
||||
&::after {
|
||||
width: $custom-toggle-knob-active-width-sm;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_boolean_toggle {
|
||||
.custom-control.custom-checkbox>label.custom-control-label {
|
||||
|
||||
&::before {
|
||||
width: 2.6375rem;
|
||||
max-height: 20px;
|
||||
}
|
||||
|
||||
i.fa-check-circle {
|
||||
right: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,838 @@
|
||||
// form
|
||||
$o-form-lightsecondary: lighten($o-brand-secondary, 25%) !default;
|
||||
$o-chatter-min-width: 530px !default;
|
||||
$o-form-sheet-min-width: 990px !default;
|
||||
$o-font-size-base-touch: 12px;
|
||||
|
||||
.o_input,
|
||||
.single-line,
|
||||
.js_import_options input {
|
||||
-webkit-appearance: none;
|
||||
background-color: #fff;
|
||||
background-image: none;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #dcdfe6;
|
||||
box-sizing: border-box;
|
||||
color: #606266;
|
||||
font-size: inherit;
|
||||
height: 34px;
|
||||
line-height: 34px;
|
||||
outline: none;
|
||||
padding: 0 10px;
|
||||
width: 100%;
|
||||
|
||||
&:focus {
|
||||
border-color: #66afe9;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: #c0c4cc;
|
||||
}
|
||||
|
||||
input {
|
||||
&.o_input {
|
||||
height: 30px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_form_view {
|
||||
background-color: transparent;
|
||||
|
||||
.o_form_sheet_bg {
|
||||
border-bottom: 1px solid #ddd;
|
||||
background: transparent
|
||||
}
|
||||
|
||||
.o_group {
|
||||
.o_td_label {
|
||||
border-right: none
|
||||
}
|
||||
}
|
||||
|
||||
.o_form_sheet_bg {
|
||||
border: none;
|
||||
background: transparent;
|
||||
|
||||
>.o_form_sheet {
|
||||
min-width: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.o_form_statusbar {
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
padding-left: 0px;
|
||||
}
|
||||
|
||||
>.o_statusbar_status {
|
||||
>.o_arrow_button.btn-primary.disabled:after {
|
||||
border-left-color: #007bff;
|
||||
}
|
||||
}
|
||||
|
||||
.o_statusbar_buttons_dropdown.dropdown-toggle {
|
||||
width: 80px;
|
||||
padding-left: 0px;
|
||||
border: none;
|
||||
border-right: 1px solid;
|
||||
border-radius: 0px;
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
padding: 5px;
|
||||
|
||||
button {
|
||||
margin-bottom: 5px;
|
||||
width: 150px;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.oe_button_box {
|
||||
|
||||
+.oe_title,
|
||||
+.oe_avatar+.oe_title {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.o_cp_pager {
|
||||
margin-right: 5px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.o_pager_counter {
|
||||
margin: 5px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
button {
|
||||
max-height: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.table-responsive {
|
||||
border-top: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
.o_list_table {
|
||||
border-top: 1px solid #dee2e6;
|
||||
margin-top: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.o_form_view {
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
// Sheet
|
||||
.o_form_sheet_bg {
|
||||
flex: 1 0 auto;
|
||||
border-bottom: 1px solid gray('300');
|
||||
|
||||
>.o_form_sheet {
|
||||
@include make-container();
|
||||
@include make-container-max-widths();
|
||||
|
||||
border: 1px solid gray('300');
|
||||
box-shadow: 0 5px 20px -15px black;
|
||||
margin: $o-sheet-vpadding * 0.2 auto;
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
margin: $o-sheet-vpadding*0.5 auto;
|
||||
}
|
||||
|
||||
padding: $o-sheet-vpadding;
|
||||
|
||||
@include o-form-sheet-inner-right-padding;
|
||||
@include o-form-sheet-inner-left-padding;
|
||||
|
||||
// Selection
|
||||
>.o_selection {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Title & avatar
|
||||
.oe_title {
|
||||
color: $headings-color;
|
||||
|
||||
@include media-breakpoint-up(vsm, $o-extra-grid-breakpoints) {
|
||||
padding-right: $o-innergroup-rpadding;
|
||||
}
|
||||
}
|
||||
|
||||
.oe_avatar+.oe_title {
|
||||
padding-right: $o-avatar-size + 10;
|
||||
}
|
||||
|
||||
// Groups
|
||||
.o_group {
|
||||
|
||||
// all groups take width 100% in mobile
|
||||
@mixin o-generate-groups($n) {
|
||||
@for $i from 1 through $n {
|
||||
.o_group_col_#{$i} {
|
||||
@include media-breakpoint-down(md) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include o-generate-groups($o-form-group-cols);
|
||||
|
||||
&.o_inner_group {
|
||||
>tbody>tr>td {
|
||||
padding: 0 $o-innergroup-rpadding 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(vsm, $o-extra-grid-breakpoints) {
|
||||
.o_field_widget {
|
||||
&.o_text_overflow {
|
||||
width: 1px !important;
|
||||
// hack to make the table layout believe it is a small element (so that the table does not grow too much) ...
|
||||
min-width: 100%;
|
||||
// ... but in fact it takes the whole table space
|
||||
}
|
||||
}
|
||||
|
||||
.o_form_label {
|
||||
margin-bottom: $o-form-spacing-unit;
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(md) {
|
||||
&.o_label_nowrap .o_form_label {
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Labels
|
||||
.o_form_label {
|
||||
margin: auto;
|
||||
text-align: right;
|
||||
|
||||
&.o_form_label_empty,
|
||||
&.o_form_label_false {
|
||||
opacity: 0.5;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
font-size: $o-font-size-base-touch;
|
||||
}
|
||||
}
|
||||
|
||||
// Chatter
|
||||
>.oe_chatter,
|
||||
.o_FormRenderer_chatterContainer {
|
||||
flex: 1000 0 auto;
|
||||
@include make-container();
|
||||
// @include make-container-max-widths();
|
||||
padding: $grid-gutter-width*0.25 0;
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(xl) {
|
||||
|
||||
>.oe_chatter,
|
||||
.o_FormRenderer_chatterContainer {
|
||||
max-width: 530px;
|
||||
}
|
||||
}
|
||||
|
||||
// Notebooks
|
||||
.o_notebook {
|
||||
>.o_notebook_headers {
|
||||
// @include o-form-sheet-negative-margin;
|
||||
|
||||
>.nav.nav-tabs {
|
||||
@include o-form-sheet-inner-left-padding;
|
||||
//border-bottom: $nav-tabs-border-width solid gray('400');
|
||||
|
||||
>.nav-item {
|
||||
white-space: nowrap;
|
||||
|
||||
>.nav-link {
|
||||
color: $body-color;
|
||||
//border-color: gray('400');
|
||||
//background-color: white;
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
outline: none;
|
||||
color: $link-color;
|
||||
}
|
||||
|
||||
&.active {
|
||||
//border-bottom-color: white;
|
||||
|
||||
&,
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
color: $headings-color;
|
||||
//border-top-color: $o-brand-odoo;
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
color: $text-muted;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
>.tab-content {
|
||||
border-bottom: 1px solid gray('400');
|
||||
|
||||
>.tab-pane {
|
||||
|
||||
> :first-child {
|
||||
// Reset margin to 0 and use tab-pane's padding
|
||||
// to define the distance between panel and content
|
||||
margin-top: 0;
|
||||
|
||||
// These elements will appear attached to the tabs
|
||||
&.oe_form_field_html {
|
||||
@include o-form-sheet-negative-margin;
|
||||
margin-top: -$o-horizontal-padding;
|
||||
border: none;
|
||||
|
||||
.note-editor.panel {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.note-toolbar.panel-heading {
|
||||
@include o-form-sheet-inner-left-padding;
|
||||
@include o-form-sheet-inner-right-padding;
|
||||
border-top: none;
|
||||
padding-top: $o-horizontal-padding*0.3;
|
||||
// background: white;
|
||||
|
||||
.btn-secondary {
|
||||
@include o-hover-text-color($body-color, $link-color);
|
||||
}
|
||||
}
|
||||
|
||||
.note-editing-area,
|
||||
.o_readonly {
|
||||
padding: $card-spacer-y $card-spacer-x;
|
||||
@include o-form-sheet-inner-left-padding;
|
||||
@include o-form-sheet-inner-right-padding;
|
||||
}
|
||||
|
||||
// If immediatly followed by an .oe_clear element, the note-editor it's the 'only'
|
||||
// tab's element. Reset margins to push the bar at the bottom.
|
||||
+.oe_clear:last-child {
|
||||
margin-bottom: -$o-horizontal-padding - $o-sheet-vpadding - $o-form-spacing-unit;
|
||||
}
|
||||
}
|
||||
|
||||
&.o_field_x2many.o_field_x2many_list {
|
||||
display: block;
|
||||
width: auto;
|
||||
@include o-form-sheet-negative-margin;
|
||||
margin-top: -$o-horizontal-padding;
|
||||
|
||||
.o_pager {
|
||||
button {
|
||||
min-width: 34px;
|
||||
}
|
||||
}
|
||||
|
||||
.table-responsive {
|
||||
border-top: 1px solid #dee2e6
|
||||
}
|
||||
|
||||
// use original padding-left for handle cell in editable list
|
||||
tr> :first-child.o_handle_cell {
|
||||
padding-left: $o-horizontal-padding;
|
||||
}
|
||||
|
||||
tr> :first-child {
|
||||
@include o-form-sheet-inner-left-padding;
|
||||
}
|
||||
|
||||
tr> :last-child {
|
||||
@include o-form-sheet-inner-right-padding;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child>.tab-content {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
// Notebooks for small screen
|
||||
@include media-breakpoint-down(sm) {
|
||||
|
||||
.o_notebook .o_notebook_headers {
|
||||
overflow-x: auto;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.nav.nav-tabs {
|
||||
flex-flow: row nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.o_cp_buttons {
|
||||
width: 100%;
|
||||
|
||||
div,
|
||||
.o-kanban-button-new {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.o_cp_action_menus {
|
||||
padding: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
// One2Many List views
|
||||
.o_field_widget {
|
||||
.o_list_table {
|
||||
&.table-striped {
|
||||
>tbody {
|
||||
>tr:not(.o_data_row)>td {
|
||||
border-top: none;
|
||||
}
|
||||
}
|
||||
|
||||
// Show "border" if tfoot has content only
|
||||
>tfoot>tr {
|
||||
|
||||
// box-shadow: inset 0 1px 0 gray('300');
|
||||
>td {
|
||||
border: none;
|
||||
|
||||
&:empty {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Translate icon
|
||||
.o_field_translate {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
// Specific style classes
|
||||
.o_group.o_inner_group.oe_subtotal_footer {
|
||||
border-top: 1px solid gray('300');
|
||||
|
||||
.oe_subtotal_footer_separator {
|
||||
border-top: 1px solid gray('300');
|
||||
}
|
||||
}
|
||||
|
||||
// Status Bar
|
||||
.o_form_statusbar {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
|
||||
>.o_statusbar_status {
|
||||
.dropdown-menu {
|
||||
.o_arrow_button {
|
||||
font-weight: 500;
|
||||
padding: 0.75rem;
|
||||
|
||||
&.btn-primary.disabled {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
// Last element at the right should respect overall padding
|
||||
&:first-of-type {
|
||||
padding-right: $o-horizontal-padding;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(xxl, $o-extra-grid-breakpoints) {
|
||||
// flex-flow: row nowrap;
|
||||
height: 100%;
|
||||
|
||||
>.o_form_sheet_bg {
|
||||
flex: 1 1 auto; // Side chatter is disabled if this has to shrink but this was added for safety
|
||||
// width: $o-form-sheet-min-width + 2 * $o-horizontal-padding;
|
||||
// max-width: map-get($container-max-widths, xl) + 2 * $o-horizontal-padding; // would be logical but breaks no-chatter form views
|
||||
padding: 0 $o-horizontal-padding;
|
||||
overflow: auto;
|
||||
border-bottom: none;
|
||||
|
||||
> :not(.o_form_sheet) {
|
||||
margin-left: -$o-horizontal-padding;
|
||||
margin-right: -$o-horizontal-padding;
|
||||
}
|
||||
|
||||
>.o_form_sheet {
|
||||
width: 100%;
|
||||
max-width: map-get($container-max-widths, xl);
|
||||
}
|
||||
}
|
||||
|
||||
>.oe_chatter,
|
||||
.o_FormRenderer_chatterContainer {
|
||||
// flex: 1 1 auto; // Side chatter is disabled if this has to shrink but this was added for safety
|
||||
width: $o-chatter-min-width;
|
||||
max-width: 530px;
|
||||
padding: 0;
|
||||
overflow: auto;
|
||||
border-left: 1px solid gray('300');
|
||||
background-color: white;
|
||||
|
||||
.o_chatter_topbar {
|
||||
height: $o-statusbar-height + $nav-tabs-border-width;
|
||||
|
||||
.btn,
|
||||
.btn.o_active {
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&.o_chatter_composer_active {
|
||||
.o_chatter_topbar {
|
||||
|
||||
.btn,
|
||||
.btn.o_active {
|
||||
border-top: 0;
|
||||
|
||||
&:first-child {
|
||||
border-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_thread_composer {
|
||||
border-width: 1px 0;
|
||||
margin-top: 0;
|
||||
|
||||
// have mention drowdown below composer
|
||||
.o_composer_mention_dropdown {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
|
||||
>.dropdown-menu {
|
||||
top: 100%;
|
||||
bottom: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_field_widget {
|
||||
&.o_field_many2one {
|
||||
.o_external_button {
|
||||
background: transparent;
|
||||
min-width: 34px;
|
||||
|
||||
&:hover {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_input_dropdown>input {
|
||||
height: 34px;
|
||||
line-height: 34px;
|
||||
padding-right: 15px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.o_field_color {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.o_input_dropdown {
|
||||
.o_dropdown_arrow {
|
||||
position: absolute;
|
||||
line-height: 34px;
|
||||
top: 0px;
|
||||
bottom: 0px;
|
||||
left: auto;
|
||||
bottom: auto;
|
||||
right: 8px;
|
||||
color: #c0c4cc;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
pointer-events: none;
|
||||
-webkit-transition: -webkit-transform .3s;
|
||||
transition: -webkit-transform .3s;
|
||||
transition: transform .3s;
|
||||
transition: transform .3s, -webkit-transform .3s;
|
||||
-webkit-transform: rotateZ(180deg);
|
||||
transform: rotateZ(180deg);
|
||||
|
||||
&.anita_reverse {
|
||||
-webkit-transform: rotateZ(0);
|
||||
transform: rotateZ(0)
|
||||
}
|
||||
|
||||
&.dropdown_reverse {
|
||||
-webkit-transform: rotateZ(0);
|
||||
transform: rotateZ(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.o_field_many2manytags {
|
||||
overflow: hidden;
|
||||
align-items: center;
|
||||
|
||||
.o_input_dropdown {
|
||||
.o_dropdown_arrow {
|
||||
right: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.o_form_uri {
|
||||
line-height: 36px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Overriden style when form view in modal
|
||||
.modal .modal-dialog {
|
||||
.o_form_view {
|
||||
@include media-breakpoint-down(md) {
|
||||
.o_group.o_inner_group>tbody>tr>td.o_td_label {
|
||||
min-width: 100px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.modal-sm .o_form_view {
|
||||
.o_group {
|
||||
@include form-break-table;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// XXS form view specific rules
|
||||
.o_form_view.o_xxs_form_view {
|
||||
.o_group {
|
||||
|
||||
// Target XXS form view on mobile devices in portrait mode
|
||||
@include media-breakpoint-down(sm) {
|
||||
&.o_inner_group>tbody>tr>td {
|
||||
.o_field_widget {
|
||||
margin-bottom: $o-form-spacing-unit * 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_td_label .o_form_label {
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
|
||||
.o_form_label {
|
||||
margin-top: 3px;
|
||||
font-size: $o-label-font-size-factor * $o-font-size-base-touch;
|
||||
font-weight: normal;
|
||||
color: #6c757d;
|
||||
|
||||
&.o_form_label_empty {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
color: #495057;
|
||||
background-color: #FFFFFF;
|
||||
border-color: #007bff;
|
||||
outline: 0;
|
||||
box-shadow: 0 0 0 0.2rem rgba(233, 236, 239, 0.25);
|
||||
}
|
||||
|
||||
.ui-autocomplete {
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
.o_setting_right_pane {
|
||||
.o_web_settings_invite {
|
||||
margin: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.o_external_button {
|
||||
background: none;
|
||||
border: none;
|
||||
|
||||
&:hover {
|
||||
background: none;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.note-editor {
|
||||
border: 1px solid #dcdfe6 !important;
|
||||
|
||||
.panel-heading {
|
||||
&.note-toolbar {
|
||||
border-bottom: 1px solid #dcdfe6;
|
||||
background-color: #ffffff;
|
||||
color: #212529;
|
||||
|
||||
.btn {
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
background-color: #595964;
|
||||
color: #C6C6C6;
|
||||
}
|
||||
|
||||
.btn-group.show>.btn {
|
||||
background: #dee2e6;
|
||||
color: rgba(76, 76, 76, 0.82);
|
||||
}
|
||||
|
||||
.dropdown-item:not(.d-none)~.dropdown-item {
|
||||
border-top: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_input_dropdown {
|
||||
.o_dropdown_arrow {
|
||||
position: absolute;
|
||||
line-height: 34px;
|
||||
top: 0px;
|
||||
bottom: 0px;
|
||||
left: auto;
|
||||
bottom: auto;
|
||||
right: 8px;
|
||||
color: #c0c4cc;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
pointer-events: none;
|
||||
-webkit-transition: -webkit-transform .3s;
|
||||
transition: -webkit-transform .3s;
|
||||
transition: transform .3s;
|
||||
transition: transform .3s, -webkit-transform .3s;
|
||||
-webkit-transform: rotateZ(180deg);
|
||||
transform: rotateZ(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
.o_field_binary_file {
|
||||
|
||||
.btn,
|
||||
button {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-tabs {
|
||||
|
||||
.nav-link.active,
|
||||
.nav-item.show .nav-link {
|
||||
color: #495057;
|
||||
background-color: #FFFFFF;
|
||||
border-color: #dee2e6 #dee2e6 #FFFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
.o_field_binary_file>.btn {
|
||||
padding: 0px 10px;
|
||||
height: 34px;
|
||||
line-height: 34px;
|
||||
}
|
||||
|
||||
.o_required_modifier.o_input,
|
||||
.o_required_modifier .o_input {
|
||||
background-color: #f8f8ff !important;
|
||||
}
|
||||
|
||||
.oe_title {
|
||||
h1 {
|
||||
padding: 10px 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.o_form_view .o_form_statusbar>.o_statusbar_status>.o_arrow_button.btn-primary.disabled {
|
||||
background-color: #006fe6;
|
||||
}
|
||||
|
||||
.note-popover .popover .popover-body .dropdown-menu,
|
||||
.panel-heading.note-toolbar .dropdown-menu {
|
||||
border: none;
|
||||
background-color: inherit;
|
||||
border-color: inherit;
|
||||
box-shadow: inherit;
|
||||
}
|
||||
|
||||
.o_field_x2many_list {
|
||||
border: 1px solid #dcdfe6;
|
||||
|
||||
.o_list_table {
|
||||
border-top: none;
|
||||
margin-top: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.o_form_view {
|
||||
.o_notebook {
|
||||
.tab-content {
|
||||
padding: 10px
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_boolean_toggle {
|
||||
&.custom-control.custom-checkbox>label.custom-control-label::after {
|
||||
content: none !important;
|
||||
}
|
||||
|
||||
&.custom-control.custom-checkbox>label.custom-control-label::before,
|
||||
&.custom-control.custom-checkbox>label.custom-control-label::after {
|
||||
left: 1px !important;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
// Bootstrap functions
|
||||
//
|
||||
// Utility mixins and functions for evalutating source code across our variables, maps, and mixins.
|
||||
|
||||
// Ascending
|
||||
// Used to evaluate Sass maps like our grid breakpoints.
|
||||
@mixin _assert-ascending($map, $map-name) {
|
||||
$prev-key: null;
|
||||
$prev-num: null;
|
||||
@each $key, $num in $map {
|
||||
@if $prev-num == null {
|
||||
// Do nothing
|
||||
} @else if not comparable($prev-num, $num) {
|
||||
@warn "Potentially invalid value for #{$map-name}: This map must be in ascending order, but key '#{$key}' has value #{$num} whose unit makes it incomparable to #{$prev-num}, the value of the previous key '#{$prev-key}' !";
|
||||
} @else if $prev-num >= $num {
|
||||
@warn "Invalid value for #{$map-name}: This map must be in ascending order, but key '#{$key}' has value #{$num} which isn't greater than #{$prev-num}, the value of the previous key '#{$prev-key}' !";
|
||||
}
|
||||
$prev-key: $key;
|
||||
$prev-num: $num;
|
||||
}
|
||||
}
|
||||
|
||||
// Starts at zero
|
||||
// Another grid mixin that ensures the min-width of the lowest breakpoint starts at 0.
|
||||
@mixin _assert-starts-at-zero($map) {
|
||||
$values: map-values($map);
|
||||
$first-value: nth($values, 1);
|
||||
@if $first-value != 0 {
|
||||
@warn "First breakpoint in `$grid-breakpoints` must start at 0, but starts at #{$first-value}.";
|
||||
}
|
||||
}
|
||||
|
||||
// Replace `$search` with `$replace` in `$string`
|
||||
// Used on our SVG icon backgrounds for custom forms.
|
||||
//
|
||||
// @author Hugo Giraudel
|
||||
// @param {String} $string - Initial string
|
||||
// @param {String} $search - Substring to replace
|
||||
// @param {String} $replace ('') - New value
|
||||
// @return {String} - Updated string
|
||||
@function str-replace($string, $search, $replace: "") {
|
||||
$index: str-index($string, $search);
|
||||
|
||||
@if $index {
|
||||
@return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace);
|
||||
}
|
||||
|
||||
@return $string;
|
||||
}
|
||||
|
||||
// Color contrast
|
||||
@mixin color-yiq($color) {
|
||||
$r: red($color);
|
||||
$g: green($color);
|
||||
$b: blue($color);
|
||||
|
||||
$yiq: (($r * 299) + ($g * 587) + ($b * 114)) / 1000;
|
||||
|
||||
@if ($yiq >= 150) {
|
||||
color: darken($color, 65);
|
||||
} @else {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
// Retreive color Sass maps
|
||||
@function color($key: "blue") {
|
||||
@return map-get($colors, $key);
|
||||
}
|
||||
|
||||
@function theme-color($key: "primary") {
|
||||
@return map-get($theme-colors, $key);
|
||||
}
|
||||
|
||||
@function grayscale($key: "100") {
|
||||
@return map-get($grays, $key);
|
||||
}
|
||||
|
||||
// Request a theme color level
|
||||
@function theme-color-level($color-name: "primary", $level: 0) {
|
||||
$color: theme-color($color-name);
|
||||
$color-base: if($level > 0, #000, #fff);
|
||||
|
||||
@if $level < 0 {
|
||||
// Lighter values need a quick double negative for the Sass math to work
|
||||
@return mix($color-base, $color, $level * -1 * $theme-color-interval);
|
||||
} @else {
|
||||
@return mix($color-base, $color, $level * $theme-color-interval);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,229 @@
|
||||
|
||||
html .o_web_client > .o_action_manager > .o_action .o_content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&.o_component_with_search_panel {
|
||||
flex-direction: row !important;
|
||||
}
|
||||
}
|
||||
|
||||
.o_list_view {
|
||||
overflow: auto;
|
||||
flex: 1 1 auto;
|
||||
|
||||
.custom-checkbox {
|
||||
.custom-control-label::before {
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
thead>tr>th:nth-of-type(1) {
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
.o_list_table {
|
||||
tbody>tr>td:not(.o_list_record_selector):not(.o_handle_cell) {
|
||||
vertical-align: middle !important;
|
||||
}
|
||||
|
||||
.o_list_record_selector {
|
||||
vertical-align: middle !important;
|
||||
}
|
||||
}
|
||||
|
||||
.table-responsive {
|
||||
|
||||
.o_list_table {
|
||||
|
||||
thead {
|
||||
box-shadow: 0px 1px 5px 0px rgba(0, 0, 0, 0.1);
|
||||
|
||||
>tr {
|
||||
min-height: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
}
|
||||
|
||||
thead,
|
||||
thead tr th {
|
||||
position: sticky !important;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.o_list_record_selector {
|
||||
width: 40px;
|
||||
padding-left: 16px;
|
||||
}
|
||||
|
||||
tfoot,
|
||||
tfoot>tr>td {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
background: white;
|
||||
background-color: $o-list-footer-bg-color;
|
||||
}
|
||||
|
||||
.o_list_record_selector {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.o_column_sortable:not(.o_handle_cell) {
|
||||
position: relative;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
tfoot {
|
||||
background-color: none !important;
|
||||
}
|
||||
|
||||
.o_data_row {
|
||||
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-color: #dcdfe6;
|
||||
|
||||
&.o_selected_row>.o_data_cell:not(.o_readonly_modifier):not(.o_invisible_modifier) {
|
||||
background-color: transparent;
|
||||
|
||||
&:not(.o_remaining_days_cell) .o_input {
|
||||
border: 1px solid #dcdfe6 !important;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
thead {
|
||||
>tr {
|
||||
>th {
|
||||
padding: 5px 4px !important;
|
||||
border-top: none;
|
||||
border-bottom: none;
|
||||
|
||||
&:not(.o_list_record_selector) {
|
||||
border-left: none;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
&:nth-of-type(1) {
|
||||
border-left: none;
|
||||
padding: 0px 10px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add the shaddow
|
||||
tbody {
|
||||
>tr {
|
||||
>td {
|
||||
|
||||
// padding: 0px 10px !important;
|
||||
&:not(.o_list_record_selector) {
|
||||
&.o_list_button>button {
|
||||
padding: 0.4rem 1.2rem;
|
||||
}
|
||||
|
||||
&:not(.o_handle_cell) {
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-of-type(odd) {
|
||||
background-color: rgba(0, 0, 0, 0.01);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_list_record_remove {
|
||||
padding-right: 0px !important;
|
||||
line-height: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.o_field_cell,
|
||||
.o_data_cell,
|
||||
.o_column_sortable {
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
padding: 0 10px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
.o_optional_columns_dropdown_toggle,
|
||||
.o_optional_columns {
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
}
|
||||
|
||||
.o_field_badge {
|
||||
margin-top: 8px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.o_optional_columns_dropdown_toggle,
|
||||
.o_optional_columns {
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.o_list_view {
|
||||
.o_list_table {
|
||||
tfoot {
|
||||
background-color: none !important;
|
||||
border-bottom: 1px solid #dcdfe6;
|
||||
}
|
||||
|
||||
.o_data_row.o_selected_row>.o_data_cell:not(.o_readonly_modifier):not(.o_invisible_modifier) {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
thead>tr>th:not(.o_list_record_selector) {
|
||||
border-left: none;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
&.table-bordered {
|
||||
border-collapse: collapse;
|
||||
|
||||
.tfoot {
|
||||
border-top: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_field_x2many_list_row_add {
|
||||
list-style: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.o_list_buttons {
|
||||
button {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.o_form_sheet {
|
||||
.o_list_view {
|
||||
.o_list_table {
|
||||
thead {
|
||||
box-shadow: none !important;
|
||||
border-bottom: 1px solid #dcdfe6;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table > :not(caption) > * > * {
|
||||
border-bottom-color: #dcdfe6;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
|
||||
.o_filter_condition {
|
||||
select:not(:first-child) {
|
||||
margin-top: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.row {
|
||||
.o_field_widget.o_readonly_modifier {
|
||||
padding-top: calc(0.375rem + 1px);
|
||||
padding-bottom: calc(0.375rem + 1px - 5px);
|
||||
}
|
||||
}
|
||||
|
||||
.o_input input {
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.o_form_status_indicator_buttons {
|
||||
button {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
|
||||
// scroll bar
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: #FFFFFF;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #888;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:increment,
|
||||
::-webkit-scrollbar-thumb:decrement {
|
||||
background: #000;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #555;
|
||||
}
|
||||
@@ -0,0 +1,327 @@
|
||||
$white: #fff !default;
|
||||
$gray-100: #f8f9fa !default;
|
||||
$gray-200: #e9ecef !default;
|
||||
$gray-300: #dee2e6 !default;
|
||||
$gray-400: #ced4da !default;
|
||||
$gray-500: #adb5bd !default;
|
||||
$gray-600: #868e96 !default;
|
||||
$gray-700: #495057 !default;
|
||||
$gray-800: #343a40 !default;
|
||||
$gray-900: #212529 !default;
|
||||
$black: #000 !default;
|
||||
|
||||
$grays: () !default;
|
||||
$grays: map-merge((
|
||||
"100": $gray-100,
|
||||
"200": $gray-200,
|
||||
"300": $gray-300,
|
||||
"400": $gray-400,
|
||||
"500": $gray-500,
|
||||
"600": $gray-600,
|
||||
"700": $gray-700,
|
||||
"800": $gray-800,
|
||||
"900": $gray-900
|
||||
), $grays);
|
||||
|
||||
$blue: #007bff !default;
|
||||
$indigo: #674eec !default;
|
||||
$purple: #8445f7 !default;
|
||||
$pink: #ff4169 !default;
|
||||
$red: #c4183c !default;
|
||||
$orange: #fb7906 !default;
|
||||
$yellow: #ffb400 !default;
|
||||
$green: #17c671 !default;
|
||||
$teal: #1adba2 !default;
|
||||
$cyan: #00b8d8 !default;
|
||||
|
||||
$colors: () !default;
|
||||
$colors: map-merge((
|
||||
"blue": $blue,
|
||||
"indigo": $indigo,
|
||||
"purple": $purple,
|
||||
"pink": $pink,
|
||||
"red": $red,
|
||||
"orange": $orange,
|
||||
"yellow": $yellow,
|
||||
"green": $green,
|
||||
"teal": $teal,
|
||||
"cyan": $cyan,
|
||||
"white": $white,
|
||||
"gray": $gray-600,
|
||||
"gray-dark": $gray-800
|
||||
), $colors);
|
||||
|
||||
$primary: $blue !default;
|
||||
$secondary: $blueish-grey !default;
|
||||
$success: $green !default;
|
||||
$info: $cyan !default;
|
||||
$warning: $yellow !default;
|
||||
$danger: $red !default;
|
||||
$light: $gray-200 !default;
|
||||
$dark: $gray-900 !default;
|
||||
|
||||
// Options
|
||||
$enable-rounded: true !default;
|
||||
$enable-shadows: true !default;
|
||||
$enable-gradients: false !default;
|
||||
$enable-transitions: true !default;
|
||||
$enable-hover-media-query: false !default;
|
||||
$enable-fonts-import: true !default;
|
||||
|
||||
$theme-colors: () !default;
|
||||
$theme-colors: map-merge((
|
||||
"primary": $primary,
|
||||
"secondary": $secondary,
|
||||
"success": $success,
|
||||
"info": $info,
|
||||
"warning": $warning,
|
||||
"danger": $danger,
|
||||
"light": $light,
|
||||
"dark": $dark
|
||||
), $theme-colors);
|
||||
|
||||
$blueish-grey: #5A6169 !default;
|
||||
|
||||
// Easings
|
||||
$ease-in-out-circ: cubic-bezier(.27,.01,.38,1.06);
|
||||
|
||||
// Transitions
|
||||
$transition-duration: 250ms;
|
||||
$transition-base: all $transition-duration $ease-in-out-circ !default;
|
||||
$transition-fade: opacity 200ms ease-in-out !default;
|
||||
$transition-collapse: height 350ms ease-in-out !default;
|
||||
|
||||
// Fonts
|
||||
$google-fonts-import: 'https://fonts.googleapis.com/css?family=Poppins:300,400,500,600|Roboto+Mono' !default; // Includes: Poppins: Light, Regular, Medium, Semi-bold; Roboto Mono: Regular
|
||||
|
||||
$font-family-poppins-first: "Poppins", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif !default;
|
||||
$font-family-system-first: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif !default;
|
||||
$font-family-roboto-mono-first: "Roboto Mono", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace !default;
|
||||
|
||||
$font-family-sans-serif: $font-family-system-first !default;
|
||||
$font-family-monospace: $font-family-roboto-mono-first !default;
|
||||
$font-family-base: $font-family-sans-serif !default;
|
||||
|
||||
$font-size-base: 1rem !default;
|
||||
$font-size-lg: 1.25rem !default;
|
||||
$font-size-sm: .875rem !default;
|
||||
|
||||
$font-weight-normal: 300 !default;
|
||||
$font-weight-bold: 500 !default;
|
||||
|
||||
$font-weight-base: $font-weight-normal !default;
|
||||
$line-height-base: 1.5 !default;
|
||||
|
||||
// Buttons
|
||||
// For each of Bootstrap's buttons, define text, background and border color.
|
||||
$btn-padding-y-lg: .75rem !default;
|
||||
$btn-padding-x-lg: 1.75rem !default;
|
||||
|
||||
$btn-line-height-sm: $line-height-sm !default;
|
||||
$btn-line-height-lg: $line-height-lg !default;
|
||||
|
||||
$btn-padding-y-sm: .35rem !default;
|
||||
$btn-padding-x-sm: 1rem !default;
|
||||
|
||||
$input-btn-padding-y: .5rem !default;
|
||||
$input-btn-padding-x: 1rem !default;
|
||||
$input-btn-line-height: 1.125 !default;
|
||||
|
||||
$input-btn-padding-y-sm: .35rem !default;
|
||||
$input-btn-padding-x-sm: .75rem !default;
|
||||
|
||||
$input-btn-padding-y-lg: .75rem !default;
|
||||
$input-btn-padding-x-lg: 1rem !default;
|
||||
|
||||
$btn-pill-border-radius: 50px !default;
|
||||
|
||||
$btn-padding-y: .75rem !default;
|
||||
$btn-padding-x: 1.25rem !default;
|
||||
|
||||
$btn-font-weight: $font-weight-normal !default;
|
||||
$btn-font-size: .875rem !default;
|
||||
$btn-font-size-sm: 0.75rem !default;
|
||||
$btn-font-size-lg: 1.125rem !default;
|
||||
$btn-font-family: $font-family-poppins-first !default;
|
||||
$btn-box-shadow: none !default;
|
||||
|
||||
$input-btn-focus-width: .2rem !default;
|
||||
|
||||
$btn-active-box-shadow: inset 0 3px 5px rgba($black,.125) !default;
|
||||
|
||||
$btn-link-disabled-color: $gray-600 !default;
|
||||
$btn-block-spacing-y: .5rem !default;
|
||||
|
||||
// Allows for customizing button radius independently from global border radius
|
||||
$btn-border-radius: $border-radius !default;
|
||||
$btn-border-radius-lg: $border-radius-lg !default;
|
||||
$btn-border-radius-sm: $border-radius-sm !default;
|
||||
|
||||
$btn-border-width: 1px !default;
|
||||
|
||||
$btn-transition: $transition-base !default;
|
||||
|
||||
// Forms
|
||||
$input-padding-y: $input-btn-padding-y !default;
|
||||
$input-padding-x: $input-btn-padding-x !default;
|
||||
$input-line-height: 1.5 !default;
|
||||
|
||||
$input-padding-y-sm: $input-btn-padding-y-sm !default;
|
||||
$input-padding-x-sm: $input-btn-padding-x-sm !default;
|
||||
$input-line-height-sm: 1.5 !default;
|
||||
|
||||
$input-padding-y-lg: $input-btn-padding-y-lg !default;
|
||||
$input-padding-x-lg: $input-btn-padding-x-lg !default;
|
||||
$input-line-height-lg: 1.5 !default;
|
||||
|
||||
$input-bg: $white !default;
|
||||
$input-disabled-bg: #f5f6f7 !default;
|
||||
|
||||
$input-font-size: .95rem !default;
|
||||
|
||||
$input-color: $gray-700 !default;
|
||||
$input-border-color: $border-color !default;
|
||||
$input-btn-border-width: $border-width !default;
|
||||
$input-box-shadow: none !default;
|
||||
|
||||
$input-border-width: $border-width !default;
|
||||
$input-border-radius: $border-radius !default;
|
||||
$input-border-radius-lg: $border-radius-lg !default;
|
||||
$input-border-radius-sm: $border-radius-sm !default;
|
||||
|
||||
$input-focus-bg: $input-bg !default;
|
||||
$input-focus-border-color: theme-color("primary") !default;
|
||||
$input-focus-box-shadow: 0 .313rem .719rem rgba(theme-color("primary"),.1), 0 .156rem .125rem rgba($black, .06) !default;
|
||||
$input-focus-color: $input-color !default;
|
||||
|
||||
$input-hover-border-color: darken($input-border-color, 15) !default;
|
||||
|
||||
$input-placeholder-color: $gray-600 !default;
|
||||
|
||||
$input-height-border: $input-border-width * 2 !default;
|
||||
|
||||
$input-height-inner: ($input-font-size * $input-line-height) + ($input-padding-y * 2) !default;
|
||||
$input-height: calc(#{$input-height-inner} + #{$input-height-border}) !default;
|
||||
|
||||
$input-height-inner-sm: ($font-size-sm * $input-line-height-sm) + ($input-padding-y-sm * 2) !default;
|
||||
$input-height-sm: calc(#{$input-height-inner-sm} + #{$input-height-border}) !default;
|
||||
|
||||
$input-height-inner-lg: ($font-size-lg * $input-line-height-lg) + ($input-padding-y-lg * 2) !default;
|
||||
$input-height-lg: calc(#{$input-height-inner-lg} + #{$input-height-border}) !default;
|
||||
|
||||
$input-transition: box-shadow $transition-duration $ease-in-out-circ,
|
||||
border $transition-duration $ease-in-out-circ !default;
|
||||
|
||||
$form-text-margin-top: .25rem !default;
|
||||
|
||||
$form-check-input-gutter: 1.25rem !default;
|
||||
$form-check-input-margin-y: .313rem !default;
|
||||
$form-check-input-margin-x: .313rem !default;
|
||||
|
||||
$form-check-inline-margin-x: .75rem !default;
|
||||
$form-check-inline-input-margin-x: .3125rem !default;
|
||||
|
||||
$form-group-margin-bottom: 1rem !default;
|
||||
|
||||
$input-group-padding-y: 12px !default;
|
||||
$input-group-padding-x: 14px !default;
|
||||
$input-group-addon-bg: lighten($input-border-color, 19%) !default;
|
||||
$input-group-addon-border-color: $input-border-color !default;
|
||||
$input-group-addon-color: $input-frozen-color !default;
|
||||
|
||||
|
||||
$custom-control-gutter: 1.688rem !default;
|
||||
$custom-control-spacer-x: 1rem !default;
|
||||
|
||||
$custom-control-indicator-size: 1.125rem !default;
|
||||
$custom-control-indicator-bg: $white !default;
|
||||
$custom-control-indicator-bg-size: 50% 50% !default;
|
||||
$custom-control-indicator-box-shadow: none !default;
|
||||
$custom-control-indicator-border: 1px solid $border-color !default;
|
||||
$custom-checkbox-indicator-transition: transform $transition-duration $ease-in-out-circ,
|
||||
border $transition-duration $ease-in-out-circ !default;
|
||||
|
||||
$custom-control-indicator-disabled-bg: $gray-200 !default;
|
||||
$custom-control-description-disabled-color: $gray-600 !default;
|
||||
|
||||
$custom-control-indicator-checked-color: $white !default;
|
||||
$custom-control-indicator-checked-bg: theme-color("primary") !default;
|
||||
$custom-control-indicator-checked-box-shadow: none !default;
|
||||
|
||||
$custom-control-indicator-focus-box-shadow: $input-focus-box-shadow !default;
|
||||
|
||||
$custom-control-indicator-active-color: $white !default;
|
||||
$theme-color-primary: theme-color("primary") !default;
|
||||
$custom-control-indicator-active-bg: lighten(#007bff, 35%) !default;
|
||||
$custom-control-indicator-active-box-shadow: none !default;
|
||||
|
||||
// Custom checkbox
|
||||
$custom-checkbox-indicator-border-radius: 2px !default;
|
||||
$custom-checkbox-indicator-icon-checked: none !default;
|
||||
$custom-checkbox-indicator-indeterminate-bg: theme-color("primary") !default;
|
||||
$custom-checkbox-indicator-indeterminate-color: $custom-control-indicator-checked-color !default;
|
||||
$custom-checkbox-indicator-icon-indeterminate: none !default;
|
||||
$custom-checkbox-indicator-indeterminate-box-shadow: none !default;
|
||||
|
||||
// Custom radio
|
||||
$custom-radio-indicator-border-radius: 50% !default;
|
||||
|
||||
// Custom switches (core)
|
||||
$custom-switch-padding-left: 2.25rem !default;
|
||||
|
||||
// Custom toggle
|
||||
$custom-toggle-width: 3.125rem !default;
|
||||
$custom-toggle-height: 1.75rem !default;
|
||||
$custom-toggle-border-width: 0.0625rem !default;
|
||||
$custom-toggle-border: $custom-toggle-border-width solid $input-border-color !default;
|
||||
$custom-toggle-background-color: $white !default;
|
||||
$custom-toggle-border-radius: 100px !default;
|
||||
|
||||
$custom-toggle-focus-box-shadow: 0 .313rem .719rem rgba($green,.1), 0 .156rem .125rem rgba($black, .06) !default;
|
||||
|
||||
$custom-toggle-checked-background: theme-color("success") !default;
|
||||
$custom-toggle-checked-border-color: $custom-toggle-checked-background !default;
|
||||
$custom-toggle-checked-knob-background: $white !default;
|
||||
|
||||
$custom-toggle-knob-width: 1.25rem !default;
|
||||
$custom-toggle-knob-height: 1.25rem !default;
|
||||
$custom-toggle-knob-border-radius: 6.25rem !default;
|
||||
$custom-toggle-knob-active-width: 1.625rem !default;
|
||||
|
||||
$custom-toggle-invalid-knob-background-color: #eb8c95 !default;
|
||||
$custom-toggle-invalid-background-color: $white !default;
|
||||
|
||||
$custom-toggle-width-sm : 2.1875rem !default;
|
||||
$custom-toggle-height-sm : 1.125rem !default;
|
||||
|
||||
$custom-toggle-knob-width-sm: .75rem !default;
|
||||
$custom-toggle-knob-active-width-sm: 1rem !default;
|
||||
$custom-toggle-knob-height-sm: .75rem !default;
|
||||
|
||||
$body-bg: $white !default;
|
||||
$body-color: $blueish-grey !default;
|
||||
|
||||
// awesome
|
||||
$sidebar_toggle_one_width: 70px;
|
||||
$sidebar_toggle_two_width: 240px;
|
||||
$o-font-size-base-touch: 12px;
|
||||
$custom-file-height: 30px;
|
||||
$custom-file-width: 200px;
|
||||
|
||||
// form
|
||||
$o-form-lightsecondary: lighten($o-brand-secondary, 25%) !default;
|
||||
|
||||
$o-tooltip-background-color: $o-view-background-color !default;
|
||||
$o-tooltip-color: $o-main-text-color !default;
|
||||
$o-tooltip-arrow-color: $o-main-text-color !default;
|
||||
|
||||
// Needed for having no spacing between sheet and mail body in mass_mailing:
|
||||
// Different required cancel paddings between web and web_enterprise
|
||||
$o-sheet-cancel-tpadding: $o-horizontal-padding !default;
|
||||
$o-sheet-cancel-bpadding: $o-horizontal-padding + $o-sheet-vpadding !default;
|
||||
|
||||
// Side chatter
|
||||
$o-chatter-min-width: 530px !default;
|
||||
// Safest for the next value would be map-get($container-max-widths, lg) as it
|
||||
// is the minimal width of the default form view design for md/lg sizes
|
||||
$o-form-sheet-min-width: 990px !default;
|
||||
@@ -0,0 +1,135 @@
|
||||
/* devanagari */
|
||||
@font-face {
|
||||
font-family: 'Poppins';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url('/anita_theme_base/static/css/login/fonts/pxiByp8kv8JHgFVrLDz8Z11lFc-K.woff2') format('woff2');
|
||||
unicode-range: U+0900-097F, U+1CD0-1CF6, U+1CF8-1CF9, U+200C-200D, U+20A8, U+20B9, U+25CC, U+A830-A839, U+A8E0-A8FB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Poppins';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url('/anita_theme_base/static/css/login/fonts/pxiByp8kv8JHgFVrLDz8Z1JlFc-K.woff2') format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Poppins';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url('/anita_theme_base/static/css/login/fonts/pxiByp8kv8JHgFVrLDz8Z1xlFQ.woff2') format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* devanagari */
|
||||
@font-face {
|
||||
font-family: 'Poppins';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url('/anita_theme_base/static/css/login/fonts/pxiEyp8kv8JHgFVrJJbecmNE.woff2') format('woff2');
|
||||
unicode-range: U+0900-097F, U+1CD0-1CF6, U+1CF8-1CF9, U+200C-200D, U+20A8, U+20B9, U+25CC, U+A830-A839, U+A8E0-A8FB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Poppins';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url('/anita_theme_base/static/css/login/fonts/pxiEyp8kv8JHgFVrJJnecmNE.woff2') format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Poppins';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url('/anita_theme_base/static/css/login/fonts/pxiEyp8kv8JHgFVrJJfecg.woff2') format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* devanagari */
|
||||
@font-face {
|
||||
font-family: 'Poppins';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url('/anita_theme_base/static/css/login/fonts/pxiByp8kv8JHgFVrLGT9Z11lFc-K.woff2') format('woff2');
|
||||
unicode-range: U+0900-097F, U+1CD0-1CF6, U+1CF8-1CF9, U+200C-200D, U+20A8, U+20B9, U+25CC, U+A830-A839, U+A8E0-A8FB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Poppins';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url('/anita_theme_base/static/css/login/fonts/pxiByp8kv8JHgFVrLGT9Z1JlFc-K.woff2') format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Poppins';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url('/anita_theme_base/static/css/login/fonts/pxiByp8kv8JHgFVrLGT9Z1xlFQ.woff2') format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* devanagari */
|
||||
@font-face {
|
||||
font-family: 'Poppins';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url('/anita_theme_base/static/css/login/fonts/pxiByp8kv8JHgFVrLEj6Z11lFc-K.woff2') format('woff2');
|
||||
unicode-range: U+0900-097F, U+1CD0-1CF6, U+1CF8-1CF9, U+200C-200D, U+20A8, U+20B9, U+25CC, U+A830-A839, U+A8E0-A8FB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Poppins';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url('/anita_theme_base/static/css/login/fonts/pxiByp8kv8JHgFVrLEj6Z1JlFc-K.woff2') format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Poppins';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url('/anita_theme_base/static/css/login/fonts/pxiByp8kv8JHgFVrLEj6Z1xlFQ.woff2') format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* devanagari */
|
||||
@font-face {
|
||||
font-family: 'Poppins';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url('/anita_theme_base/static/css/login/fonts/pxiByp8kv8JHgFVrLCz7Z11lFc-K.woff2') format('woff2');
|
||||
unicode-range: U+0900-097F, U+1CD0-1CF6, U+1CF8-1CF9, U+200C-200D, U+20A8, U+20B9, U+25CC, U+A830-A839, U+A8E0-A8FB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Poppins';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url('/anita_theme_base/static/css/login/fonts/pxiByp8kv8JHgFVrLCz7Z1JlFc-K.woff2') format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Poppins';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url('/anita_theme_base/static/css/login/fonts/pxiByp8kv8JHgFVrLCz7Z1xlFQ.woff2') format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
@@ -0,0 +1,390 @@
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
overflow: auto;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #182139;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
height: auto;
|
||||
border: 1px solid #bfc9d4;
|
||||
color: #3b3f5c;
|
||||
font-size: 15px;
|
||||
padding: 8px 10px;
|
||||
letter-spacing: 1px;
|
||||
height: calc(1.4em + 1.4rem + 2px);
|
||||
padding: .75rem 1.25rem;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
box-shadow: 0 0 5px 2px rgba(194, 213, 255, 0.6196078431372549);
|
||||
border-color: #1b55e2;
|
||||
color: #3b3f5c;
|
||||
}
|
||||
|
||||
.form-form {
|
||||
width: 50%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.form-form .form-form-wrap {
|
||||
max-width: 480px;
|
||||
margin: 0 auto;
|
||||
min-width: 311px;
|
||||
min-height: 100%;
|
||||
align-self: center;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.form-form .form-container {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.form-form .form-container .form-content {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 25px;
|
||||
background: #fff;
|
||||
text-align: center;
|
||||
border-radius: 15px;
|
||||
border: 1px solid #e0e6ed;
|
||||
-webkit-box-shadow: 0 4px 6px 0 rgba(85, 85, 85, 0.09019607843137255), 0 1px 20px 0 rgba(0, 0, 0, 0.08), 0px 1px 11px 0px rgba(0, 0, 0, 0.06);
|
||||
-moz-box-shadow: 0 4px 6px 0 rgba(85, 85, 85, 0.09019607843137255), 0 1px 20px 0 rgba(0, 0, 0, 0.08), 0px 1px 11px 0px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: 0 4px 6px 0 rgba(85, 85, 85, 0.09019607843137255), 0 1px 20px 0 rgba(0, 0, 0, 0.08), 0px 1px 11px 0px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.form-form .form-container .form-content h1 {
|
||||
font-size: 32px;
|
||||
color: #3b3f5c;
|
||||
}
|
||||
|
||||
.form-form .form-container .form-content>p {
|
||||
font-size: 13px;
|
||||
color: #888ea8;
|
||||
font-weight: 600;
|
||||
margin-bottom: 35px;
|
||||
}
|
||||
|
||||
.form-form .form-form-wrap .user-meta {
|
||||
margin-bottom: 35px;
|
||||
}
|
||||
|
||||
.form-form .form-form-wrap .user-meta img {
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
border-radius: 50%;
|
||||
margin-right: 15px;
|
||||
border: 4px solid #e0e6ed;
|
||||
}
|
||||
|
||||
.form-form .form-form-wrap .user-meta div {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.form-form .form-form-wrap .user-meta p {
|
||||
font-size: 31px;
|
||||
color: #3b3f5c;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.form-form .form-form-wrap h1 .brand-name {
|
||||
color: #3b3f5c;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.form-form .form-form-wrap p.signup-link {
|
||||
font-size: 14px;
|
||||
color: #3b3f5c;
|
||||
font-weight: 700;
|
||||
margin-bottom: 15px;
|
||||
text-align: center;
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.form-form .form-form-wrap p.signup-link.register {
|
||||
font-size: 13px;
|
||||
color: #888ea8;
|
||||
font-weight: 600;
|
||||
margin-bottom: 25px;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.form-form .form-form-wrap p.signup-link.recovery {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.form-form .form-form-wrap p.signup-link a {
|
||||
color: #1b55e2;
|
||||
border-bottom: 1px solid;
|
||||
}
|
||||
|
||||
.form-form .form-form-wrap form .field-wrapper.input {
|
||||
padding: 11px 0px 16px 0;
|
||||
border-bottom: none;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.form-form .form-form-wrap form .field-wrapper.input:focus {
|
||||
border: 1px solid #000;
|
||||
}
|
||||
|
||||
.form-form .form-form-wrap form .field-wrapper.toggle-pass p {
|
||||
font-weight: 600;
|
||||
color: #3b3f5c;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.form-form .form-form-wrap form .field-wrapper .logged-in-user-name {
|
||||
font-size: 37px;
|
||||
color: #3b3f5c;
|
||||
}
|
||||
|
||||
.form-form .form-form-wrap form .field-wrapper label {
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
color: #3b3f5c;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.form-form .form-form-wrap form .field-wrapper.terms_condition {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-form .form-form-wrap form .field-wrapper.terms_condition label {
|
||||
font-size: 14px;
|
||||
color: #888ea8;
|
||||
padding-left: 31px;
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
.form-form .form-form-wrap form .field-wrapper.terms_condition a {
|
||||
color: #1b55e2;
|
||||
}
|
||||
|
||||
.form-form .form-form-wrap form .field-wrapper svg.feather-user {
|
||||
top: 53px;
|
||||
}
|
||||
|
||||
.form-form .form-form-wrap form .field-wrapper svg.feather-lock {
|
||||
top: 48px;
|
||||
}
|
||||
|
||||
.form-form .form-form-wrap form .field-wrapper svg.feather-at-sign {
|
||||
top: 47px;
|
||||
}
|
||||
|
||||
.form-form .form-form-wrap form .field-wrapper svg.feather-at-sign.register {
|
||||
top: 53px;
|
||||
}
|
||||
|
||||
.form-form .form-form-wrap form .field-wrapper svg:not(.feather-eye) {
|
||||
position: absolute;
|
||||
left: 12px;
|
||||
color: #888ea8;
|
||||
fill: rgba(0, 23, 55, 0.08);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.form-form .form-form-wrap form .field-wrapper svg.feather-eye {
|
||||
position: absolute;
|
||||
top: 46px;
|
||||
right: 13px;
|
||||
color: #888ea8;
|
||||
fill: rgba(0, 23, 55, 0.08);
|
||||
width: 17px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.form-form .form-form-wrap form .field-wrapper input {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
border-radius: 6px;
|
||||
min-width: 50px;
|
||||
max-width: 635px;
|
||||
width: 100%;
|
||||
-ms-transition: all 0.2s ease-in-out 0s;
|
||||
transition: all 0.2s ease-in-out 0s;
|
||||
color: #3b3f5c;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
padding: 13px 35px 13px 46px;
|
||||
}
|
||||
|
||||
.form-form .form-form-wrap form .field-wrapper input::-webkit-input-placeholder {
|
||||
color: #d3d3d3;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.form-form .form-form-wrap form .field-wrapper input::-ms-input-placeholder {
|
||||
color: #d3d3d3;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.form-form .form-form-wrap form .field-wrapper input::-moz-placeholder {
|
||||
color: #d3d3d3;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.form-form .form-form-wrap form .field-wrapper {
|
||||
width: 100%
|
||||
}
|
||||
|
||||
.form-form .form-form-wrap form .field-wrapper button.btn {
|
||||
align-self: center;
|
||||
width: 100%;
|
||||
padding: 11px 14px;
|
||||
font-size: 16px;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
.form-form .form-form-wrap form .field-wrapper a.forgot-pass-link {
|
||||
font-weight: 600;
|
||||
color: #888ea8;
|
||||
display: block;
|
||||
letter-spacing: 1px;
|
||||
font-size: 12px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.form-form .form-form-wrap form .field-wrapper .n-chk .new-control-indicator {
|
||||
top: 2px;
|
||||
border: 1px solid #bfc9d4;
|
||||
background-color: #f1f2f3;
|
||||
}
|
||||
|
||||
.form-form .form-form-wrap form .field-wrapper .n-chk .new-control-indicator:after {
|
||||
top: 54%;
|
||||
}
|
||||
|
||||
.new-control.new-checkbox.checkbox-primary>input:checked~span.new-control-indicator {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.form-form .form-form-wrap form .division {
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
margin: 35px 0 38px 0;
|
||||
}
|
||||
|
||||
.form-form .form-form-wrap form .social {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.form-form .form-form-wrap form .social a {
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
border: 1px solid #e0e6ed;
|
||||
padding: 12px 10px;
|
||||
width: 181px;
|
||||
}
|
||||
|
||||
.form-form .form-form-wrap form .social a.social-fb {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.form-form .form-form-wrap form .social a.social-fb svg {
|
||||
color: #1b55e2;
|
||||
}
|
||||
|
||||
.form-form .form-form-wrap form .social a.social-fb .brand-name {
|
||||
color: #1b55e2;
|
||||
}
|
||||
|
||||
@media (max-width: 991px) {
|
||||
.form-form {
|
||||
width: 100%;
|
||||
}
|
||||
.form-form .form-form-wrap {
|
||||
min-width: auto;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.form-form .form-container .form-content {
|
||||
border-radius: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 575px) {
|
||||
.form-form .form-form-wrap form .field-wrapper.toggle-pass {
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
.form-form .form-form-wrap form .social a.social-fb {
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
margin-bottom: 10px;
|
||||
display: block;
|
||||
}
|
||||
.form-form .form-form-wrap form .social a.social-github {
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
margin-bottom: 10px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.form-form .form-container .form-content {
|
||||
border: none;
|
||||
border-radius: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
|
||||
.form-form .form-form-wrap {
|
||||
width: 100%;
|
||||
}
|
||||
.form-form .form-container {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@supports (-ms-ime-align: auto) {
|
||||
.form-form .form-form-wrap {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
transition: all 0.3s ease-out;
|
||||
-webkit-transition: all 0.3s ease-out;
|
||||
}
|
||||
|
||||
.main-container {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.main-container .background {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.main-container .background path {
|
||||
opacity: .1;
|
||||
fill: #e5edff;
|
||||
border-color: #e2e8f0;
|
||||
}
|
||||
|
||||
.bg-100 {
|
||||
background-image: url('../../../images/bk1.jpg');
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
*, ::after, ::before {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.wrapper {
|
||||
width: 100%;
|
||||
height:100%
|
||||
}
|
||||
|
||||
body {
|
||||
font-size: 14px;
|
||||
color: #525f7f;
|
||||
background-color: #f8f9fe;
|
||||
letter-spacing: 0.5px;
|
||||
overflow-x: hidden;
|
||||
font-family: "Roboto",sans-serif;
|
||||
}
|
||||
|
||||
.authentication-header {
|
||||
position: absolute;
|
||||
background: #165078;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
height: 50%;
|
||||
}
|
||||
.d-flex {
|
||||
display: flex !important;
|
||||
}
|
||||
.justify-content-center {
|
||||
justify-content: center !important;
|
||||
}
|
||||
.align-items-center {
|
||||
align-items: center !important;
|
||||
}
|
||||
|
||||
.section-authentication-sign-in {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-block;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
color: #212529;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
background-color: transparent;
|
||||
border: 1px solid transparent;
|
||||
padding: .375rem .75rem;
|
||||
font-size: 1rem;
|
||||
border-radius: .25rem;
|
||||
transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;
|
||||
}
|
||||
.btn-primary {
|
||||
color: #fff;
|
||||
background-color: #0d6efd;
|
||||
border-color: #0d6efd;
|
||||
}
|
||||
.my-4 {
|
||||
margin-top: 1.5rem !important;
|
||||
margin-bottom: 1.5rem !important;
|
||||
}
|
||||
.btn {
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
.btn-primary {
|
||||
color: #fff;
|
||||
background-color: #8833ff;
|
||||
border-color: #8833ff;
|
||||
}
|
||||
[type=button]:not(:disabled), [type=reset]:not(:disabled), [type=submit]:not(:disabled), button:not(:disabled) {
|
||||
cursor: pointer;
|
||||
}
|
||||
.bx {
|
||||
font-family: "boxicons" !important;
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-variant: normal;
|
||||
line-height: inherit;
|
||||
display: inline-block;
|
||||
text-transform: none;
|
||||
speak: none;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
.btn i {
|
||||
vertical-align: middle;
|
||||
font-size: 1.3rem;
|
||||
margin-top: -1em;
|
||||
margin-bottom: -1em;
|
||||
margin-right: 5px;
|
||||
}
|
||||
.form-check {
|
||||
display: block;
|
||||
min-height: 1.5rem;
|
||||
padding-left: 1.5em;
|
||||
margin-bottom: 0.12rem;
|
||||
}
|
||||
.form-switch {
|
||||
padding-left: 2.5em;
|
||||
}
|
||||
.form-check-input {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
margin-top: 0.25em;
|
||||
vertical-align: top;
|
||||
background-color: #fff;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: contain;
|
||||
border: 1px solid rgba(0,0,0,.25);
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
-webkit-print-color-adjust: exact;
|
||||
color-adjust: exact;
|
||||
}
|
||||
.form-check .form-check-input {
|
||||
float: left;
|
||||
margin-left: -1.5em;
|
||||
}
|
||||
[type=checkbox].form-check-input {
|
||||
border-radius: .25em;
|
||||
}
|
||||
:checked.form-check-input {
|
||||
background-color: #0d6efd;
|
||||
border-color: #0d6efd;
|
||||
}
|
||||
.form-switch .form-check-input {
|
||||
width: 2em;
|
||||
margin-left: -2.5em;
|
||||
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");
|
||||
background-position: left center;
|
||||
border-radius: 2em;
|
||||
transition: background-position .15s ease-in-out;
|
||||
}
|
||||
:checked.form-check-input {
|
||||
background-color: #8833ff;
|
||||
border-color: #8833ff;
|
||||
}
|
||||
:checked[type=checkbox].form-check-input {
|
||||
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e");
|
||||
}
|
||||
.form-switch :checked.form-check-input {
|
||||
background-position: right center;
|
||||
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e");
|
||||
}
|
||||
label {
|
||||
display: inline-block;
|
||||
font-weight: 400 !important;
|
||||
}
|
||||
.form-label {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.input-group {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: stretch;
|
||||
width: 100%;
|
||||
}
|
||||
.form-control {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: .375rem .75rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
color: #212529;
|
||||
background-color: #fff;
|
||||
background-clip: padding-box;
|
||||
border: 1px solid #ced4da;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
border-radius: .25rem;
|
||||
transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out;
|
||||
}
|
||||
.border-end-0 {
|
||||
border-right: 0 !important;
|
||||
}
|
||||
.input-group > .form-control, .input-group > .form-select {
|
||||
position: relative;
|
||||
flex: 1 1 auto;
|
||||
width: 1%;
|
||||
min-width: 0px;
|
||||
}
|
||||
:not(.has-validation).input-group > :nth-last-child(n+3).dropdown-toggle, :not(.has-validation).input-group > :not(:last-child):not(.dropdown-menu):not(.dropdown-toggle) {
|
||||
border-top-right-radius: 0px;
|
||||
border-bottom-right-radius: 0px;
|
||||
}
|
||||
.input-group-text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: .375rem .75rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
color: #212529;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
background-color: #e9ecef;
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: .25rem;
|
||||
}
|
||||
.bg-transparent {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
.input-group > :not(:first-child):not(.invalid-feedback):not(.invalid-tooltip):not(.valid-feedback):not(.valid-tooltip):not(.dropdown-menu) {
|
||||
margin-left: -1px;
|
||||
border-top-left-radius: 0px;
|
||||
border-bottom-left-radius: 0px;
|
||||
}
|
||||
.login-separater span {
|
||||
position: relative;
|
||||
top: 26px;
|
||||
margin-top: -10px;
|
||||
background: #ffffff;
|
||||
padding: 5px;
|
||||
font-size: 12px;
|
||||
color: #cbcbcb;
|
||||
z-index: 1;
|
||||
}
|
||||
hr {
|
||||
margin: 1rem 0;
|
||||
color: inherit;
|
||||
background-color: currentColor;
|
||||
border: 0;
|
||||
opacity: 0.25;
|
||||
}
|
||||
hr:not([size]) {
|
||||
height: 1px;
|
||||
}
|
||||
.shadow-sm {
|
||||
box-shadow: 0px 0.12rem 0.25rem rgba(0,0,0,0.075) !important;
|
||||
}
|
||||
.btn-white {
|
||||
background-color: #fff;
|
||||
border-color: #e7eaf3;
|
||||
}
|
||||
|
||||
img, svg {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.me-2 {
|
||||
margin-right: 0.5rem !important;
|
||||
}
|
||||
|
||||
.h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 {
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.h3, h3 {
|
||||
font-size: calc(1.3rem + 0.6vw);
|
||||
}
|
||||
|
||||
/* @media all and (min-width:1200px) */
|
||||
.h3, h3 {
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
|
||||
.h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 {
|
||||
color: #32393f;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0px;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
box-shadow: 1px -1px 20px 1px #403c4329;
|
||||
}
|
||||
|
After Width: | Height: | Size: 135 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 264 KiB |
@@ -0,0 +1,441 @@
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system,BlinkMacSystemFont,"Poppins",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji" !important;
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
color: #212529;
|
||||
text-align: left;
|
||||
background-color: #fff;
|
||||
}
|
||||
body {
|
||||
font-family: Nunito,sans-serif;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 400;
|
||||
color: #3a3a3a;
|
||||
background: #f8f8f8;
|
||||
}
|
||||
body {
|
||||
min-height: calc(-150px + 100%);
|
||||
position: relative;
|
||||
padding-bottom: 90px;
|
||||
}
|
||||
|
||||
body {
|
||||
overflow-x: hidden !important;
|
||||
}
|
||||
|
||||
.rounded {
|
||||
border-radius: .25rem !important;
|
||||
}
|
||||
|
||||
.rounded {
|
||||
border-radius: 50px !important;
|
||||
}
|
||||
|
||||
body.no-footer {
|
||||
padding-bottom: initial;
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: sans-serif;
|
||||
line-height: 1.15;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
html {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #f8f8f8;
|
||||
}
|
||||
html {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
article, aside, figcaption, figure, footer, header, hgroup, main, nav, section {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.bootstrap-tagsinput.focus, .form-control:focus {
|
||||
border-color: rgba(237,113,23,.6);
|
||||
}
|
||||
|
||||
.has-float-label label::after, .has-float-label>span::after {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.has-float-label label::after, .has-float-label > span::after {
|
||||
content: " ";
|
||||
display: block;
|
||||
position: absolute;
|
||||
height: 5px;
|
||||
top: 3px;
|
||||
left: -0.2em;
|
||||
right: -0.2em;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.bootstrap-tagsinput, .form-control {
|
||||
border-radius: .1rem;
|
||||
outline: initial!important;
|
||||
box-shadow: initial!important;
|
||||
font-size: .8rem;
|
||||
padding: .5rem .75rem;
|
||||
line-height: 1;
|
||||
border: 1px solid #d7d7d7;
|
||||
background: #fff;
|
||||
color: #3a3a3a;
|
||||
border-color: #d7d7d7;
|
||||
height: calc(2em + .8rem);
|
||||
}
|
||||
|
||||
.fixed-background {
|
||||
background: url(./images/bk.jpg) no-repeat center center fixed;
|
||||
background-size: cover;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
main.default-transition {
|
||||
transition: margin-left .3s;
|
||||
}
|
||||
|
||||
main {
|
||||
margin: 0 !important;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
main .container {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.background .mx-auto {
|
||||
margin-left: auto !important;
|
||||
margin-right: auto !important;
|
||||
}
|
||||
|
||||
.card {
|
||||
position: relative;
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-box-direction: normal;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
min-width: 0px;
|
||||
word-wrap: break-word;
|
||||
background-color: #fff;
|
||||
background-clip: border-box;
|
||||
border: 1px solid rgba(0,0,0,0.125);
|
||||
border-radius: .25rem;
|
||||
}
|
||||
|
||||
.card {
|
||||
border: initial;
|
||||
background: #fff;
|
||||
border-radius: .1rem;
|
||||
box-shadow: 0px 1px 15px rgba(0,0,0,0.04), 0px 1px 6px rgba(0,0,0,0.04);
|
||||
}
|
||||
|
||||
.auth-card {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
box-shadow: 0px 1px 15px rgba(0,0,0,0.04), 0px 1px 6px rgba(0,0,0,0.04);
|
||||
}
|
||||
|
||||
.rounded .card {
|
||||
border-radius: .75rem;
|
||||
}
|
||||
|
||||
.rounded .card.auth-card {
|
||||
border-top-left-radius: 1.25rem;
|
||||
border-bottom-left-radius: 1.25rem;
|
||||
}
|
||||
|
||||
.position-relative {
|
||||
position: relative !important;
|
||||
}
|
||||
|
||||
.auth-card .image-side {
|
||||
position: absolute;
|
||||
width: 40%;
|
||||
background: url(./images/aside_bk.png) no-repeat center top;
|
||||
background-size: cover;
|
||||
padding: 80px 40px;
|
||||
}
|
||||
|
||||
.image-side::before {
|
||||
content: '';
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background: rgba(77, 76, 178, 0.9);
|
||||
position: absolute;
|
||||
left: 0;
|
||||
z-index: 0;
|
||||
top:0px
|
||||
}
|
||||
|
||||
.text-layer {
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
top: 0px;
|
||||
background-size: cover;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
padding: 80px 40px
|
||||
}
|
||||
|
||||
.rounded .auth-card .image-side {
|
||||
border-top-left-radius: 0.75rem;
|
||||
border-bottom-left-radius: 0.75rem;
|
||||
}
|
||||
.auth-card .form-side {
|
||||
width: 60%;
|
||||
padding: 80px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #007bff;
|
||||
text-decoration: none;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #3a3a3a;
|
||||
transition: color .2s;
|
||||
outline: initial !important;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
p {
|
||||
margin-top: 0px;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
p {
|
||||
font-size: 0.85rem;
|
||||
line-height: 1.3rem;
|
||||
font-family: Nunito,sans-serif;
|
||||
}
|
||||
h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 {
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
}
|
||||
h6, .h6 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
.text-white {
|
||||
color: #fff !important;
|
||||
}
|
||||
.h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 {
|
||||
line-height: 1.3;
|
||||
}
|
||||
h6 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
label {
|
||||
display: inline-block;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.form-group {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.mb-4, .my-4 {
|
||||
margin-bottom: 1.5rem !important;
|
||||
}
|
||||
.has-float-label {
|
||||
display: block;
|
||||
position: relative;
|
||||
}
|
||||
.d-flex {
|
||||
display: -webkit-box !important;
|
||||
display: -ms-flexbox !important;
|
||||
display: flex !important;
|
||||
}
|
||||
.justify-content-between {
|
||||
-webkit-box-pack: justify !important;
|
||||
-ms-flex-pack: justify !important;
|
||||
justify-content: space-between !important;
|
||||
}
|
||||
.align-items-center {
|
||||
-webkit-box-align: center !important;
|
||||
-ms-flex-align: center !important;
|
||||
align-items: center !important;
|
||||
}
|
||||
button {
|
||||
border-radius: 0;
|
||||
}
|
||||
input, button, select, optgroup, textarea {
|
||||
margin: 0;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
button, input {
|
||||
overflow: visible;
|
||||
}
|
||||
button, select {
|
||||
text-transform: none;
|
||||
}
|
||||
button {
|
||||
color: #3a3a3a;
|
||||
outline: initial !important;
|
||||
}
|
||||
button, [type='button'], [type='reset'], [type='submit'] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
button {
|
||||
color: #3a3a3a;
|
||||
outline: initial !important;
|
||||
}
|
||||
.btn {
|
||||
display: inline-block;
|
||||
font-weight: 400;
|
||||
color: #212529;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
background-color: transparent;
|
||||
border: 1px solid transparent;
|
||||
padding: .375rem .75rem;
|
||||
font-size: 1rem;
|
||||
line-height: 1.5;
|
||||
border-radius: .25rem;
|
||||
-webkit-transition: color 0.15s ease-in-out,background-color 0.15s ease-in-out,border-color 0.15s ease-in-out,-webkit-box-shadow 0.15s ease-in-out;
|
||||
transition: color 0.15s ease-in-out,background-color 0.15s ease-in-out,border-color 0.15s ease-in-out,-webkit-box-shadow 0.15s ease-in-out;
|
||||
transition: color 0.15s ease-in-out,background-color 0.15s ease-in-out,border-color 0.15s ease-in-out,box-shadow 0.15s ease-in-out;
|
||||
transition: color 0.15s ease-in-out,background-color 0.15s ease-in-out,border-color 0.15s ease-in-out,box-shadow 0.15s ease-in-out,-webkit-box-shadow 0.15s ease-in-out;
|
||||
}
|
||||
.btn-primary {
|
||||
color: #fff;
|
||||
background-color: #007bff;
|
||||
border-color: #007bff;
|
||||
}
|
||||
.btn-lg, .btn-group-lg > .btn {
|
||||
padding: .5rem 1rem;
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.5;
|
||||
border-radius: .3rem;
|
||||
}
|
||||
.btn {
|
||||
border-radius: 50px;
|
||||
outline: initial !important;
|
||||
box-shadow: none !important;
|
||||
box-shadow: initial !important;
|
||||
font-size: 0.8rem;
|
||||
padding: .5rem 1.25rem .5rem 1.25rem;
|
||||
transition: background-color box-shadow .1s linear;
|
||||
}
|
||||
.btn-shadow {
|
||||
box-shadow: 0px 1px 2px 0px rgba(0,0,0,0.15), 0px 1px 3px 1px rgba(0,0,0,0.15) !important;
|
||||
transition: background-color box-shadow .1s linear;
|
||||
}
|
||||
.btn-group-lg > .btn, .btn-group-sm > .btn, .btn-lg {
|
||||
border-radius: 50px;
|
||||
}
|
||||
.btn-primary {
|
||||
background-color: #00365a;
|
||||
border-color: #00365a;
|
||||
color: #fff;
|
||||
}
|
||||
.btn-group-lg > .btn, .btn-lg {
|
||||
line-height: 1.5;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.05rem;
|
||||
padding: .75rem 2.6rem .6rem 2.6rem;
|
||||
}
|
||||
button:not(:disabled), [type='button']:not(:disabled), [type='reset']:not(:disabled), [type='submit']:not(:disabled) {
|
||||
cursor: pointer;
|
||||
}
|
||||
.form-control {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: calc(1.5em + 2px + 0.75rem);
|
||||
padding: .375rem .75rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
color: #495057;
|
||||
background-color: #fff;
|
||||
background-clip: padding-box;
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: .25rem;
|
||||
-webkit-transition: border-color 0.15s ease-in-out,-webkit-box-shadow 0.15s ease-in-out;
|
||||
transition: border-color 0.15s ease-in-out,-webkit-box-shadow 0.15s ease-in-out;
|
||||
transition: border-color 0.15s ease-in-out,box-shadow 0.15s ease-in-out;
|
||||
transition: border-color 0.15s ease-in-out,box-shadow 0.15s ease-in-out,-webkit-box-shadow 0.15s ease-in-out;
|
||||
}
|
||||
.bootstrap-tagsinput, .form-control {
|
||||
border-radius: .1rem;
|
||||
outline: initial !important;
|
||||
box-shadow: initial !important;
|
||||
font-size: 0.8rem;
|
||||
padding: .5rem .75rem;
|
||||
line-height: 1;
|
||||
border: 1px solid #d7d7d7;
|
||||
background: #fff;
|
||||
color: #3a3a3a;
|
||||
border-color: #d7d7d7;
|
||||
height: calc(2em + 0.8rem);
|
||||
}
|
||||
.has-float-label label, .has-float-label > span, .has-top-label label, .has-top-label > span {
|
||||
color: rgba(58, 58, 58, 0.7);
|
||||
}
|
||||
.has-float-label label, .has-float-label > span:last-of-type {
|
||||
position: absolute;
|
||||
cursor: text;
|
||||
font-size: 90%;
|
||||
opacity: 1;
|
||||
top: -0.4em;
|
||||
left: 0.75rem;
|
||||
z-index: 3;
|
||||
line-height: 1;
|
||||
padding: 0 1px;
|
||||
}
|
||||
.logo-single {
|
||||
width: 110px;
|
||||
height: 35px;
|
||||
background: url(../logos/black.svg) no-repeat;
|
||||
background-position: center center;
|
||||
display: inline-block;
|
||||
margin-bottom: 60px;
|
||||
}
|
||||
h2, .h2 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
.text-white {
|
||||
color: #fff !important;
|
||||
}
|
||||
.mb-0, .my-0 {
|
||||
margin-bottom: 0px !important;
|
||||
}
|
||||
.white {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
@media all and (max-width:768px) {
|
||||
.auth-card .form-side {
|
||||
width: 100%;
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
.auth-card .image-side {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
|
||||
<svg id="Capa_1" enable-background="new 0 0 510 510" height="512" viewBox="0 0 510 510" width="512" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><linearGradient id="lg1"><stop offset="0" stop-color="#7faef4"/><stop offset="1" stop-color="#4c8df1"/></linearGradient><linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="72.017" x2="448.659" xlink:href="#lg1" y1="30.771" y2="407.412"/><linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="393.324" x2="118.808" xlink:href="#lg1" y1="393.324" y2="118.808"/><linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="220.253" x2="342.575" y1="220.253" y2="342.575"><stop offset="0" stop-color="#4166bf"/><stop offset="1" stop-color="#4256ac"/></linearGradient><linearGradient id="SVGID_4_" gradientUnits="userSpaceOnUse" x1="368.352" x2="227.221" y1="309.634" y2="168.502"><stop offset="0" stop-color="#4256ac" stop-opacity="0"/><stop offset="1" stop-color="#1b1464"/></linearGradient><linearGradient id="lg2"><stop offset="0" stop-color="#ebeff0"/><stop offset="1" stop-color="#e3e5e4"/></linearGradient><linearGradient id="SVGID_5_" gradientUnits="userSpaceOnUse" x1="51.502" x2="399.05" xlink:href="#lg2" y1="-7.217" y2="340.331"/><linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="232.637" x2="276.114" xlink:href="#lg2" y1="303.885" y2="381.967"/><g><path d="m239.907 2.943c-38.555 15.625-127.875 53.362-171.842 62.404-9.715 1.998-16.648 10.61-16.648 20.528v127.182c0 99.733 44.909 194.164 122.273 257.105l23.878 19.426c33.451 27.215 81.411 27.215 114.863 0l23.878-19.426c77.364-62.941 122.273-157.376 122.273-257.109v-127.178c0-9.918-6.933-18.53-16.648-20.528-43.967-9.042-133.287-46.779-171.841-62.404-9.683-3.924-20.503-3.924-30.186 0z" fill="url(#SVGID_1_)"/><g><circle cx="255" cy="255" fill="url(#SVGID_2_)" r="169.321"/><path d="m405.238 255c0 45.321-20.067 85.954-51.801 113.5-26.365 22.886-60.783 22.738-98.437 22.738-37.275 0-71.379.426-97.639-22.05-32.195-27.555-52.599-68.489-52.599-114.188 0-82.974 67.264-150.238 150.238-150.238s150.238 67.264 150.238 150.238z" fill="url(#SVGID_3_)"/><path d="m255.332 127.937c-38.909 0-68.676 29.767-68.676 68.676 0 20.788 7.232 39.467 21.552 52.363l121.193 121.193c5.079-3.425 19.423 2.333 24.035-1.67 31.734-27.546 51.802-68.178 51.802-113.499 0-2.749-.079-5.48-.225-8.193l-97.318-97.318c-12.896-14.32-31.575-21.552-52.363-21.552z" fill="url(#SVGID_4_)"/><circle cx="255" cy="196.281" fill="url(#SVGID_5_)" r="70.452"/><path d="m348.476 315.4c19.076 12.239 20.97 39.524 3.686 54.185-.199.169-.398.337-.598.505-26.113 21.943-59.795 35.145-96.564 35.145-37.133 0-71.107-13.469-97.327-35.793-.102-.087-.204-.174-.306-.261-17.185-14.694-15.152-41.941 3.917-54.088 26.972-17.181 59.001-27.131 93.351-27.131 34.573 0 66.77 10.069 93.841 27.438z" fill="url(#SVGID_6_)"/></g></g></svg>
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
@@ -0,0 +1,4 @@
|
||||
@mixin box-shadow($shadow...) {
|
||||
box-shadow: $shadow;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
|
||||
@mixin button-variant( $background, $border: $background, $hover-background: darken($background, 5%), $hover-border: $hover-background, $hover-box-shadow: 0 4px 10px rgba($background, .25), $active-background: darken($background, 5%), $active-border: darken($border, 10%), $active-box-shadow: inset 0 3px 5px rgba($black, .125)) {
|
||||
color: color-yiq($background);
|
||||
border-color: $border;
|
||||
|
||||
// @include gradient-bg($background);
|
||||
@include box-shadow($btn-box-shadow);
|
||||
|
||||
@include hover {
|
||||
color: color-yiq($hover-background);
|
||||
//@include gradient-bg($hover-background);
|
||||
border-color: $hover-border;
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05), $hover-box-shadow;
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&.focus {
|
||||
box-shadow: 0 0 0 3px rgba($background, .15), 0 3px 15px rgba($background, 0.2), 0 2px 5px rgba($black, .1);
|
||||
}
|
||||
|
||||
// Disabled comes first so active can properly restyle
|
||||
&.disabled,
|
||||
&:disabled {
|
||||
background-color: $background;
|
||||
border-color: $border;
|
||||
box-shadow: none;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
&:not(:disabled):not(.disabled):active,
|
||||
&:not(:disabled):not(.disabled).active,
|
||||
.show>&.dropdown-toggle {
|
||||
color: color-yiq($active-background);
|
||||
background-color: $active-background;
|
||||
border-color: $active-border;
|
||||
background-image: none;
|
||||
//box-shadow: $active-box-shadow !important;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin button-outline-variant( $color, $color-hover: color-yiq($color), $active-background: $color, $active-border: $color) {
|
||||
$button-text-color: $color; // Fix light or white outlined buttons
|
||||
|
||||
@if ($color==theme-color("light") or $color==$white) {
|
||||
$button-text-color: color-yiq($color);
|
||||
}
|
||||
|
||||
background-color: transparent;
|
||||
background-image: none;
|
||||
border-color: $color;
|
||||
color: $button-text-color;
|
||||
|
||||
&:hover {
|
||||
color: color-yiq($color);
|
||||
background-color: $active-background;
|
||||
border-color: $active-border;
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05), 0 4px 10px rgba($color, .25);
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&.focus {
|
||||
box-shadow: 0 0 0 3px rgba($color, .15), 0 3px 15px rgba($color, 0.2), 0 2px 5px rgba($black, .1) !important;
|
||||
}
|
||||
|
||||
&.disabled,
|
||||
&:disabled {
|
||||
color: $color;
|
||||
background-color: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&:not(:disabled):not(.disabled):active,
|
||||
&:not(:disabled):not(.disabled).active,
|
||||
.show>&.dropdown-toggle {
|
||||
color: color-yiq($active-background);
|
||||
background-color: $active-background;
|
||||
border-color: $active-border;
|
||||
// &:focus {
|
||||
// // Avoid using mixin so we can pass custom focus shadow properly
|
||||
// // box-shadow: inset 0 3px 5px rgba($black, .125) !important;
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
// Button sizes
|
||||
@mixin button-size( $padding-y, $padding-x, $font-size, $line-height, $border-radius) {
|
||||
padding: $padding-y $padding-x;
|
||||
font-size: $font-size;
|
||||
line-height: $line-height;
|
||||
border-radius: $border-radius;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
@mixin clearfix() {
|
||||
&::after {
|
||||
display: block;
|
||||
clear: both;
|
||||
content: "";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
|
||||
@mixin awesome-flex {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -moz-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@mixin awesome-align-items-center {
|
||||
-webkit-box-align: center;
|
||||
-webkit-align-items: center;
|
||||
-moz-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@mixin awesome-justify-content-center {
|
||||
-webkit-box-pack: center;
|
||||
-webkit-justify-content: center;
|
||||
-moz-box-pack: center;
|
||||
-ms-flex-pack: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
@mixin awesome-justify-content-between {
|
||||
-webkit-box-pack: justify;
|
||||
-webkit-justify-content: space-between;
|
||||
-moz-box-pack: justify;
|
||||
-ms-flex-pack: justify;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
@mixin awesome-flex-row {
|
||||
-webkit-box-orient: horizontal;
|
||||
-webkit-box-direction: normal;
|
||||
-webkit-flex-direction: row;
|
||||
-moz-box-orient: horizontal;
|
||||
-moz-box-direction: normal;
|
||||
-ms-flex-direction: row;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
@mixin awesome-flex-column {
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-box-direction: normal;
|
||||
-webkit-flex-direction: column;
|
||||
-moz-box-orient: vertical;
|
||||
-moz-box-direction: normal;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
@mixin gradient-bg($color) {
|
||||
background: $color linear-gradient(180deg, mix($body-bg, $color, 15%), $color) repeat-x;
|
||||
}
|
||||
|
||||
@mixin box-shadow($shadow...) {
|
||||
box-shadow: $shadow;
|
||||
}
|
||||
|
||||
@mixin border-radius($radius: $border-radius) {
|
||||
-webkit-border-radius: $radius;
|
||||
-moz-border-radius: $radius;
|
||||
border-radius: $radius;
|
||||
}
|
||||
|
||||
// @mixin awesome-trainsition-all ($seconds) {
|
||||
// -webkit-transition: all $seconds;
|
||||
// -moz-transition: all $seconds;
|
||||
// -ms-transition: all $seconds;
|
||||
// -o-transition: all $seconds;
|
||||
// transition: all $seconds;
|
||||
// }
|
||||
@@ -0,0 +1,10 @@
|
||||
@mixin float-left {
|
||||
float: left !important;
|
||||
}
|
||||
@mixin float-right {
|
||||
float: right !important;
|
||||
}
|
||||
@mixin float-none {
|
||||
float: none !important;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
// Gradients
|
||||
|
||||
@mixin gradient-bg($color) {
|
||||
background: $color linear-gradient(180deg, mix($body-bg, $color, 15%), $color) repeat-x;
|
||||
}
|
||||
|
||||
// Horizontal gradient, from left to right
|
||||
//
|
||||
// Creates two color stops, start and end, by specifying a color and position for each color stop.
|
||||
@mixin gradient-x($start-color: #555, $end-color: #333, $start-percent: 0%, $end-percent: 100%) {
|
||||
background-image: linear-gradient(to right, $start-color $start-percent, $end-color $end-percent);
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
|
||||
// Vertical gradient, from top to bottom
|
||||
//
|
||||
// Creates two color stops, start and end, by specifying a color and position for each color stop.
|
||||
@mixin gradient-y($start-color: #555, $end-color: #333, $start-percent: 0%, $end-percent: 100%) {
|
||||
background-image: linear-gradient(to bottom, $start-color $start-percent, $end-color $end-percent);
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
|
||||
@mixin gradient-directional($start-color: #555, $end-color: #333, $deg: 45deg) {
|
||||
background-image: linear-gradient($deg, $start-color, $end-color);
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
@mixin gradient-x-three-colors($start-color: #00b3ee, $mid-color: #7a43b6, $color-stop: 50%, $end-color: #c3325f) {
|
||||
background-image: linear-gradient(to right, $start-color, $mid-color $color-stop, $end-color);
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
@mixin gradient-y-three-colors($start-color: #00b3ee, $mid-color: #7a43b6, $color-stop: 50%, $end-color: #c3325f) {
|
||||
background-image: linear-gradient($start-color, $mid-color $color-stop, $end-color);
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
@mixin gradient-radial($inner-color: #555, $outer-color: #333) {
|
||||
background-image: radial-gradient(circle, $inner-color, $outer-color);
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
@mixin gradient-striped($color: rgba(255,255,255,.15), $angle: 45deg) {
|
||||
background-image: linear-gradient($angle, $color 25%, transparent 25%, transparent 50%, $color 50%, $color 75%, transparent 75%, transparent);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
@mixin hover {
|
||||
&:hover { @content; }
|
||||
}
|
||||
|
||||
@mixin hover-focus {
|
||||
&:hover,
|
||||
&:focus {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin plain-hover-focus {
|
||||
&,
|
||||
&:hover,
|
||||
&:focus {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin hover-focus-active {
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 7.1 MiB |
|
After Width: | Height: | Size: 21 MiB |
|
After Width: | Height: | Size: 101 KiB |
|
After Width: | Height: | Size: 8.6 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 67 KiB |
@@ -0,0 +1,81 @@
|
||||
<div class="container">
|
||||
|
||||
<div class="oe_styling_v8">
|
||||
<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">
|
||||
|
||||
<div class="row">
|
||||
<p style="width: 100%; font-size: 18px; text-indent:1em;" class="px-sm-4 p-2">
|
||||
This module is base of our themes, it contains all the common features of our themes.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 class="w-100 text-center mb-3">(Current Realse V16.0.0.2 -- 2023.1.5)</h2>
|
||||
|
||||
<div class="shadow row my-5 p-3 pb-2 overflow-hidden"
|
||||
style="background-color:#fbfcfd; border-radius: 5px; border: 1px solid #d2caca66;">
|
||||
<img src="./banner.png" style="width:100%; " />
|
||||
</div>
|
||||
|
||||
<h2>
|
||||
Our Other Products
|
||||
</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="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 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>Admin Theme</b>
|
||||
</h3>
|
||||
<hr class="mb-4 mt-0">
|
||||
<a href="https://apps.odoo.com/apps/modules/15.0/anita_list/">
|
||||
<img src="./admin_theme.gif" class="img img-fluid mt-3" loading="lazy">
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
After Width: | Height: | Size: 527 KiB |
|
After Width: | Height: | Size: 377 KiB |
|
After Width: | Height: | Size: 264 KiB |
@@ -0,0 +1,36 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { Component, useState, useExternalListener} from "@odoo/owl";
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { Dropdown } from "@web/core/dropdown/dropdown";
|
||||
import { DropdownItem } from "@web/core/dropdown/dropdown_item";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
|
||||
export class ThemeCustomizer extends Component {
|
||||
setup() {
|
||||
super.setup();
|
||||
this.actionService = useService("action");
|
||||
}
|
||||
|
||||
_on_toggle_click() {
|
||||
// url action
|
||||
this.actionService.doAction({
|
||||
type: "ir.actions.act_url",
|
||||
url: "https://apps.odoo.com/apps/themes/15.0/anita_theme_setting/",
|
||||
target: "current",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ThemeCustomizer.template = "anita_theme_base.customizer";
|
||||
|
||||
ThemeCustomizer.components = {
|
||||
Dropdown: Dropdown,
|
||||
DropdownItem: DropdownItem,
|
||||
};
|
||||
|
||||
export const systrayItem = {
|
||||
Component: ThemeCustomizer,
|
||||
};
|
||||
registry.category("systray").add("anita_theme_base.customizer", systrayItem, { sequence: 1 });
|
||||
@@ -0,0 +1,372 @@
|
||||
.customizer {
|
||||
|
||||
width: 395px;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.customizer-content {
|
||||
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 {
|
||||
.admin_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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<!-- style setting -->
|
||||
<div t-name="anita_theme_base.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"/>
|
||||
</a>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import controllers
|
||||
from . import models
|
||||
@@ -0,0 +1,96 @@
|
||||
# -*- 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'),
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import anita_home
|
||||
@@ -0,0 +1,87 @@
|
||||
# -*- 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
|
||||
@@ -0,0 +1,12 @@
|
||||
|
||||
<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>
|
||||
@@ -0,0 +1,21 @@
|
||||
# -*- 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
|
||||
@@ -0,0 +1,14 @@
|
||||
# -*- 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)
|
||||
@@ -0,0 +1,130 @@
|
||||
# -*- 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)
|
||||
@@ -0,0 +1,147 @@
|
||||
# -*- 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
|
||||
@@ -0,0 +1,53 @@
|
||||
# -*- 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)])
|
||||
@@ -0,0 +1,13 @@
|
||||
# -*- 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)
|
||||
@@ -0,0 +1,244 @@
|
||||
# -*- 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
|
||||
@@ -0,0 +1,107 @@
|
||||
# -*- 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
|
||||
})
|
||||
@@ -0,0 +1,109 @@
|
||||
# -*- 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
|
||||
@@ -0,0 +1,133 @@
|
||||
# -*- 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'
|
||||
@@ -0,0 +1,314 @@
|
||||
# -*- 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()
|
||||
@@ -0,0 +1,21 @@
|
||||
# -*- 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")
|
||||
|
||||
@@ -0,0 +1,588 @@
|
||||
# -*- 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": []
|
||||
})
|
||||
@@ -0,0 +1,506 @@
|
||||
# -*- 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
|
||||
@@ -0,0 +1,332 @@
|
||||
# -*- 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
|
||||
@@ -0,0 +1,199 @@
|
||||
# -*- 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
|
||||
@@ -0,0 +1,147 @@
|
||||
# -*- 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()
|
||||
@@ -0,0 +1,124 @@
|
||||
# -*- 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
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
# -*- 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
|
||||
@@ -0,0 +1,132 @@
|
||||
# -*- 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)
|
||||
@@ -0,0 +1,16 @@
|
||||
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
|
||||
|
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 21 MiB |
|
After Width: | Height: | Size: 101 KiB |
|
After Width: | Height: | Size: 274 KiB |
|
After Width: | Height: | Size: 550 KiB |
|
After Width: | Height: | Size: 8.6 MiB |
|
After Width: | Height: | Size: 1.9 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 154 KiB |
|
After Width: | Height: | Size: 358 KiB |
|
After Width: | Height: | Size: 65 KiB |
|
After Width: | Height: | Size: 760 KiB |
|
After Width: | Height: | Size: 21 MiB |
|
After Width: | Height: | Size: 50 MiB |