ica web responsive theme
This commit is contained in:
parent
53f90a7834
commit
ad5967d420
|
|
@ -0,0 +1,76 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
{
|
||||
'name': 'ICA Web Responsive',
|
||||
'author':"Agga, IdeaCode Academy",
|
||||
'version': '18.0.1.0',
|
||||
'depends': ['web', 'base_setup'],
|
||||
# 'auto_install': ['web'],
|
||||
'auto_install': False,
|
||||
'data': [
|
||||
'views/webclient_templates.xml',
|
||||
],
|
||||
'assets': {
|
||||
'web._assets_primary_variables': [
|
||||
('after', 'web/static/src/scss/primary_variables.scss', 'ica_web_responsive/static/src/**/*.variables.scss'),
|
||||
('before', 'web/static/src/scss/primary_variables.scss', 'ica_web_responsive/static/src/scss/primary_variables.scss'),
|
||||
],
|
||||
'web._assets_secondary_variables': [
|
||||
('before', 'web/static/src/scss/secondary_variables.scss', 'ica_web_responsive/static/src/scss/secondary_variables.scss'),
|
||||
],
|
||||
'web._assets_backend_helpers': [
|
||||
('before', 'web/static/src/scss/bootstrap_overridden.scss', 'ica_web_responsive/static/src/scss/bootstrap_overridden.scss'),
|
||||
],
|
||||
'web.assets_frontend': [
|
||||
'ica_web_responsive/static/src/webclient/home_menu/home_menu_background.scss', # used by login page
|
||||
'ica_web_responsive/static/src/webclient/navbar/navbar.scss',
|
||||
],
|
||||
'web.assets_backend': [
|
||||
'ica_web_responsive/static/src/webclient/**/*.scss',
|
||||
'ica_web_responsive/static/src/views/**/*.scss',
|
||||
|
||||
'ica_web_responsive/static/src/core/**/*',
|
||||
'ica_web_responsive/static/src/webclient/**/*.js',
|
||||
('after', 'web/static/src/views/list/list_renderer.xml', 'ica_web_responsive/static/src/views/list/list_renderer_desktop.xml'),
|
||||
'ica_web_responsive/static/src/webclient/**/*.xml',
|
||||
'ica_web_responsive/static/src/views/**/*.js',
|
||||
'ica_web_responsive/static/src/views/**/*.xml',
|
||||
('remove', 'ica_web_responsive/static/src/views/pivot/**'),
|
||||
|
||||
# Don't include dark mode files in light mode
|
||||
('remove', 'ica_web_responsive/static/src/**/*.dark.scss'),
|
||||
],
|
||||
'web.assets_backend_lazy': [
|
||||
'ica_web_responsive/static/src/views/pivot/**',
|
||||
],
|
||||
'web.assets_backend_lazy_dark': [
|
||||
('include', 'web.dark_mode_variables'),
|
||||
# web._assets_backend_helpers
|
||||
('before', 'ica_web_responsive/static/src/scss/bootstrap_overridden.scss', 'ica_web_responsive/static/src/scss/bootstrap_overridden.dark.scss'),
|
||||
('after', 'web/static/lib/bootstrap/scss/_functions.scss', 'ica_web_responsive/static/src/scss/bs_functions_overridden.dark.scss'),
|
||||
],
|
||||
'web.assets_web': [
|
||||
('replace', 'web/static/src/main.js', 'ica_web_responsive/static/src/main.js'),
|
||||
],
|
||||
# ========= Dark Mode =========
|
||||
"web.dark_mode_variables": [
|
||||
# web._assets_primary_variables
|
||||
('before', 'ica_web_responsive/static/src/scss/primary_variables.scss', 'ica_web_responsive/static/src/scss/primary_variables.dark.scss'),
|
||||
('before', 'ica_web_responsive/static/src/**/*.variables.scss', 'ica_web_responsive/static/src/**/*.variables.dark.scss'),
|
||||
# web._assets_secondary_variables
|
||||
('before', 'ica_web_responsive/static/src/scss/secondary_variables.scss', 'ica_web_responsive/static/src/scss/secondary_variables.dark.scss'),
|
||||
],
|
||||
"web.assets_web_dark": [
|
||||
('include', 'web.dark_mode_variables'),
|
||||
# web._assets_backend_helpers
|
||||
('before', 'ica_web_responsive/static/src/scss/bootstrap_overridden.scss', 'ica_web_responsive/static/src/scss/bootstrap_overridden.dark.scss'),
|
||||
('after', 'web/static/lib/bootstrap/scss/_functions.scss', 'ica_web_responsive/static/src/scss/bs_functions_overridden.dark.scss'),
|
||||
# assets_backend
|
||||
'ica_web_responsive/static/src/**/*.dark.scss',
|
||||
],
|
||||
},
|
||||
'license': 'LGPL-3',
|
||||
|
||||
"images":["static/description/img.png"],
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 54 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 419 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 44 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 8.5 KiB |
|
|
@ -0,0 +1,35 @@
|
|||
<svg width="1920" height="1080" viewBox="0 0 1920 1080" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.51001 1080H76.35L1153.55 0H3.51001V1080Z" fill="url(#o_app_switcher_gradient_01)"/>
|
||||
<path d="M76.35 1080H842.98L1920 0.18V0H1153.55L76.35 1080Z" fill="url(#o_app_switcher_gradient_02)"/>
|
||||
<path d="M1920 0.180176L842.98 1080H1063.11L1920 220.88V0.180176Z" fill="url(#o_app_switcher_gradient_03)"/>
|
||||
<path d="M1920 1080V220.88L1063.11 1080H1920Z" fill="url(#o_app_switcher_gradient_04)"/>
|
||||
<rect width="1920" height="1080" fill="url(#o_app_switcher_gradient_05)" fill-opacity="0.25"/>
|
||||
<rect width="1920" height="1080" fill="#E9E6F9" fill-opacity="0.25"/>
|
||||
<defs>
|
||||
<linearGradient id="o_app_switcher_gradient_01" x1="-222.43" y1="727.19" x2="904.26" y2="-76.67" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.1" stop-color="white"/>
|
||||
<stop offset="0.36" stop-color="#FEFEFE"/>
|
||||
<stop offset="0.68" stop-color="#EAE7F9"/>
|
||||
<stop offset="1" stop-color="#E4E9F7"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="o_app_switcher_gradient_02" x1="407.23" y1="1021.82" x2="1848.47" y2="-153.08" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.32" stop-color="#FEFEFE"/>
|
||||
<stop offset="0.66" stop-color="#EAE7F9"/>
|
||||
<stop offset="1" stop-color="#E5E2F6"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="o_app_switcher_gradient_03" x1="1142.33" y1="846.57" x2="1951.83" y2="136.16" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.15" stop-color="white"/>
|
||||
<stop offset="0.51" stop-color="#F7F0FD"/>
|
||||
<stop offset="0.85" stop-color="#F0E7F9"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="o_app_switcher_gradient_04" x1="1409.74" y1="1071" x2="2070.98" y2="526.01" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.45" stop-color="white"/>
|
||||
<stop offset="0.88" stop-color="#F7F0FD"/>
|
||||
<stop offset="1" stop-color="#ECE5F8"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="o_app_switcher_gradient_05" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(960 540) rotate(90) scale(540 960)">
|
||||
<stop stop-color="#9996A9" stop-opacity="0.53"/>
|
||||
<stop offset="1" stop-color="#7A768F"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
|
|
@ -0,0 +1,3 @@
|
|||
declare module "@odoo/owl" {
|
||||
export * from "@odoo/owl/dist/types/owl"
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
// Overrides the existing classes to fit the text-color of
|
||||
// tag_list.dark.scss
|
||||
@for $size from 2 through length($o-colors) {
|
||||
.o_colorlist_item_color_#{$size - 1} {
|
||||
--background-color: #{adjust-color(nth($o-colors, $size), $lightness: -5%, $saturation: -15%)};
|
||||
--color: #{mix(nth($o-colors, $size), $o-view-background-color, 15%)};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
// = Dropdowns
|
||||
// ============================================================================
|
||||
// No CSS hacks, variables overrides only
|
||||
|
||||
.o-dropdown {
|
||||
--border-color: #{$dropdown-border-color};
|
||||
--o-input-border-color: #{$dropdown-border-color};
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
// Custom SCSS for enterprise version of notebook tabs
|
||||
|
||||
.o_notebook {
|
||||
--notebook-link-border-color: #{$border-color};
|
||||
--notebook-link-border-color-hover: #{$border-color};
|
||||
--notebook-link-border-color-active-accent: #{$o-brand-odoo};
|
||||
|
||||
.modal & {
|
||||
--notebook-padding-x: #{$modal-inner-padding};
|
||||
--notebook-margin-x: -#{$modal-inner-padding};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
// = Popovers
|
||||
// ============================================================================
|
||||
// No CSS hacks, variables overrides only
|
||||
|
||||
.o_popover {
|
||||
--border-color: #{$popover-border-color};
|
||||
|
||||
.table {
|
||||
--table-bg: #{$popover-bg};
|
||||
}
|
||||
|
||||
.o_input {
|
||||
--o-input-border-color: #{$popover-border-color};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
.o_tag {
|
||||
@for $size from 1 through length($o-colors) {
|
||||
&.o_tag_color_#{$size - 1} {
|
||||
--background-color: #{mix(nth($o-colors, $size), $o-view-background-color, 15%)};
|
||||
--color: #{adjust-color(nth($o-colors, $size), $lightness: 5%, $saturation: -15%)};
|
||||
|
||||
&::after {
|
||||
--background-color: var(--background-color);
|
||||
--color: var(--color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { startWebClient } from "@web/start";
|
||||
import { WebClientEnterprise } from "./webclient/webclient";
|
||||
|
||||
/**
|
||||
* This file starts the enterprise webclient. In the manifest, it replaces
|
||||
* the community main.js to load a different webclient class
|
||||
* (WebClientEnterprise instead of WebClient)
|
||||
*/
|
||||
startWebClient(WebClientEnterprise);
|
||||
180
third_party_addons/ica_web_responsive/static/src/scss/bootstrap_overridden.dark.scss
vendored
Normal file
180
third_party_addons/ica_web_responsive/static/src/scss/bootstrap_overridden.dark.scss
vendored
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
///
|
||||
/// This file is a copy of the bootstrap _variables.scss file where all the
|
||||
/// left-untouched variables definition have been removed.
|
||||
///
|
||||
|
||||
|
||||
// == Color system
|
||||
$danger: $o-danger !default;
|
||||
|
||||
$min-contrast-ratio: 4.5 !default;
|
||||
|
||||
$info-text-emphasis: shift-color($o-info, 90%) !default;
|
||||
$info-bg-subtle: shift-color($o-info, -65%) !default;
|
||||
$info-border-subtle: shift-color($o-info, 0%) !default;
|
||||
|
||||
// == Characters which are escaped by the escape-svg function
|
||||
|
||||
|
||||
// == Options
|
||||
|
||||
|
||||
// == Prefix for :root CSS variables
|
||||
|
||||
|
||||
// == Gradient
|
||||
|
||||
|
||||
// == Spacing
|
||||
|
||||
|
||||
// == Position
|
||||
|
||||
|
||||
// == Body
|
||||
|
||||
// == Links
|
||||
|
||||
$link-shade-percentage: 15% !default;
|
||||
$link-hover-color: shift-color($o-action, 30%) !default;
|
||||
|
||||
// == Paragraphs
|
||||
|
||||
|
||||
// == Grid breakpoints
|
||||
|
||||
|
||||
// == Grid containers
|
||||
|
||||
|
||||
// == Grid columns
|
||||
|
||||
|
||||
// == Components
|
||||
$box-shadow: 0 .5rem 1rem rgba($o-white, .3) !default;
|
||||
$box-shadow-sm: 0 .125rem .25rem rgba($o-white, .15) !default;
|
||||
$box-shadow-lg: 0 1rem 3rem rgba($o-white, .3) !default;
|
||||
$box-shadow-inset: inset 0 1px 2px rgba($o-white, .15) !default;
|
||||
|
||||
$component-active-bg: $o-gray-300 !default;
|
||||
|
||||
|
||||
// == Typography
|
||||
$mark-bg: #ffdebc !default;
|
||||
|
||||
// == Tables
|
||||
$table-bg: $o-view-background-color !default;
|
||||
$table-border-color: $o-gray-300 !default;
|
||||
$table-group-separator-color: $o-gray-300 !default;
|
||||
$table-bg-scale: -70% !default;
|
||||
$table-striped-bg-factor: .02 !default;
|
||||
$table-hover-bg-factor: .1 !default;
|
||||
$table-active-bg-factor: .1 !default;
|
||||
|
||||
// == Buttons + Forms
|
||||
|
||||
|
||||
// == Buttons
|
||||
|
||||
|
||||
// == Forms
|
||||
$input-border-color: $o-gray-300 !default;
|
||||
$input-placeholder-color: mix($o-gray-500, $o-gray-600) !default;
|
||||
$input-focus-bg: inherit !default;
|
||||
$form-range-thumb-active-bg: lighten($o-brand-primary, 10%);
|
||||
$form-range-track-bg: $o-gray-300 !default;
|
||||
|
||||
$form-switch-color: rgba($o-black, .5) !default;
|
||||
$form-switch-focus-color: $o-black !default;
|
||||
$form-switch-checked-color: $o-view-background-color !default;
|
||||
|
||||
// == Form validation
|
||||
|
||||
|
||||
// == Z-index master list
|
||||
|
||||
|
||||
// == Navs
|
||||
|
||||
|
||||
// == Navbar
|
||||
|
||||
|
||||
// == Dropdowns
|
||||
$dropdown-bg: $o-gray-300 !default;
|
||||
$dropdown-border-color: $o-gray-400 !default;
|
||||
$dropdown-header-color: $o-gray-700 !default;
|
||||
|
||||
|
||||
// == Pagination
|
||||
|
||||
|
||||
// == Placeholders
|
||||
|
||||
|
||||
// == Cards
|
||||
$card-cap-bg: $o-view-background-color !default;
|
||||
|
||||
// == Accordion
|
||||
|
||||
|
||||
// == Tooltips
|
||||
$tooltip-color: $o-gray-800 !default;
|
||||
$tooltip-bg: $o-gray-300 !default;
|
||||
|
||||
|
||||
// == Form tooltips must come after regular tooltips
|
||||
|
||||
|
||||
// == Popovers
|
||||
$popover-bg: $o-gray-300 !default;
|
||||
$popover-border-color: $o-gray-400 !default;
|
||||
|
||||
// == Toasts
|
||||
|
||||
|
||||
// == Badges
|
||||
|
||||
|
||||
// == Modals
|
||||
|
||||
|
||||
// == Alerts
|
||||
$alert-bg-scale: -65% !default;
|
||||
$alert-border-scale: 0% !default;
|
||||
$alert-color-scale: 90% !default;
|
||||
|
||||
// == Progress bars
|
||||
|
||||
|
||||
// == List group
|
||||
$list-group-bg: $o-view-background-color !default;
|
||||
|
||||
|
||||
// == Image thumbnails
|
||||
|
||||
|
||||
// == Figures
|
||||
|
||||
|
||||
// == Breadcrumbs
|
||||
|
||||
|
||||
// == Carousel
|
||||
|
||||
|
||||
// == Spinners
|
||||
|
||||
|
||||
// == Close
|
||||
|
||||
|
||||
// == Offcanvas
|
||||
|
||||
|
||||
// == Code
|
||||
|
||||
// == Keyboard Input
|
||||
$kbd-color: $o-gray-200 !default;
|
||||
$kbd-bg: $o-gray-900 !default;
|
||||
$kbd-box-shadow: 0px 1px 1px rgba($o-white, 0.2), inset 0px -1px 1px 1px rgba($o-gray-800, 0.8), inset 0px 2px 0px 0px rgba($o-black, 0.8) !default;
|
||||
136
third_party_addons/ica_web_responsive/static/src/scss/bootstrap_overridden.scss
vendored
Normal file
136
third_party_addons/ica_web_responsive/static/src/scss/bootstrap_overridden.scss
vendored
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
///
|
||||
/// This file is a copy of the bootstrap _variables.scss file where all the
|
||||
/// left-untouched variables definition have been removed.
|
||||
///
|
||||
|
||||
//
|
||||
// Color system
|
||||
//
|
||||
|
||||
$light: $o-white !default;
|
||||
$dark: $o-gray-900 !default;
|
||||
$warning: #e99d00 !default;
|
||||
$danger: #d44c59 !default;
|
||||
|
||||
|
||||
// Options
|
||||
|
||||
// Enable predefined decorative box-shadow styles on various components.
|
||||
// Does not affect box-shadows used for focus states.
|
||||
|
||||
$enable-shadows: true !default;
|
||||
|
||||
// Components
|
||||
//
|
||||
// Define common padding and border radius sizes and more.
|
||||
|
||||
$component-active-color: unset !default;
|
||||
$component-active-bg: mix($o-enterprise-action-color, $o-white, 10%) !default;
|
||||
|
||||
$nav-tabs-border-radius: 0 !default;
|
||||
$nav-pills-border-radius: 0 !default;
|
||||
$card-border-radius: 0 !default;
|
||||
$accordion-border-radius: 0 !default;
|
||||
$toast-border-radius: 0 !default;
|
||||
$badge-border-radius: 0 !default;
|
||||
$progress-border-radius: 0 !default;
|
||||
$list-group-border-radius: 0 !default;
|
||||
$thumbnail-border-radius: 0 !default;
|
||||
$form-check-input-border-radius: 0 !default;
|
||||
|
||||
// Typography
|
||||
//
|
||||
// Font, line-height, and color for body text, headings, and more.
|
||||
|
||||
$h1-font-size: $o-font-size-base * 2.4 !default;
|
||||
$h2-font-size: $o-font-size-base * 1.5 !default;
|
||||
$h3-font-size: $o-font-size-base * 1.3 !default;
|
||||
$h4-font-size: $o-font-size-base * 1.2 !default;
|
||||
$h5-font-size: $o-font-size-base * 1.1 !default;
|
||||
|
||||
// Buttons
|
||||
//
|
||||
// For each of Bootstrap's buttons, define text, background, and border color.
|
||||
|
||||
$btn-transition: none !default;
|
||||
|
||||
$btn-box-shadow: 0 !default;
|
||||
$btn-active-box-shadow: 0 !default;
|
||||
$btn-focus-box-shadow: 0 !default;
|
||||
|
||||
// Dropdowns
|
||||
//
|
||||
// Dropdown menu container and contents.
|
||||
|
||||
$dropdown-box-shadow: 0 .3rem 1rem rgba(#000, .1) !default;
|
||||
|
||||
// Forms
|
||||
//
|
||||
|
||||
$input-border-color: $o-gray-200 !default;
|
||||
$input-box-shadow: 0 !default;
|
||||
|
||||
$input-focus-bg: $o-white !default;
|
||||
$input-focus-box-shadow: 0 !default;
|
||||
$input-focus-border-color: mix($o-enterprise-action-color, $o-gray-200) !default;
|
||||
|
||||
$form-check-input-checked-color: $o-white !default;
|
||||
$form-check-input-checked-border-color: $o-enterprise-action-color !default;
|
||||
$form-check-input-checked-bg-color: $o-enterprise-action-color !default;
|
||||
|
||||
$form-select-focus-box-shadow: 0 !default;
|
||||
|
||||
$form-range-track-box-shadow: 0 !default;
|
||||
|
||||
// Z-index master list
|
||||
//
|
||||
// Change the z-index of the modal-backdrop elements to be equal to the
|
||||
// modal elements' ones. Bootstrap does not support multi-modals, and without
|
||||
// this rule all the modal-backdrops are below all the opened modals.
|
||||
// Indeed, bootstrap forces them to a lower z-index as the modal-backdrop
|
||||
// element (unique in their supported cases) might be put after the modal
|
||||
// element (if the modal is already in the DOM, hidden, then opened). This
|
||||
// cannot happen in odoo though as modals are not hidden but removed from
|
||||
// the DOM and are always put at the end of the body when opened.
|
||||
//
|
||||
// TODO the following code was disabled because it is saas-incompatible
|
||||
//
|
||||
// $zindex-modal-backdrop: $zindex-modal;
|
||||
|
||||
// Navs
|
||||
$nav-link-color: $o-main-text-color !default;
|
||||
$nav-tabs-link-active-color: $o-main-headings-color !default;
|
||||
$nav-tabs-link-active-bg: transparent !default;
|
||||
|
||||
|
||||
// Badges
|
||||
|
||||
$badge-border-radius: $o-border-radius !default;
|
||||
$badge-font-weight: normal !default;
|
||||
|
||||
// Alerts
|
||||
//
|
||||
// Define alert colors, border radius, and padding.
|
||||
|
||||
$alert-border-width: 0 !default;
|
||||
|
||||
// Progress bars
|
||||
|
||||
$progress-box-shadow: 0 !default;
|
||||
|
||||
// List group
|
||||
|
||||
$list-group-active-color: $o-enterprise-action-color !default;
|
||||
$list-group-active-bg: $component-active-bg !default;
|
||||
$list-group-active-border-color: $o-enterprise-action-color !default;
|
||||
|
||||
|
||||
// Image thumbnails
|
||||
|
||||
$thumbnail-box-shadow: 0 !default;
|
||||
|
||||
|
||||
// Breadcrumbs
|
||||
|
||||
$breadcrumb-active-color: $o-main-text-color !default;
|
||||
$breadcrumb-divider-color: $o-main-color-muted !default;
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
///
|
||||
/// This file is a copy of the bootstrap _functions.scss file where all the
|
||||
/// left-untouched function definition have been removed.
|
||||
///
|
||||
|
||||
// Tint a color: mix a color with black
|
||||
@function tint-color($color, $weight) {
|
||||
@return mix(#000, $color, $weight);
|
||||
}
|
||||
|
||||
// Shade a color: mix a color with white
|
||||
@function shade-color($color, $weight) {
|
||||
@return mix(#FFF, $color, $weight);
|
||||
}
|
||||
|
|
@ -0,0 +1,193 @@
|
|||
///
|
||||
/// Odoo Dark-Mode
|
||||
///
|
||||
///
|
||||
|
||||
// = Colors
|
||||
// ============================================================================
|
||||
|
||||
$o-white: #000 !default;
|
||||
$o-black: #FFF !default;
|
||||
|
||||
$o-gray-100: #1B1D26 !default;
|
||||
$o-gray-200: #262A36 !default;
|
||||
$o-gray-300: #3C3E4B !default;
|
||||
$o-gray-400: #5A5E6B !default;
|
||||
$o-gray-500: #6B707F !default;
|
||||
$o-gray-600: #7E8392 !default;
|
||||
$o-gray-700: #B1B3BC !default;
|
||||
$o-gray-800: #D1D1D1 !default;
|
||||
$o-gray-900: #E4E4E4 !default;
|
||||
|
||||
$o-enterprise-color: #6b3e66 !default;
|
||||
$o-brand-primary: $o-enterprise-color !default;
|
||||
$o-enterprise-action-color: #02c7b5 !default;
|
||||
|
||||
$o-success: #1dc959 !default;
|
||||
$o-info: #6AB5FB !default;
|
||||
$o-warning: #FBB56A !default;
|
||||
$o-danger: #b83232 !default;
|
||||
$o-action: $o-enterprise-action-color !default;
|
||||
$light: $o-gray-300 !default;
|
||||
$dark: $o-gray-700 !default;
|
||||
|
||||
|
||||
// = Text
|
||||
// ============================================================================
|
||||
|
||||
$o-main-text-color: $o-gray-800 !default;
|
||||
$o-main-link-color: $o-action !default;
|
||||
$o-enterprise-color: $o-brand-odoo !default;
|
||||
$o-main-favorite-color: #ffd532 !default;
|
||||
$o-main-code-color: #c58bc8 !default;
|
||||
|
||||
// = Fine-tune contextual text colors.
|
||||
$o-theme-text-colors: (
|
||||
"primary": #b972a6,
|
||||
"success": #1dc959,
|
||||
"info": #6AB5FB,
|
||||
"warning": #FBB56A,
|
||||
"danger": #ff5757,
|
||||
) !default;
|
||||
|
||||
|
||||
// = Webclient
|
||||
// ============================================================================
|
||||
$o-webclient-color-scheme: dark !default;
|
||||
$o-webclient-background-color: $o-gray-100 !default;
|
||||
$o-view-background-color: $o-gray-200 !default;
|
||||
|
||||
// = Inputs
|
||||
$o-input-border-required: $o-black !default;
|
||||
|
||||
// = Components
|
||||
// ============================================================================
|
||||
$o-component-active-bg: mix($o-action, $o-gray-300, 10%) !default;
|
||||
$o-form-lightsecondary: $o-gray-300 !default;
|
||||
|
||||
// = List-group
|
||||
$o-list-group-active-color: $o-gray-900 !default;
|
||||
$o-list-group-active-bg: rgba(saturate(adjust-hue($o-info, 15), 1.8), .5) !default;
|
||||
|
||||
// = Modal
|
||||
$modal-backdrop-bg: $o-white !default;
|
||||
|
||||
// = Buttons
|
||||
$o-btns-bs-override: () !default;
|
||||
$o-btns-bs-override: map-merge((
|
||||
"primary": (
|
||||
background: $o-brand-primary,
|
||||
border: $o-brand-primary,
|
||||
color: $o-black,
|
||||
|
||||
hover-background: lighten($o-brand-primary, 5%),
|
||||
hover-border: lighten($o-brand-primary, 5%),
|
||||
hover-color: $o-black,
|
||||
|
||||
active-background: lighten($o-brand-primary, 10%),
|
||||
active-border: lighten($o-brand-primary, 10%),
|
||||
active-color: $o-black,
|
||||
),
|
||||
|
||||
"secondary": (
|
||||
background: $o-gray-300,
|
||||
border: $o-gray-300,
|
||||
color: $o-gray-900,
|
||||
|
||||
hover-background: $o-gray-400,
|
||||
hover-border: $o-gray-400,
|
||||
hover-color: $o-gray-900,
|
||||
|
||||
active-background: mix($o-action, $o-gray-100, 15%),
|
||||
active-border: lighten($o-action, 10%),
|
||||
active-color: $o-black,
|
||||
),
|
||||
|
||||
"light": (
|
||||
background: $o-gray-200,
|
||||
border: $o-gray-200,
|
||||
color: $o-gray-800,
|
||||
|
||||
hover-background: $o-gray-300,
|
||||
hover-border: $o-gray-300,
|
||||
hover-color: $o-gray-900,
|
||||
|
||||
active-background: mix($o-action, $o-gray-100, 15%),
|
||||
active-border: darken($o-action, 10%),
|
||||
active-color: $o-black,
|
||||
),
|
||||
|
||||
"danger": (
|
||||
background: $o-danger,
|
||||
border: $o-danger,
|
||||
color: $o-black,
|
||||
|
||||
hover-background: lighten($o-danger, 5%),
|
||||
hover-border: lighten($o-danger, 5%),
|
||||
hover-color: $o-black,
|
||||
|
||||
active-background: lighten($o-danger, 10%),
|
||||
active-border: lighten($o-danger, 10%),
|
||||
active-color: $o-black,
|
||||
),
|
||||
), $o-btns-bs-override);
|
||||
|
||||
|
||||
$o-btns-bs-outline-override: () !default;
|
||||
$o-btns-bs-outline-override: map-merge((
|
||||
|
||||
"primary": (
|
||||
background: transparent,
|
||||
border: map-get($o-theme-text-colors, 'primary'),
|
||||
color: map-get($o-theme-text-colors, 'primary'),
|
||||
|
||||
hover-background: lighten($o-brand-primary, 5%),
|
||||
hover-border: lighten($o-brand-primary, 5%),
|
||||
hover-color: $o-black,
|
||||
|
||||
active-background: lighten($o-brand-primary, 10%),
|
||||
active-border: lighten($o-brand-primary, 10%),
|
||||
active-color: $o-black,
|
||||
),
|
||||
"secondary": (
|
||||
background: transparent,
|
||||
border: $o-gray-300,
|
||||
color: $o-gray-700,
|
||||
|
||||
hover-background: $o-gray-300,
|
||||
hover-border: $o-gray-300,
|
||||
hover-color: $o-black,
|
||||
|
||||
active-background: mix($o-action, $o-gray-100, 15%),
|
||||
active-border: lighten($o-action, 10%),
|
||||
active-color: $o-black,
|
||||
),
|
||||
|
||||
"light": (
|
||||
background: transparent,
|
||||
border: $o-gray-300,
|
||||
color: $o-black,
|
||||
|
||||
hover-background: $o-gray-300,
|
||||
hover-border: $o-gray-300,
|
||||
hover-color: $o-gray-900,
|
||||
|
||||
active-background: mix($o-action, $o-gray-100, 15%),
|
||||
active-border: lighten($o-action, 10%),
|
||||
active-color: $o-black,
|
||||
),
|
||||
|
||||
"danger": (
|
||||
background: transparent,
|
||||
border: $o-danger,
|
||||
color: $o-danger,
|
||||
|
||||
hover-background: lighten($o-danger, 5%),
|
||||
hover-border: lighten($o-danger, 5%),
|
||||
hover-color: $o-black,
|
||||
|
||||
active-background: lighten($o-danger, 10%),
|
||||
active-border: lighten($o-danger, 10%),
|
||||
active-color: $o-black,
|
||||
),
|
||||
), $o-btns-bs-outline-override);
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
///
|
||||
/// This file regroups the variables that style odoo components.
|
||||
/// They are available in every asset bundle.
|
||||
///
|
||||
|
||||
// Colors
|
||||
$o-white: #FFF !default;
|
||||
$o-black: #000 !default;
|
||||
|
||||
$o-gray-100: #F9FAFB !default;
|
||||
$o-gray-200: #e7e9ed !default;
|
||||
$o-gray-300: #d8dadd !default;
|
||||
$o-gray-400: #9a9ca5 !default;
|
||||
$o-gray-500: #7c7f89 !default;
|
||||
$o-gray-600: #5f636f !default;
|
||||
$o-gray-700: #374151 !default;
|
||||
$o-gray-800: #1F2937 !default;
|
||||
$o-gray-900: #111827 !default;
|
||||
|
||||
$o-enterprise-color: #714B67 !default;
|
||||
$o-enterprise-action-color: #017e84 !default;
|
||||
|
||||
$o-opacity-disabled: .5 !default;
|
||||
$o-opacity-muted: .76 !default;
|
||||
|
||||
$o-brand-odoo: $o-enterprise-color !default;
|
||||
$o-brand-primary: $o-brand-odoo !default;
|
||||
$o-brand-secondary: #8f8f8f !default;
|
||||
$o-brand-lightsecondary: $o-gray-100 !default;
|
||||
|
||||
$o-action: $o-enterprise-action-color !default;
|
||||
$o-main-text-color: $o-gray-700 !default;
|
||||
$o-main-link-color: $o-enterprise-action-color !default;
|
||||
$o-main-color-muted: rgba($o-main-text-color, $o-opacity-muted) !default;
|
||||
|
||||
// Components
|
||||
$o-component-active-color: $o-gray-900 !default;
|
||||
$o-component-active-bg: mix($o-action, $o-white, 10%) !default;
|
||||
$o-component-active-border: $o-action !default;
|
||||
|
||||
$o-list-group-header-color: $o-gray-900 !default;
|
||||
$o-list-footer-color: $o-gray-900 !default;
|
||||
$o-list-footer-bg-color: transparent !default;
|
||||
$o-list-footer-font-weight: 500 !default;
|
||||
|
||||
$o-form-lightsecondary: $o-gray-200 !default;
|
||||
|
||||
// o-inputs
|
||||
$o-input-padding-y: 1px !default;
|
||||
$o-input-padding-x: 0 !default;
|
||||
|
||||
$o-input-border-required: $o-gray-900 !default;
|
||||
|
||||
// Badges
|
||||
$o-badge-min-width: 2.7ch !default !default;
|
||||
|
||||
// Buttons
|
||||
// Map of customized values for each button. If a button's design is defined
|
||||
// here, the relative values will take priority over default BS ones.
|
||||
// Notice: each map's entry is passed directly to the Bootstrap mixin, meaning
|
||||
// that all states must be defined, there can't be omissions.
|
||||
$o-btns-bs-override: () !default;
|
||||
$o-btns-bs-override: map-merge((
|
||||
"primary": (
|
||||
background: $o-brand-primary,
|
||||
border: $o-brand-primary,
|
||||
color: $o-white,
|
||||
|
||||
hover-background: darken($o-brand-primary, 10%),
|
||||
hover-border: darken($o-brand-primary, 10%),
|
||||
hover-color: $o-white,
|
||||
|
||||
active-background: mix($o-brand-primary, $o-white, 10%),
|
||||
active-border: $o-brand-primary,
|
||||
active-color:$o-brand-primary,
|
||||
),
|
||||
"secondary": (
|
||||
background: $o-gray-200,
|
||||
border: $o-gray-200,
|
||||
color: $o-gray-700,
|
||||
|
||||
hover-background: $o-gray-300,
|
||||
hover-border: $o-gray-300,
|
||||
hover-color: $o-gray-800,
|
||||
|
||||
active-background: $o-component-active-bg,
|
||||
active-border: $o-component-active-border,
|
||||
active-color: $o-component-active-color,
|
||||
),
|
||||
), $o-btns-bs-override);
|
||||
|
||||
$o-btns-bs-outline-override: () !default;
|
||||
$o-btns-bs-outline-override: map-merge((
|
||||
"secondary": (
|
||||
background: transparent,
|
||||
border: $o-gray-300,
|
||||
color: $o-gray-700,
|
||||
|
||||
hover-background: $o-gray-200,
|
||||
hover-border: $o-gray-300,
|
||||
hover-color: $o-gray-800,
|
||||
|
||||
active-background: mix($o-enterprise-action-color, $o-white, 10%),
|
||||
active-border: $o-enterprise-action-color,
|
||||
active-color: $o-gray-900,
|
||||
),
|
||||
), $o-btns-bs-outline-override);
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
$o-colors-original: lighten(#000, 46.7%), #f07b50, #f4b660, #F7CD1F, #6cedeb, #8d5482,
|
||||
#e74e4e, #2C8397, #475577, #dc0457, #30C381, #9365B8 !default;
|
||||
|
||||
$o-colors-secondary-original: #aa4b6b, #30C381, #97743a, #F7CD1F, #4285F4, #8E24AA,
|
||||
#D6145F, #173e43, #348F50, #AA3A38, #795548, #5e0231,
|
||||
#6be585, #999966, #e9d362, #b56969, #bdc3c7, #649173 !default;
|
||||
|
||||
$o-colors: ()!default;
|
||||
$o-colors-secondary: ()!default;
|
||||
|
||||
@each $-color in $o-colors-original {
|
||||
$-adjusted: saturate(mix($-color, $o-black, 50%), 60%);
|
||||
$o-colors: append($o-colors, $-adjusted);
|
||||
}
|
||||
|
||||
@each $-color in $o-colors-secondary-original {
|
||||
$-adjusted: saturate(mix($-color, $o-black, 50%), 80%);
|
||||
$o-colors-secondary: append($o-colors-secondary, $-adjusted);
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
// Needed for having no spacing between sheet and mail body in mass_mailing:
|
||||
// Different required cancel paddings between web and ica_web_responsive
|
||||
$o-sheet-cancel-tpadding: $o-horizontal-padding !default;
|
||||
$o-sheet-cancel-bpadding: $o-horizontal-padding + $o-sheet-vpadding !default;
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
// = Search Bar
|
||||
// ============================================================================
|
||||
// No CSS hacks, variables overrides only
|
||||
|
||||
.o_searchview_facet {
|
||||
--SearchBar-facet-background: #{$o-black};
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
// = Mobile Search
|
||||
// ============================================================================
|
||||
// No CSS hacks, variables overrides only
|
||||
|
||||
.o_mobile_search {
|
||||
--mobileSearch-bg: #{$o-gray-200};
|
||||
--mobileSearch__header-bg: #{$o-gray-100};
|
||||
}
|
||||
|
||||
.o_searchview {
|
||||
--SearchBar-background-color: #{$o-gray-100};
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
// = Dashboard View
|
||||
// ============================================================================
|
||||
// No CSS hacks, variables overrides only
|
||||
|
||||
.o_dashboard_view {
|
||||
--DashboardView-background-color: #{$o-gray-100};
|
||||
--DashboardView__controlPanel-background-color: transparent;
|
||||
--DashboardView__pieChart-background-color: transparent;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
// = Image Field
|
||||
// ============================================================================
|
||||
// No CSS hacks, variables overrides only
|
||||
|
||||
.o_field_image {
|
||||
--ImageField-background-color: #{$o-gray-900};
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
.o_field_property_definition_type, .o_field_property_definition_type_menu {
|
||||
.o_field_property_dropdown > img {
|
||||
-webkit-filter: invert(100%);
|
||||
filter: invert(100%);
|
||||
}
|
||||
}
|
||||
.o_property_field_value {
|
||||
select {
|
||||
option {
|
||||
background-color: $border-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
.o_field_property_definition_type_popover.popover {
|
||||
.dropdown-item img {
|
||||
-webkit-filter: invert(100%);
|
||||
filter: invert(100%);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
.o-form-buttonbox {
|
||||
--o-stat-button-color: currentColor;
|
||||
--o-stat-text-color: #{o-text-color('primary')};
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
// = Gantt View Variables
|
||||
// ============================================================================
|
||||
// No CSS hacks, variables overrides only
|
||||
|
||||
$gantt-highlight-today-border: rgba($o-warning, 0.5) !default;
|
||||
$gantt-highlight-today-bg: rgba($o-warning, 0.15)!default;
|
||||
$gantt-highlight-hover-row: rgba($o-brand-primary, .1) !default;
|
||||
$gantt-row-open-bg: $o-gray-100 !default;
|
||||
$gantt-unavailability-bg: $o-gray-200 !default;
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
// = Kanban Rendered
|
||||
// ============================================================================
|
||||
// No CSS hacks, variables overrides only
|
||||
|
||||
.o_kanban_renderer {
|
||||
--KanbanGroup-grouped-bg: #{$o-view-background-color};
|
||||
--KanbanRecord__image-bg-color: #{$o-gray-900};
|
||||
--KanbanColumn__highlight-background: #{mix($o-action, $o-gray-100, 15%)};
|
||||
--KanbanColumn__highlight-border: #{$o-component-active-border};
|
||||
--Kanban-background: #{$gray-100};
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
/* @odoo-module */
|
||||
|
||||
import { registry } from "@web/core/registry";
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
import { KanbanHeader } from "@web/views/kanban/kanban_header";
|
||||
import { PromoteStudioAutomationDialog } from "@ica_web_responsive/webclient/promote_studio_dialog/promote_studio_dialog";
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { user } from "@web/core/user";
|
||||
|
||||
patch(KanbanHeader.prototype, {
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
get permissions() {
|
||||
const permissions = super.permissions;
|
||||
Object.defineProperty(permissions, "canEditAutomations", {
|
||||
get: () => user.isAdmin,
|
||||
configurable: true,
|
||||
});
|
||||
return permissions;
|
||||
},
|
||||
|
||||
async openAutomations() {
|
||||
if (typeof this._openAutomations === "function") {
|
||||
// this is the case if base_automation is installed
|
||||
return this._openAutomations();
|
||||
} else {
|
||||
this.env.services.dialog.add(PromoteStudioAutomationDialog, {
|
||||
title: _t("Odoo Studio - Customize workflows in minutes"),
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
registry.category("kanban_header_config_items").add(
|
||||
"open_automations",
|
||||
{
|
||||
label: _t("Automations"),
|
||||
method: "openAutomations",
|
||||
isVisible: ({ permissions }) => permissions.canEditAutomations,
|
||||
class: "o_column_automations",
|
||||
},
|
||||
{ sequence: 25, force: true }
|
||||
);
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
.o_kanban_view {
|
||||
|
||||
.o_column_quick_create .o_kanban_quick_create {
|
||||
input {
|
||||
&, &:focus, &:hover {
|
||||
background: transparent;
|
||||
border-bottom: 1px solid map-get($grays, '600');
|
||||
}
|
||||
}
|
||||
.input-group-append, .input-group-prepend {
|
||||
border-left: 10px solid map-get($grays, '200');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
// = ListRenderer
|
||||
// ============================================================================
|
||||
// No CSS hacks, variables overrides only
|
||||
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
.o_list_renderer {
|
||||
--ListRenderer-thead-border-end-color: transparent;
|
||||
}
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
/** @odoo-module */
|
||||
|
||||
import { isMobileOS } from "@web/core/browser/feature_detection";
|
||||
import { user } from "@web/core/user";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
import { ListRenderer } from "@web/views/list/list_renderer";
|
||||
import { PromoteStudioDialog } from "@ica_web_responsive/webclient/promote_studio_dialog/promote_studio_dialog";
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { onWillDestroy, useState } from "@odoo/owl";
|
||||
|
||||
export const patchListRendererDesktop = () => ({
|
||||
setup() {
|
||||
super.setup(...arguments);
|
||||
this.actionService = useService("action");
|
||||
const list = this.props.list;
|
||||
|
||||
const { actionId, actionType } = this.env.config || {};
|
||||
|
||||
// Start by determining if the current ListRenderer is in a context that would
|
||||
// allow the edition of the arch by studio.
|
||||
// It needs to be a full list view, in an action
|
||||
// (not a X2Many list, and not an "embedded" list in another component)
|
||||
// Also, there is not enough information when an action is in target new,
|
||||
// and this use case is fairly outside of the feature's scope
|
||||
const isPotentiallyEditable =
|
||||
!isMobileOS() &&
|
||||
!this.env.inDialog &&
|
||||
user.isSystem &&
|
||||
list === list.model.root &&
|
||||
actionId &&
|
||||
actionType === "ir.actions.act_window";
|
||||
this.studioEditable = useState({ value: isPotentiallyEditable });
|
||||
|
||||
if (isPotentiallyEditable) {
|
||||
const computeStudioEditable = (action) => {
|
||||
// Finalize the computation when the actionService is ready.
|
||||
// The following code is copied from studioService.
|
||||
if (!action.xml_id) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
action.res_model.indexOf("settings") > -1 &&
|
||||
action.res_model.indexOf("x_") !== 0
|
||||
) {
|
||||
return false; // settings views aren't editable; but x_settings is
|
||||
}
|
||||
if (action.res_model === "board.board") {
|
||||
return false; // dashboard isn't editable
|
||||
}
|
||||
if (action.view_mode === "qweb") {
|
||||
// Apparently there is a QWebView that allows to
|
||||
// implement ActWindow actions that are completely custom
|
||||
// but not editable by studio
|
||||
return false;
|
||||
}
|
||||
if (action.res_model === "knowledge.article") {
|
||||
// The knowledge form view is very specific and custom, it doesn't make sense
|
||||
// to edit it. Editing the list and kanban is more debatable, but for simplicity's sake
|
||||
// we set them to not editable too.
|
||||
return false;
|
||||
}
|
||||
return Boolean(action.res_model);
|
||||
};
|
||||
const onUiUpdated = () => {
|
||||
const action = this.actionService.currentController.action;
|
||||
if (action.id === actionId) {
|
||||
this.studioEditable.value = computeStudioEditable(action);
|
||||
}
|
||||
stopListening();
|
||||
};
|
||||
const stopListening = () =>
|
||||
this.env.bus.removeEventListener("ACTION_MANAGER:UI-UPDATED", onUiUpdated);
|
||||
this.env.bus.addEventListener("ACTION_MANAGER:UI-UPDATED", onUiUpdated);
|
||||
|
||||
onWillDestroy(stopListening);
|
||||
}
|
||||
},
|
||||
|
||||
isStudioEditable() {
|
||||
return this.studioEditable.value;
|
||||
},
|
||||
|
||||
get displayOptionalFields() {
|
||||
return this.isStudioEditable() || super.displayOptionalFields;
|
||||
},
|
||||
|
||||
/**
|
||||
* This function opens promote studio dialog
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
onSelectedAddCustomField() {
|
||||
this.env.services.dialog.add(PromoteStudioDialog, {
|
||||
title: _t("Odoo Studio - Add new fields to any view"),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const unpatchListRendererDesktop = patch(ListRenderer.prototype, patchListRendererDesktop());
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-inherit="web.ListRenderer" t-inherit-mode="extension">
|
||||
<xpath expr="//Dropdown/t[@t-set-slot='content']" position="inside">
|
||||
<t t-if="this.isStudioEditable ? this.isStudioEditable() : false">
|
||||
<div t-if="hasOptionalFields" class="dropdown-divider"/>
|
||||
<DropdownItem closingMode="'none'" onSelected="() => this.onSelectedAddCustomField()" class="'dropdown-item-studio'">
|
||||
<i class="fa fa-plus fa-fw me-2"/>
|
||||
<span>Add Custom Field</span>
|
||||
</DropdownItem>
|
||||
</t>
|
||||
</xpath>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
/** @odoo-module */
|
||||
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
import { PivotRenderer } from "@web/views/pivot/pivot_renderer";
|
||||
|
||||
import { useEffect, useRef } from "@odoo/owl";
|
||||
|
||||
patch(PivotRenderer.prototype, {
|
||||
setup() {
|
||||
super.setup();
|
||||
this.root = useRef("root");
|
||||
if (this.env.isSmall) {
|
||||
useEffect(() => {
|
||||
if (this.root.el) {
|
||||
const tooltipElems = this.root.el.querySelectorAll("*[data-tooltip]");
|
||||
for (const el of tooltipElems) {
|
||||
el.removeAttribute("data-tooltip");
|
||||
el.removeAttribute("data-tooltip-position");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
getPadding(cell) {
|
||||
if (this.env.isSmall) {
|
||||
return 5 + cell.indent * 5;
|
||||
}
|
||||
return super.getPadding(...arguments);
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
@include media-breakpoint-down(md) {
|
||||
.o_pivot {
|
||||
height: 100%;
|
||||
|
||||
.dropdown.show {
|
||||
> .dropdown-toggle::after {
|
||||
@include o-caret-down;
|
||||
}
|
||||
}
|
||||
|
||||
th > .o_group_by_menu > .dropdown-menu {
|
||||
.dropdown-item {
|
||||
// caret centered vertically
|
||||
.dropdown-toggle::after{
|
||||
top: 12px;
|
||||
}
|
||||
|
||||
// nested dropdown should be *under* the parent, not on its side
|
||||
.dropdown-menu {
|
||||
top: initial !important;
|
||||
left: 5% !important;
|
||||
width: 95%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-inherit="web.PivotRenderer" t-inherit-mode="extension">
|
||||
<xpath expr="//div[hasclass('o_pivot')]" position="attributes">
|
||||
<attribute name="t-ref">root</attribute>
|
||||
</xpath>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
/** @odoo-module **/
|
||||
import { BurgerMenu } from "@web/webclient/burger_menu/burger_menu";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
export class EnterpriseBurgerMenu extends BurgerMenu {
|
||||
setup() {
|
||||
super.setup();
|
||||
this.hm = useService("home_menu");
|
||||
}
|
||||
|
||||
get currentApp() {
|
||||
return !this.hm.hasHomeMenu && super.currentApp;
|
||||
}
|
||||
}
|
||||
|
||||
const systrayItem = {
|
||||
Component: EnterpriseBurgerMenu,
|
||||
};
|
||||
|
||||
registry.category("systray").add("burger_menu", systrayItem, { sequence: 0, force: true });
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
// = Burger Menu Variables
|
||||
// ============================================================================
|
||||
// No CSS hacks, variables overrides only
|
||||
|
||||
$o-burger-topbar-bg: $o-gray-100 !default;
|
||||
$o-burger-topbar-color: $o-gray-900 !default;
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { cookie as cookieManager } from "@web/core/browser/cookie";
|
||||
|
||||
export function switchColorSchemeItem(env) {
|
||||
return {
|
||||
type: "switch",
|
||||
id: "color_scheme.switch_theme",
|
||||
description: _t("Dark Mode"),
|
||||
callback: () => {
|
||||
const cookie = cookieManager.get("color_scheme");
|
||||
const scheme = cookie === "dark" ? "light" : "dark";
|
||||
env.services.color_scheme.switchToColorScheme(scheme);
|
||||
},
|
||||
isChecked: cookieManager.get("color_scheme") === "dark",
|
||||
sequence: 30,
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
import { registry } from "@web/core/registry";
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import { cookie } from "@web/core/browser/cookie";
|
||||
|
||||
import { switchColorSchemeItem } from "./color_scheme_menu_items";
|
||||
|
||||
const serviceRegistry = registry.category("services");
|
||||
const userMenuRegistry = registry.category("user_menuitems");
|
||||
|
||||
export const colorSchemeService = {
|
||||
dependencies: ["ui"],
|
||||
|
||||
start(env, { ui }) {
|
||||
userMenuRegistry.add("color_scheme.switch", switchColorSchemeItem);
|
||||
return {
|
||||
switchToColorScheme: (scheme) => {
|
||||
cookie.set("color_scheme", scheme);
|
||||
ui.block();
|
||||
this.reload();
|
||||
},
|
||||
};
|
||||
},
|
||||
reload() {
|
||||
browser.location.reload();
|
||||
},
|
||||
};
|
||||
serviceRegistry.add("color_scheme", colorSchemeService);
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
// = Home Menu
|
||||
// ============================================================================
|
||||
// No CSS hacks, variables overrides only
|
||||
|
||||
.o_home_menu_background {
|
||||
|
||||
.o_app_icon {
|
||||
--AppSwitcherIcon-background: #{rgba(#fff, .05)};
|
||||
--AppSwitcherIcon-inset-shadow: #{inset 0 0 0 1px rgba(#fff, .1)};
|
||||
--AppSwitcherIcon-border-color: transparent;
|
||||
}
|
||||
|
||||
.o_app:hover .o_app_icon {
|
||||
--AppSwitcherIcon-inset-shadow: #{inset 0 0 0 1px rgba(#fff, .2)};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,375 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { hasTouch, isIosApp, isMacOS } from "@web/core/browser/feature_detection";
|
||||
import { useHotkey } from "@web/core/hotkeys/hotkey_hook";
|
||||
import { user } from "@web/core/user";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { useSortable } from "@web/core/utils/sortable_owl";
|
||||
|
||||
import {
|
||||
Component,
|
||||
useExternalListener,
|
||||
onMounted,
|
||||
onPatched,
|
||||
onWillUpdateProps,
|
||||
useState,
|
||||
useRef,
|
||||
} from "@odoo/owl";
|
||||
|
||||
class FooterComponent extends Component {
|
||||
static template = "ica_web_responsive.HomeMenu.CommandPalette.Footer";
|
||||
static props = {
|
||||
//prop added by the command palette
|
||||
switchNamespace: { type: Function, optional: true },
|
||||
};
|
||||
|
||||
setup() {
|
||||
this.controlKey = isMacOS() ? "COMMAND" : "CONTROL";
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Home menu
|
||||
*
|
||||
* This component handles the display and navigation between the different
|
||||
* available applications and menus.
|
||||
* @extends Component
|
||||
*/
|
||||
export class HomeMenu extends Component {
|
||||
static template = "ica_web_responsive.HomeMenu";
|
||||
static components = { };
|
||||
static props = {
|
||||
apps: {
|
||||
type: Array,
|
||||
element: {
|
||||
type: Object,
|
||||
shape: {
|
||||
actionID: Number,
|
||||
href: String,
|
||||
appID: Number,
|
||||
id: Number,
|
||||
label: String,
|
||||
parents: String,
|
||||
webIcon: {
|
||||
type: [
|
||||
Boolean,
|
||||
String,
|
||||
{
|
||||
type: Object,
|
||||
optional: 1,
|
||||
shape: {
|
||||
iconClass: String,
|
||||
color: String,
|
||||
backgroundColor: String,
|
||||
},
|
||||
},
|
||||
],
|
||||
optional: true,
|
||||
},
|
||||
webIconData: { type: String, optional: 1 },
|
||||
xmlid: String,
|
||||
},
|
||||
},
|
||||
},
|
||||
reorderApps: { type: Function },
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Object} props
|
||||
* @param {Object[]} props.apps application icons
|
||||
* @param {number} props.apps[].actionID
|
||||
* @param {number} props.apps[].id
|
||||
* @param {string} props.apps[].label
|
||||
* @param {string} props.apps[].parents
|
||||
* @param {(boolean|string|Object)} props.apps[].webIcon either:
|
||||
* - boolean: false (no webIcon)
|
||||
* - string: path to Odoo icon file
|
||||
* - Object: customized icon (background, class and color)
|
||||
* @param {string} [props.apps[].webIconData]
|
||||
* @param {string} props.apps[].xmlid
|
||||
* @param {function} props.reorderApps
|
||||
*/
|
||||
setup() {
|
||||
this.command = useService("command");
|
||||
this.menus = useService("menu");
|
||||
this.homeMenuService = useService("home_menu");
|
||||
this.ui = useService("ui");
|
||||
this.state = useState({
|
||||
focusedIndex: null,
|
||||
isIosApp: isIosApp(),
|
||||
});
|
||||
this.inputRef = useRef("input");
|
||||
this.rootRef = useRef("root");
|
||||
this.pressTimer;
|
||||
|
||||
if (!this.env.isSmall) {
|
||||
this._registerHotkeys();
|
||||
}
|
||||
|
||||
useSortable({
|
||||
enable: this._enableAppsSorting,
|
||||
// Params
|
||||
ref: this.rootRef,
|
||||
elements: ".o_draggable",
|
||||
cursor: "move",
|
||||
delay: 500,
|
||||
tolerance: 10,
|
||||
// Hooks
|
||||
onWillStartDrag: (params) => this._sortStart(params),
|
||||
onDrop: (params) => this._sortAppDrop(params),
|
||||
});
|
||||
|
||||
onWillUpdateProps(() => {
|
||||
// State is reset on each remount
|
||||
this.state.focusedIndex = null;
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
if (!hasTouch()) {
|
||||
this._focusInput();
|
||||
}
|
||||
});
|
||||
|
||||
onPatched(() => {
|
||||
if (this.state.focusedIndex !== null && !this.env.isSmall) {
|
||||
const selectedItem = document.querySelector(".o_home_menu .o_menuitem.o_focused");
|
||||
// When TAB is managed externally the class o_focused disappears.
|
||||
if (selectedItem) {
|
||||
// Center window on the focused item
|
||||
selectedItem.scrollIntoView({ block: "center" });
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Getters
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @returns {Object[]}
|
||||
*/
|
||||
get displayedApps() {
|
||||
return this.props.apps;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
get maxIconNumber() {
|
||||
const w = window.innerWidth;
|
||||
if (w < 576) {
|
||||
return 3;
|
||||
} else if (w < 768) {
|
||||
return 4;
|
||||
} else {
|
||||
return 6;
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Object} menu
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_openMenu(menu) {
|
||||
return this.menus.selectMenu(menu);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update this.state.focusedIndex if not null.
|
||||
* @private
|
||||
* @param {string} cmd
|
||||
*/
|
||||
_updateFocusedIndex(cmd) {
|
||||
const nbrApps = this.displayedApps.length;
|
||||
const lastIndex = nbrApps - 1;
|
||||
const focusedIndex = this.state.focusedIndex;
|
||||
if (lastIndex < 0) {
|
||||
return;
|
||||
}
|
||||
if (focusedIndex === null) {
|
||||
this.state.focusedIndex = 0;
|
||||
return;
|
||||
}
|
||||
const lineNumber = Math.ceil(nbrApps / this.maxIconNumber);
|
||||
const currentLine = Math.ceil((focusedIndex + 1) / this.maxIconNumber);
|
||||
let newIndex;
|
||||
switch (cmd) {
|
||||
case "previousElem":
|
||||
newIndex = focusedIndex - 1;
|
||||
break;
|
||||
case "nextElem":
|
||||
newIndex = focusedIndex + 1;
|
||||
break;
|
||||
case "previousColumn":
|
||||
if (focusedIndex % this.maxIconNumber) {
|
||||
// app is not the first one on its line
|
||||
newIndex = focusedIndex - 1;
|
||||
} else {
|
||||
newIndex =
|
||||
focusedIndex + Math.min(lastIndex - focusedIndex, this.maxIconNumber - 1);
|
||||
}
|
||||
break;
|
||||
case "nextColumn":
|
||||
if (focusedIndex === lastIndex || (focusedIndex + 1) % this.maxIconNumber === 0) {
|
||||
// app is the last one on its line
|
||||
newIndex = (currentLine - 1) * this.maxIconNumber;
|
||||
} else {
|
||||
newIndex = focusedIndex + 1;
|
||||
}
|
||||
break;
|
||||
case "previousLine":
|
||||
if (currentLine === 1) {
|
||||
newIndex = focusedIndex + (lineNumber - 1) * this.maxIconNumber;
|
||||
if (newIndex > lastIndex) {
|
||||
newIndex = lastIndex;
|
||||
}
|
||||
} else {
|
||||
// we go to the previous line on same column
|
||||
newIndex = focusedIndex - this.maxIconNumber;
|
||||
}
|
||||
break;
|
||||
case "nextLine":
|
||||
if (currentLine === lineNumber) {
|
||||
newIndex = focusedIndex % this.maxIconNumber;
|
||||
} else {
|
||||
// we go to the next line on the closest column
|
||||
newIndex =
|
||||
focusedIndex + Math.min(this.maxIconNumber, lastIndex - focusedIndex);
|
||||
}
|
||||
break;
|
||||
}
|
||||
// if newIndex is out of bounds -> normalize it
|
||||
if (newIndex < 0) {
|
||||
newIndex = lastIndex;
|
||||
} else if (newIndex > lastIndex) {
|
||||
newIndex = 0;
|
||||
}
|
||||
this.state.focusedIndex = newIndex;
|
||||
}
|
||||
|
||||
_focusInput() {
|
||||
if (!this.env.isSmall && this.inputRef.el) {
|
||||
this.inputRef.el.focus({ preventScroll: true });
|
||||
}
|
||||
}
|
||||
|
||||
_enableAppsSorting() {
|
||||
return true;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @param {Object} params
|
||||
* @param {HTMLElement} params.element
|
||||
* @param {HTMLElement} params.previous
|
||||
*/
|
||||
_sortAppDrop({ element, previous }) {
|
||||
const order = this.props.apps.map((app) => app.xmlid);
|
||||
const elementId = element.children[0].dataset.menuXmlid;
|
||||
const elementIndex = order.indexOf(elementId);
|
||||
// first remove dragged element
|
||||
order.splice(elementIndex, 1);
|
||||
if (previous) {
|
||||
const prevIndex = order.indexOf(previous.children[0].dataset.menuXmlid);
|
||||
// insert dragged element after previous element
|
||||
order.splice(prevIndex + 1, 0, elementId);
|
||||
} else {
|
||||
// insert dragged element at beginning if no previous element
|
||||
order.splice(0, 0, elementId);
|
||||
}
|
||||
// apply new order
|
||||
this.props.reorderApps(order);
|
||||
user.setUserSettings("homemenu_config", JSON.stringify(order));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} params
|
||||
* @param {HTMLElement} params.element
|
||||
*/
|
||||
_sortStart({ element, addClass }) {
|
||||
addClass(element.children[0], "o_dragged_app");
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {Object} app
|
||||
*/
|
||||
_onAppClick(app) {
|
||||
this._openMenu(app);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_registerHotkeys() {
|
||||
const hotkeys = [
|
||||
["ArrowDown", () => this._updateFocusedIndex("nextLine")],
|
||||
["ArrowRight", () => this._updateFocusedIndex("nextColumn")],
|
||||
["ArrowUp", () => this._updateFocusedIndex("previousLine")],
|
||||
["ArrowLeft", () => this._updateFocusedIndex("previousColumn")],
|
||||
["Tab", () => this._updateFocusedIndex("nextElem")],
|
||||
["shift+Tab", () => this._updateFocusedIndex("previousElem")],
|
||||
[
|
||||
"Enter",
|
||||
() => {
|
||||
const menu = this.displayedApps[this.state.focusedIndex];
|
||||
if (menu) {
|
||||
this._openMenu(menu);
|
||||
}
|
||||
},
|
||||
],
|
||||
["Escape", () => this.homeMenuService.toggle(false)],
|
||||
];
|
||||
hotkeys.forEach((hotkey) => {
|
||||
useHotkey(...hotkey, {
|
||||
allowRepeat: true,
|
||||
});
|
||||
});
|
||||
useExternalListener(window, "keydown", this._onKeydownFocusInput);
|
||||
}
|
||||
|
||||
_onKeydownFocusInput() {
|
||||
if (
|
||||
document.activeElement !== this.inputRef.el &&
|
||||
this.ui.activeElement === document &&
|
||||
!["TEXTAREA", "INPUT"].includes(document.activeElement.tagName)
|
||||
) {
|
||||
this._focusInput();
|
||||
}
|
||||
}
|
||||
|
||||
_onInputSearch() {
|
||||
const onClose = () => {
|
||||
this._focusInput();
|
||||
this.inputRef.el.value = "";
|
||||
};
|
||||
const searchValue = this.compositionStart ? "/" : `/${this.inputRef.el.value.trim()}`;
|
||||
this.compositionStart = false;
|
||||
this.command.openMainPalette({ searchValue, FooterComponent }, onClose);
|
||||
}
|
||||
|
||||
_onInputBlur() {
|
||||
if (hasTouch()) {
|
||||
return;
|
||||
}
|
||||
// if we blur search input to focus on body (eg. click on any
|
||||
// non-interactive element) restore focus to avoid IME input issue
|
||||
setTimeout(() => {
|
||||
if (document.activeElement === document.body && this.ui.activeElement === document) {
|
||||
this._focusInput();
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
_onCompositionStart() {
|
||||
this.compositionStart = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
.o_home_menu_background {
|
||||
// 'Home menu background' design is shared with enterprise login
|
||||
// screens and it's located in './home_menu_background.scss'
|
||||
|
||||
// When applied on webclient (note: we do not specify the webclient class
|
||||
// here to avoid breaking studio custom style)
|
||||
&:not(.o_home_menu_background_custom):not(.o_in_studio) .o_main_navbar {
|
||||
background: transparent;
|
||||
border-bottom-color: transparent;
|
||||
|
||||
.o_dropdown_active,
|
||||
> ul > li.show > a {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.o_home_menu_background_custom .o_home_menu {
|
||||
background: {
|
||||
size: cover;
|
||||
repeat: no-repeat;
|
||||
position: center;
|
||||
}
|
||||
}
|
||||
|
||||
.o_menu_systray {
|
||||
@include print-variable(o-navbar-badge-bg, $o-navbar-badge-bg);
|
||||
}
|
||||
}
|
||||
|
||||
.o_home_menu {
|
||||
font-size: $font-size-base;
|
||||
|
||||
.container {
|
||||
@include media-breakpoint-up(md) {
|
||||
max-width: $o-home-menu-container-size !important;
|
||||
}
|
||||
}
|
||||
|
||||
.o_app {
|
||||
.o_app_icon {
|
||||
width: $o-home-menu-app-icon-max-width;
|
||||
aspect-ratio: 1;
|
||||
padding: 10px;
|
||||
background-color: var(--AppSwitcherIcon-background, rgba(#fff, 1));
|
||||
object-fit: cover;
|
||||
transform-origin: center bottom;
|
||||
transition: box-shadow ease-in 0.1s, transform ease-in 0.1s;
|
||||
box-shadow: var(--AppSwitcherIcon-inset-shadow, inset 0 0 0 1px rgba(0,0,0, .2)),
|
||||
0 1px 1px rgba(#000, .02),
|
||||
0 2px 2px rgba(#000, .02),
|
||||
0 4px 4px rgba(#000, .02),
|
||||
0 8px 8px rgba(#000, .02),
|
||||
0 16px 16px rgba(#000, .02);
|
||||
|
||||
.fa {
|
||||
font-size: $o-home-menu-app-icon-max-width * 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover .o_app_icon {
|
||||
box-shadow: var(--AppSwitcherIcon-inset-shadow, inset 0 0 0 1px rgba(0,0,0, .2)),
|
||||
0 2px 2px rgba(#000, .03),
|
||||
0 4px 4px rgba(#000, .03),
|
||||
0 8px 8px rgba(#000, .03),
|
||||
0 12px 12px rgba(#000, .03),
|
||||
0 24px 24px rgba(#000, .03);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
&:active .o_app_icon {
|
||||
transform: translateY(-2px) scale(.98);
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.o_caption {
|
||||
color: var(--homeMenuCaption-color, #{$o-home-menu-caption-color});
|
||||
text-shadow: $o-home-menu-caption-shadow;
|
||||
}
|
||||
|
||||
&.o_focused {
|
||||
background: $component-active-bg;
|
||||
outline: 1px solid $o-action;
|
||||
border-radius: $border-radius;
|
||||
}
|
||||
}
|
||||
|
||||
.o_dragged_app {
|
||||
transition: transform 0.5s;
|
||||
transform: rotate(6deg);
|
||||
.o_app_icon {
|
||||
box-shadow: 0 8px 15px -10px black;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
}
|
||||
|
||||
// iOS iPhone list layout due to Apple AppStore review
|
||||
@include media-breakpoint-down(md) {
|
||||
&.o_ios_app {
|
||||
.o_apps {
|
||||
flex-direction: column;
|
||||
font-size: $o-home-menu-font-size-base * 1.25;
|
||||
margin-top: map-get($spacers, 1);
|
||||
padding: 0 map-get($spacers, 2);
|
||||
|
||||
> *, .o_app {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.o_app {
|
||||
flex-direction: row !important;
|
||||
justify-content: initial !important;
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
padding: map-get($spacers, 3) map-get($spacers, 4) !important;
|
||||
}
|
||||
|
||||
.o_app_icon {
|
||||
width: $o-home-menu-app-icon-max-width * 0.75;
|
||||
height: $o-home-menu-app-icon-max-width * 0.75;
|
||||
margin-right: map-get($spacers, 4);
|
||||
}
|
||||
|
||||
.o_caption {
|
||||
text-align: start !important;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.o_ios_app) .o_caption {
|
||||
font-size: $font-size-sm;
|
||||
font-weight: $font-weight-bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_home_menu_background_custom {
|
||||
.o_home_menu .o_app .o_caption {
|
||||
color: $o-home-menu-custom-caption-color;
|
||||
text-shadow: $o-home-menu-custom-caption-shadow;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
// = Home Menu Variables
|
||||
// ============================================================================
|
||||
// No CSS hacks, variables overrides only
|
||||
|
||||
$o-home-menu-caption-color: $o-black !default;
|
||||
$o-home-menu-caption-shadow: 0 1px 2px rgba(0, 0, 0, .75), 0 2px 5px rgba(0, 0, 0, .05), 0 0 5px rgba(0, 0, 0, .05) !default;
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
$o-home-menu-font-size-base: 1rem;
|
||||
$o-home-menu-container-size: 850px;
|
||||
$o-home-menu-app-icon-max-width: 70px;
|
||||
|
||||
$o-home-menu-caption-color: $o-gray-700 !default;
|
||||
$o-home-menu-caption-shadow: none !default;
|
||||
|
||||
$o-home-menu-custom-caption-color: #fff !default;
|
||||
$o-home-menu-custom-caption-shadow: 0 1px 2px rgba(0, 0, 0, .75), 0 2px 5px rgba(0, 0, 0, .05), 0 0 5px rgba(0, 0, 0, .05) !default;
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="ica_web_responsive.HomeMenu">
|
||||
<div t-ref="root" class="o_home_menu h-100 overflow-auto" t-att-class="{ o_ios_app: state.isIosApp }">
|
||||
<div class="container">
|
||||
<input t-ref="input" type="text" class="o_search_hidden visually-hidden w-auto" data-allow-hotkeys="true" t-on-input="_onInputSearch" t-on-blur="_onInputBlur" t-on-compositionstart="_onCompositionStart"
|
||||
role="combobox"
|
||||
t-att-aria-activedescendant="'result_app_' + state.focusedIndex"
|
||||
t-att-aria-expanded="displayedApps.length ? 'true' : 'false'"
|
||||
aria-autocomplete="list"
|
||||
aria-haspopup="listbox"
|
||||
/>
|
||||
<!-- When the subscription has expired, the expiration panel is show over the whole UI instead of here -->
|
||||
<div t-if="displayedApps.length" role="listbox" class="o_apps row user-select-none mt-5 mx-0">
|
||||
<div t-foreach="displayedApps" t-as="app" t-key="app.id" class="col-3 col-md-2 o_draggable mb-3 px-0">
|
||||
<a t-att-id="'result_app_' + app_index"
|
||||
role="option"
|
||||
t-att-aria-selected="state.focusedIndex === app_index ? 'true' : 'false'"
|
||||
class="o_app o_menuitem d-flex flex-column rounded-3 justify-content-start align-items-center w-100 p-1 p-md-2"
|
||||
t-att-class="{o_focused: state.focusedIndex === app_index}"
|
||||
t-att-data-menu-xmlid="app.xmlid"
|
||||
t-att-href="app.href"
|
||||
t-on-click.prevent="() => this._onAppClick(app)"
|
||||
>
|
||||
<img t-if="app.webIconData" class="o_app_icon rounded-3"
|
||||
t-attf-src="{{app.webIconData}}"
|
||||
/>
|
||||
<div t-else="" class="o_app_icon position-relative d-flex justify-content-center align-items-center p-2 rounded-3 ratio ratio-1x1"
|
||||
t-attf-style="background-color: {{app.webIcon.backgroundColor}};"
|
||||
>
|
||||
<i t-attf-class="{{app.webIcon.iconClass}} position-relative w-auto h-auto" t-attf-style="color: {{app.webIcon.color}};"/>
|
||||
</div>
|
||||
<div class="o_caption w-100 text-center text-truncate mt-2" t-esc="app.label or app.name"/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div t-elif="!displayedApps.length" id="result_menu_0" role="option" aria-selected="true" class="o_no_result">
|
||||
No result
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="ica_web_responsive.HomeMenu.CommandPalette.Footer">
|
||||
<span>
|
||||
<span class='fw-bolder text-primary'>TIP</span> — open me anywhere with <span t-esc="controlKey" class='fw-bolder text-primary'/> + <span class='fw-bolder text-primary'>K</span>
|
||||
</span>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
// = Home Menu Background
|
||||
// ============================================================================
|
||||
// No CSS hacks, variables overrides only
|
||||
|
||||
.o_home_menu_background {
|
||||
--homeMenu-bg-color: #000511;
|
||||
--homeMenu-bg-image: url("/ica_web_responsive/static/img/background-dark.jpg");
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
// Shared with web client and login screen
|
||||
.o_home_menu_background, .o_web_client.o_home_menu_background {
|
||||
background: {
|
||||
size: cover;
|
||||
attachment: fixed;
|
||||
color: var(--homeMenu-bg-color, #{$o-gray-200});
|
||||
image: var(--homeMenu-bg-image, url("/ica_web_responsive/static/img/background-light.svg"));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { user } from "@web/core/user";
|
||||
import { Mutex } from "@web/core/utils/concurrency";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { computeAppsAndMenuItems, reorderApps } from "@web/webclient/menus/menu_helpers";
|
||||
import {
|
||||
ControllerNotFoundError,
|
||||
standardActionServiceProps,
|
||||
} from "@web/webclient/actions/action_service";
|
||||
import { HomeMenu } from "./home_menu";
|
||||
|
||||
import { Component, onMounted, onWillUnmount, useState, reactive, xml } from "@odoo/owl";
|
||||
|
||||
export const homeMenuService = {
|
||||
dependencies: ["action"],
|
||||
start(env) {
|
||||
const state = reactive({
|
||||
hasHomeMenu: false, // true iff the HomeMenu is currently displayed
|
||||
hasBackgroundAction: false, // true iff there is an action behind the HomeMenu
|
||||
toggle,
|
||||
});
|
||||
const mutex = new Mutex(); // used to protect against concurrent toggling requests
|
||||
class HomeMenuAction extends Component {
|
||||
static components = { HomeMenu };
|
||||
static target = "current";
|
||||
static props = { ...standardActionServiceProps };
|
||||
static template = xml`<HomeMenu t-props="homeMenuProps"/>`;
|
||||
static displayName = _t("Home");
|
||||
|
||||
setup() {
|
||||
this.menus = useService("menu");
|
||||
const homemenuConfig = JSON.parse(user.settings?.homemenu_config || "null");
|
||||
const apps = useState(
|
||||
computeAppsAndMenuItems(this.menus.getMenuAsTree("root")).apps
|
||||
);
|
||||
if (homemenuConfig) {
|
||||
reorderApps(apps, homemenuConfig);
|
||||
}
|
||||
this.homeMenuProps = {
|
||||
apps: apps,
|
||||
reorderApps: (order) => {
|
||||
reorderApps(apps, order);
|
||||
},
|
||||
};
|
||||
onMounted(() => this.onMounted());
|
||||
onWillUnmount(this.onWillUnmount);
|
||||
}
|
||||
async onMounted() {
|
||||
const { breadcrumbs } = this.env.config;
|
||||
state.hasHomeMenu = true;
|
||||
state.hasBackgroundAction = breadcrumbs.length > 0;
|
||||
this.env.bus.trigger("HOME-MENU:TOGGLED");
|
||||
}
|
||||
onWillUnmount() {
|
||||
state.hasHomeMenu = false;
|
||||
state.hasBackgroundAction = false;
|
||||
this.env.bus.trigger("HOME-MENU:TOGGLED");
|
||||
}
|
||||
}
|
||||
|
||||
registry.category("actions").add("menu", HomeMenuAction);
|
||||
|
||||
env.bus.addEventListener("HOME-MENU:TOGGLED", () => {
|
||||
document.body.classList.toggle("o_home_menu_background", state.hasHomeMenu);
|
||||
});
|
||||
|
||||
async function toggle(show) {
|
||||
return mutex.exec(async () => {
|
||||
show = show === undefined ? !state.hasHomeMenu : Boolean(show);
|
||||
if (show !== state.hasHomeMenu) {
|
||||
if (show) {
|
||||
await env.services.action.doAction("menu");
|
||||
} else {
|
||||
try {
|
||||
await env.services.action.restore();
|
||||
} catch (err) {
|
||||
if (!(err instanceof ControllerNotFoundError)) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// hack: wait for a tick to ensure that the url has been updated before
|
||||
// switching again
|
||||
return new Promise((r) => setTimeout(r));
|
||||
});
|
||||
}
|
||||
|
||||
return state;
|
||||
},
|
||||
};
|
||||
|
||||
registry.category("services").add("home_menu", homeMenuService);
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
// = Navbar
|
||||
// ============================================================================
|
||||
// No CSS hacks, variables overrides only
|
||||
|
||||
.o_main_navbar {
|
||||
--o-navbar-badge-color: #{$black};
|
||||
--o-navbar-badge-text-shadow: none;
|
||||
--NavBar-menuToggle-color: #{$o-black};
|
||||
--NavBar-brand-color: #{$o-gray-800};
|
||||
--NavBar-entry-borderColor-active: #{darken($o-action, 10%)};
|
||||
--NavBar-entry-backgroundColor--active: #{mix($o-action, $o-gray-100, 15%)};
|
||||
--NavBar-entry-backgroundColor--hover: #{$o-gray-300};
|
||||
--NavBar-entry-backgroundColor--focus: #{$o-gray-300};
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { NavBar } from "@web/webclient/navbar/navbar";
|
||||
import { useService, useBus } from "@web/core/utils/hooks";
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { useState, useEffect, useRef } from "@odoo/owl";
|
||||
|
||||
export class EnterpriseNavBar extends NavBar {
|
||||
static template = "ica_web_responsive.EnterpriseNavBar";
|
||||
setup() {
|
||||
super.setup();
|
||||
this.hm = useState(useService("home_menu"));
|
||||
this.pwa = useService("pwa");
|
||||
this.menuAppsRef = useRef("menuApps");
|
||||
this.navRef = useRef("nav");
|
||||
this._busToggledCallback = () => this._updateMenuAppsIcon();
|
||||
useBus(this.env.bus, "HOME-MENU:TOGGLED", this._busToggledCallback);
|
||||
useEffect(() => this._updateMenuAppsIcon());
|
||||
}
|
||||
get hasBackgroundAction() {
|
||||
return this.hm.hasBackgroundAction;
|
||||
}
|
||||
get isInApp() {
|
||||
return !this.hm.hasHomeMenu;
|
||||
}
|
||||
|
||||
_openAppMenuSidebar() {
|
||||
if (this.hm.hasHomeMenu) {
|
||||
this.hm.toggle(false);
|
||||
} else {
|
||||
this.state.isAppMenuSidebarOpened = true;
|
||||
}
|
||||
}
|
||||
_updateMenuAppsIcon() {
|
||||
const menuAppsEl = this.menuAppsRef.el;
|
||||
menuAppsEl.classList.toggle("o_hidden", !this.isInApp && !this.hasBackgroundAction);
|
||||
menuAppsEl.classList.toggle(
|
||||
"o_menu_toggle_back",
|
||||
!this.isInApp && this.hasBackgroundAction
|
||||
);
|
||||
if (!this.isScopedApp) {
|
||||
const title =
|
||||
!this.isInApp && this.hasBackgroundAction ? _t("Previous view") : _t("Home menu");
|
||||
menuAppsEl.title = title;
|
||||
menuAppsEl.ariaLabel = title;
|
||||
}
|
||||
|
||||
const menuBrand = this.navRef.el.querySelector(".o_menu_brand");
|
||||
if (menuBrand) {
|
||||
menuBrand.classList.toggle("o_hidden", !this.isInApp);
|
||||
}
|
||||
|
||||
const menuBrandIcon = this.navRef.el.querySelector(".o_menu_brand_icon");
|
||||
if (menuBrandIcon) {
|
||||
menuBrandIcon.classList.toggle("o_hidden", !this.isInApp);
|
||||
}
|
||||
|
||||
const appSubMenus = this.appSubMenus.el;
|
||||
if (appSubMenus) {
|
||||
appSubMenus.classList.toggle("o_hidden", !this.isInApp);
|
||||
}
|
||||
|
||||
const breadcrumb = this.navRef.el.querySelector(".o_breadcrumb");
|
||||
if (breadcrumb) {
|
||||
breadcrumb.classList.toggle("o_hidden", !this.isInApp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
onAllAppsBtnClick() {
|
||||
super.onAllAppsBtnClick();
|
||||
this.hm.toggle(true);
|
||||
this._closeAppMenuSidebar();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
// = Main Navbar
|
||||
// ============================================================================
|
||||
.o_main_navbar {
|
||||
--NavBar-entry-color--active: #{$o-component-active-color};
|
||||
--NavBar-entry-borderColor-active: #{$o-component-active-border};
|
||||
--NavBar-entry-backgroundColor--active: #{$o-component-active-bg};
|
||||
--NavBar-entry-backgroundColor--hover: #{$o-gray-200};
|
||||
--NavBar-entry-backgroundColor--focus: #{$o-gray-200};
|
||||
|
||||
--Dropdown_menu-margin-y: #{map-get($spacers, 1)};
|
||||
|
||||
.o_menu_toggle {
|
||||
color: var(--NavBar-menuToggle-color, #{$o-brand-odoo});
|
||||
}
|
||||
}
|
||||
|
||||
// Ensuring SuperUser Design menu is not compressed in Enterprise
|
||||
// ============================================================================
|
||||
body.o_is_superuser .o_menu_systray {
|
||||
border-image-outset: map-get($border-widths, 5);
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
// = Navbar Variables
|
||||
// ============================================================================
|
||||
// No CSS hacks, variables overrides only
|
||||
|
||||
$o-navbar-background: $o-view-background-color !default;
|
||||
$o-navbar-entry-color: $o-gray-900 !default;
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
// = Enterprise Main Navbar Variables
|
||||
// ============================================================================
|
||||
$o-navbar-background: $o-white !default;
|
||||
$o-navbar-padding-v: 10px !default;
|
||||
$o-navbar-border-bottom: 0 !default;
|
||||
$o-navbar-font-size: $o-font-size-base !default;
|
||||
|
||||
$o-navbar-entry-margin-h: 1px !default;
|
||||
$o-navbar-entry-border-radius: $o-border-radius !default;
|
||||
$o-navbar-entry-color: $o-gray-800 !default;
|
||||
$o-navbar-entry-padding-h: .63em !default;
|
||||
|
||||
$o-navbar-entry-bg--hover: $o-gray-200 !default;
|
||||
$o-navbar-entry-color--hover: $o-gray-900 !default;
|
||||
|
||||
$o-navbar-entry-bg--active: unset !default;
|
||||
$o-navbar-entry-color--active: unset !default;
|
||||
|
||||
$o-navbar-brand-color: $o-gray-700 !default;
|
||||
|
||||
$o-navbar-badge-size: .7em !default;
|
||||
$o-navbar-badge-padding: 6px !default;
|
||||
$o-navbar-badge-bg: $o-danger !default;
|
||||
$o-navbar-badge-color: $o-white !default;
|
||||
$o-navbar-badge-text-shadow: none !default;
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="ica_web_responsive.EnterpriseNavBar" t-inherit="web.NavBar" t-inherit-mode="primary">
|
||||
<xpath expr="//nav" position="attributes">
|
||||
<attribute name="t-ref">nav</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//t[@t-call='web.NavBar.AppsMenu']" position="replace">
|
||||
<a t-if="!isScopedApp" href="/odoo" class="o_menu_toggle border-0" t-att-class="{'hasImage': currentApp?.webIconData}" accesskey="h" t-ref="menuApps" t-on-click.prevent="() => { env.isSmall ? this._openAppMenuSidebar() : this.hm.toggle() }">
|
||||
<t t-if="env.isSmall and !hm.hasHomeMenu">
|
||||
<t t-call="web.NavBar.AppsMenu.Sidebar"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<svg class="o_menu_toggle_icon pe-none" width="14px" height="14px" viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg">
|
||||
<g t-foreach="[0, 5, 10]" t-as="Y" t-att-id="'o_menu_toggle_row_' + Y_index" fill="currentColor" t-key="'o_menu_toggle_row_' + Y_index">
|
||||
<rect t-foreach="[0, 5, 10]" t-as="X" width="3" height="3" t-att-x="X" t-att-y="Y" t-key="'o_menu_toggle_cell_' + X_index"/>
|
||||
</g>
|
||||
</svg>
|
||||
<t t-if="!env.isSmall and currentApp">
|
||||
<img
|
||||
t-if="currentApp.webIconData"
|
||||
t-att-src="currentApp.webIconData"
|
||||
class="o_menu_brand_icon d-inline position-absolute start-0 h-100 ps-1 ms-2"
|
||||
t-att-alt="currentApp.name"
|
||||
t-ref="appIcon"/>
|
||||
<span t-esc="currentApp.name" class="o_menu_brand d-flex ms-3 pe-0"/>
|
||||
</t>
|
||||
</t>
|
||||
</a>
|
||||
<a t-else="" t-att-href="pwa.startUrl" class="o_menu_toggle" t-ref="menuApps">
|
||||
<img
|
||||
t-if="currentApp && currentApp.webIconData"
|
||||
t-att-src="currentApp.webIconData"
|
||||
class="o_menu_brand_icon d-none d-lg-inline position-absolute start-0 h-100 ps-1 ms-2"
|
||||
t-att-alt="currentApp.name"
|
||||
t-ref="appIcon"/>
|
||||
|
||||
<span
|
||||
t-if="currentApp"
|
||||
t-esc="currentApp.name"
|
||||
class="o_menu_brand d-none d-md-flex ps-4 pe-0"/>
|
||||
</a>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//DropdownItem[@t-esc='currentApp.name']" position="replace"/>
|
||||
</t>
|
||||
|
||||
<t t-name="ica_web_responsive.EnterpriseNavBar.SectionsMenu" t-inherit="web.NavBar.SectionsMenu" t-inherit-mode="extension">
|
||||
<xpath expr="//Dropdown/button" position="attributes">
|
||||
<attribute name="class" add="fw-normal" separator=" "/>
|
||||
</xpath>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
/** @odoo-module */
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import { Dialog } from "@web/core/dialog/dialog";
|
||||
import { useChildRef, useService } from "@web/core/utils/hooks";
|
||||
|
||||
import { Component, useExternalListener } from "@odoo/owl";
|
||||
|
||||
export class PromoteStudioDialog extends Component {
|
||||
static template = "ica_web_responsive.PromoteStudioDialog";
|
||||
static components = { Dialog };
|
||||
static props = {
|
||||
title: String,
|
||||
close: Function,
|
||||
};
|
||||
|
||||
setup() {
|
||||
this.ormService = useService("orm");
|
||||
this.uiService = useService("ui");
|
||||
|
||||
this.modalRef = useChildRef();
|
||||
|
||||
useExternalListener(window, "mousedown", this.onWindowMouseDown);
|
||||
}
|
||||
|
||||
async onClickInstallStudio() {
|
||||
this.disableClick = true;
|
||||
this.uiService.block();
|
||||
const modules = await this.ormService.searchRead(
|
||||
"ir.module.module",
|
||||
[["name", "=", "web_studio"]],
|
||||
["id"]
|
||||
);
|
||||
await this.ormService.call("ir.module.module", "button_immediate_install", [
|
||||
[modules[0].id],
|
||||
]);
|
||||
// on rpc call return, the framework unblocks the page
|
||||
// make sure to keep the page blocked until the reload ends.
|
||||
this.uiService.unblock();
|
||||
browser.localStorage.setItem("openStudioOnReload", "main");
|
||||
browser.location.reload();
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the dialog on outside click.
|
||||
*/
|
||||
onWindowMouseDown(ev) {
|
||||
const dialogContent = this.modalRef.el.querySelector(".modal-content");
|
||||
if (!this.disableClick && !dialogContent.contains(ev.target)) {
|
||||
this.props.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class PromoteStudioAutomationDialog extends PromoteStudioDialog {
|
||||
static template = "ica_web_responsive.PromoteStudioAutomationDialog";
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<template>
|
||||
|
||||
<t t-name="ica_web_responsive.PromoteStudioDialog">
|
||||
<Dialog title="props.title" modalRef="modalRef">
|
||||
<div class="modal-studio">
|
||||
<div id="studio_install_dialog"
|
||||
class="d-flex flex-row align-items-center flex-wrap">
|
||||
<div id="studio_dialog_pitch"
|
||||
class="w-100 w-md-50">
|
||||
<h4>Want to tailor-make your Odoo?</h4>
|
||||
<p>Unleash the power of Odoo Studio:</p>
|
||||
<ul>
|
||||
<li>Create automation rules</li>
|
||||
<li>Customize any screen</li>
|
||||
<li>Customize Reports</li>
|
||||
<li>Build new reports</li>
|
||||
<li>Build new apps from scratch</li>
|
||||
<li>Define webhooks</li>
|
||||
<li>and more!</li>
|
||||
</ul>
|
||||
<a role="button" class="btn btn-secondary btn-block"
|
||||
href="https://www.odoo.com/app/studio" target="_blank"> Learn More <i
|
||||
class="fa fa-external-link" />
|
||||
</a>
|
||||
</div>
|
||||
<div id="studio_video" class="o_video_embed w-100 w-md-50 ratio ratio-16x9">
|
||||
<iframe class="embed-responsive-item"
|
||||
t-attf-src="https://www.youtube.com/embed/xCvFZrrQq7k?autoplay=1"
|
||||
frameborder="0" allowfullscreen="true" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<t t-set-slot="footer">
|
||||
<button class="btn btn-primary btn-block o_install_studio"
|
||||
t-on-click.stop="onClickInstallStudio"
|
||||
data-tooltip="Install Odoo Studio and its dependencies">
|
||||
Start using Odoo Studio
|
||||
</button>
|
||||
|
||||
<button class="btn btn-secondary btn-block"
|
||||
t-on-click="props.close">
|
||||
Discard
|
||||
</button>
|
||||
</t>
|
||||
</div>
|
||||
</Dialog>
|
||||
</t>
|
||||
|
||||
|
||||
<t t-name="ica_web_responsive.PromoteStudioAutomationDialog"
|
||||
t-inherit="ica_web_responsive.PromoteStudioDialog" t-inherit-mode="primary">
|
||||
<xpath expr="//div[@id='studio_video']" position="replace">
|
||||
<img class="w-100 w-md-50"
|
||||
src="/ica_web_responsive/static/img/automation.svg" />
|
||||
</xpath>
|
||||
</t>
|
||||
|
||||
</template>
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { isDisplayStandalone } from "@web/core/browser/feature_detection";
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
import { BurgerMenu } from "@web/webclient/burger_menu/burger_menu";
|
||||
import { shareUrl } from "./share_url";
|
||||
|
||||
if (navigator.share && isDisplayStandalone()) {
|
||||
patch(BurgerMenu.prototype, {
|
||||
shareUrl,
|
||||
});
|
||||
|
||||
patch(BurgerMenu, {
|
||||
template: "ica_web_responsive.BurgerMenu",
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="ica_web_responsive.BurgerMenu" t-inherit="web.BurgerMenu" t-inherit-mode="primary">
|
||||
<xpath expr="//button[hasclass('o_sidebar_close')]" position="replace">
|
||||
<div class="d-flex align-items-center h-100 bg-transparent">
|
||||
<button class="o_burger_menu_share fa fa-share-alt btn border-0 fs-5 text-reset" aria-label="Share URL" title="Share URL" t-on-click.stop="shareUrl"/>
|
||||
<button class="o_sidebar_close oi oi-close btn border-0 fs-2 text-reset" aria-label="Close menu" title="Close menu" t-on-click.stop="_closeBurger"/>
|
||||
</div>
|
||||
</xpath>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { markup } from "@odoo/owl";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import { isDisplayStandalone } from "@web/core/browser/feature_detection";
|
||||
import { escape } from "@web/core/utils/strings";
|
||||
|
||||
export async function shareUrl() {
|
||||
await navigator
|
||||
.share({
|
||||
url: browser.location.href,
|
||||
title: document.title,
|
||||
})
|
||||
.catch((e) => {
|
||||
if (!(e instanceof DOMException && e.name === "AbortError")) {
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function shareUrlMenuItem(env) {
|
||||
const translatedText = _t("Share");
|
||||
return {
|
||||
type: "item",
|
||||
hide: env.isSmall || !isDisplayStandalone(),
|
||||
id: "share_url",
|
||||
description: markup(
|
||||
`<div class="d-flex align-items-center justify-content-between">
|
||||
<span>${escape(translatedText)}</span>
|
||||
<span class="fa fa-share-alt"></span>
|
||||
</div>`
|
||||
),
|
||||
callback: shareUrl,
|
||||
sequence: 25,
|
||||
};
|
||||
}
|
||||
|
||||
if (navigator.share) {
|
||||
registry.category("user_menuitems").add("share_url", shareUrlMenuItem);
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { WebClient } from "@web/webclient/webclient";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { EnterpriseNavBar } from "./navbar/navbar";
|
||||
|
||||
export class WebClientEnterprise extends WebClient {
|
||||
static components = {
|
||||
...WebClient.components,
|
||||
NavBar: EnterpriseNavBar,
|
||||
};
|
||||
setup() {
|
||||
super.setup();
|
||||
this.hm = useService("home_menu");
|
||||
}
|
||||
_loadDefaultApp() {
|
||||
return this.hm.toggle(true);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<template id="webclient_login" inherit_id="web.login_layout">
|
||||
<xpath expr="//t[@t-call='web.frontend_layout']/t[last()]" position="after">
|
||||
<t t-set="body_classname" t-value="'o_home_menu_background'"/>
|
||||
<t t-set="login_card_classes" t-value="'rounded-0 shadow-sm bg-white'"/>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="webclient_bootstrap" inherit_id="web.webclient_bootstrap">
|
||||
<xpath expr="//meta[@name='theme-color']" position="replace">
|
||||
<meta name="theme-color" t-att-content="'#242733' if request.cookies.get('color_scheme') == 'dark' else '#714B67'"/>
|
||||
</xpath>
|
||||
</template>
|
||||
</odoo>
|
||||
Loading…
Reference in New Issue