add backend_theme from openworx.nl
This commit is contained in:
parent
17ecc2e4a0
commit
46d66821ec
70 changed files with 5413 additions and 0 deletions
763
web_responsive/static/src/css/web_responsive.scss
Normal file
763
web_responsive/static/src/css/web_responsive.scss
Normal file
|
@ -0,0 +1,763 @@
|
|||
/* Copyright 2018 Tecnativa - Jairo Llopis
|
||||
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
|
||||
|
||||
$chatter_zone_width: 35%;
|
||||
|
||||
@mixin full-screen-dropdown {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100vh - #{$o-navbar-height});
|
||||
max-height: calc(100vh - #{$o-navbar-height});
|
||||
position: fixed;
|
||||
width: 100vw;
|
||||
z-index: 100;
|
||||
// Inline style will override our `top`, so we need !important here
|
||||
top: $o-navbar-height !important;
|
||||
transform: none !important;
|
||||
}
|
||||
|
||||
// Support for long titles
|
||||
@include media-breakpoint-up(md) {
|
||||
.o_form_view .oe_button_box + .oe_title,
|
||||
.o_form_view .oe_button_box + .oe_avatar + .oe_title {
|
||||
/* Button-box has a hardcoded width of 132px per button and have three columns */
|
||||
width: calc(100% - 450px);
|
||||
}
|
||||
}
|
||||
|
||||
// Main navbar (with apps menu, user menu, debug menu...)
|
||||
@include media-breakpoint-down(sm) {
|
||||
.o_main_navbar {
|
||||
// This allows to have a sane layout for mobiles
|
||||
display: flex;
|
||||
|
||||
// Remove clutter to only have small icons that fit in a small screen
|
||||
> .dropdown {
|
||||
display: flex;
|
||||
|
||||
.navbar-toggler {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.o_menu_sections,
|
||||
.o_menu_systray,
|
||||
{
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Whitespace before systray icons
|
||||
.o_menu_systray {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
// Hide big things
|
||||
.o_menu_brand,
|
||||
.o_menu_sections,
|
||||
.oe_topbar_name,
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Fix toggler button padding
|
||||
.o-menu-toggle {
|
||||
cursor: pointer;
|
||||
padding: 0 $o-horizontal-padding;
|
||||
}
|
||||
|
||||
// Custom fullscreen layout when showing submenus
|
||||
.o_menu_sections.show {
|
||||
@include full-screen-dropdown();
|
||||
background-color: $dropdown-bg;
|
||||
overflow: auto;
|
||||
|
||||
.show {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.dropdown-menu {
|
||||
margin-left: 1rem;
|
||||
min-width: auto;
|
||||
width: calc(100vw - 2rem);
|
||||
}
|
||||
}
|
||||
|
||||
> li,
|
||||
.o_menu_entry_lvl_1,
|
||||
.o_menu_header_lvl_1,
|
||||
{
|
||||
// Homogeneous background color
|
||||
background-color: $dropdown-bg;
|
||||
color: $dropdown-link-color;
|
||||
|
||||
&:hover {
|
||||
background-color: $dropdown-link-hover-bg;
|
||||
color: $dropdown-link-hover-color;
|
||||
}
|
||||
|
||||
// Disable the .o-no-caret class effect, to display the caret
|
||||
&.o-no-caret::after {
|
||||
content: "";
|
||||
}
|
||||
|
||||
// Fix a strange glitch leaving headers invisible
|
||||
.dropdown-header {
|
||||
color: $dropdown-header-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Custom fullscreen layout for systray items
|
||||
.o_mail_systray_dropdown.show {
|
||||
@include full-screen-dropdown();
|
||||
|
||||
// Fix stretchy images
|
||||
.o_mail_preview_image {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
img {
|
||||
display: block;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Higher height for dropdown items, for those with sausage fingers
|
||||
.dropdown-menu .dropdown-item {
|
||||
padding: {
|
||||
bottom: 0.5rem;
|
||||
top: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Iconized full screen apps menu
|
||||
.o_menu_apps {
|
||||
|
||||
.search-input:focus {
|
||||
border-color: $o-brand-primary;
|
||||
}
|
||||
|
||||
.dropdown-menu.show {
|
||||
@include full-screen-dropdown();
|
||||
|
||||
// Display apps in a grid
|
||||
align-content: flex-start;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
|
||||
@include media-breakpoint-up(lg) {
|
||||
padding: {
|
||||
left: 20vw;
|
||||
right: 20vw;
|
||||
}
|
||||
}
|
||||
|
||||
.o_app {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
|
||||
// Size depends on screen
|
||||
width: 33.33333333%;
|
||||
@include media-breakpoint-up(sm) {
|
||||
width: 25%;
|
||||
}
|
||||
@include media-breakpoint-up(md) {
|
||||
width: 16.6666666%;
|
||||
}
|
||||
}
|
||||
|
||||
// Hide app icons when searching
|
||||
.has-results ~ .o_app {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.o-app-icon {
|
||||
height: auto;
|
||||
max-width: 7rem;
|
||||
}
|
||||
|
||||
// Search input for menus
|
||||
.form-row {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.o-menu-search-result {
|
||||
align-items: center;
|
||||
background-position: left;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
padding-left: 3rem;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
// Allow to scroll only on results, keeping static search box above
|
||||
.search-container.has-results {
|
||||
height: 100%;
|
||||
|
||||
.search-input {
|
||||
height: 3em;
|
||||
}
|
||||
|
||||
.search-results {
|
||||
height: calc(100% - 3em);
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Scroll all but top bar
|
||||
html .o_web_client .o_main .o_main_content {
|
||||
@include media-breakpoint-down(sm) {
|
||||
overflow: auto;
|
||||
|
||||
.o_content {
|
||||
overflow: initial;
|
||||
}
|
||||
}
|
||||
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
// Control panel (breadcrumbs, search box, buttons...)
|
||||
.o_control_panel {
|
||||
.o_search_options {
|
||||
flex-grow: 1;
|
||||
|
||||
> div {
|
||||
width: 100%;
|
||||
|
||||
|
||||
> .btn-group {
|
||||
max-width: 33%;
|
||||
|
||||
.o_dropdown_toggler_btn {
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_filters_menu {
|
||||
.o_menu_item {
|
||||
@include o-search-options-dropdown-custom-item;
|
||||
|
||||
a {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_pager_counter {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
.o_control_panel {
|
||||
// Arrange buttons to use space better
|
||||
.breadcrumb,
|
||||
.o_cp_buttons,
|
||||
.o_cp_left,
|
||||
.o_cp_right,
|
||||
.o_cp_searchview,
|
||||
{
|
||||
flex: 1 1 100%;
|
||||
@include media-breakpoint-up(md) {
|
||||
flex-basis: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
flex-basis: 80%;
|
||||
}
|
||||
|
||||
.o_cp_searchview,
|
||||
.o_cp_right,
|
||||
{
|
||||
flex-basis: 10%;
|
||||
}
|
||||
|
||||
.o_cp_left {
|
||||
flex-basis: 50%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.o_cp_pager {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
// Hide all but 2 last breadcrumbs, and render 2nd-to-last as arrow
|
||||
.breadcrumb-item {
|
||||
&:not(.active) {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: none;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
&:nth-last-of-type(1n+3) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:nth-last-of-type(2) {
|
||||
&::before {
|
||||
color: var(--primary);
|
||||
content: "\f048"; // .fa-step-backward
|
||||
cursor: pointer;
|
||||
font-family: FontAwesome;
|
||||
}
|
||||
|
||||
a {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ellipsize long breadcrumbs
|
||||
.breadcrumb {
|
||||
max-width: 100%;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
// Empty sidebar should not break layout
|
||||
.o_cp_sidebar:blank {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// In case you install `mail`, there is a mess on BS vs inline styles
|
||||
// we need to fix
|
||||
.o_cp_buttons .btn.d-block:not(.d-none) {
|
||||
display: inline-block !important;
|
||||
}
|
||||
|
||||
// Dropdown with buttons to switch the view type
|
||||
.o_cp_switch_buttons.show {
|
||||
.dropdown-menu {
|
||||
align-content: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
padding: 0;
|
||||
|
||||
.btn {
|
||||
border: {
|
||||
bottom: 0;
|
||||
radius: 0;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Normal views
|
||||
.o_content, .modal-content {
|
||||
max-width: 100%;
|
||||
|
||||
// Form views
|
||||
.o_form_view {
|
||||
.o_form_sheet {
|
||||
max-width: calc(100% - 32px);
|
||||
overflow-x: auto;
|
||||
|
||||
.oe_button_box {
|
||||
.o_dropdown_more {
|
||||
padding: 0.1em;
|
||||
width: min-content;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sticky statusbar
|
||||
.o_form_statusbar {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
// Support for long title (with ellipsis)
|
||||
.oe_title {
|
||||
span.o_field_widget {
|
||||
&:not(.oe_inline) {
|
||||
max-width: 100%;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
width: initial;
|
||||
|
||||
&:active {
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
min-width: auto;
|
||||
|
||||
// Avoid overflow on forms with title and/or button box
|
||||
.oe_button_box,
|
||||
.oe_title,
|
||||
{
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.oe_button_box + .oe_title,
|
||||
.oe_button_box + .oe_avatar + .oe_title {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// Avoid overflow on modals
|
||||
.o_form_sheet {
|
||||
min-width: auto;
|
||||
}
|
||||
|
||||
// Render website inputs properly in phones
|
||||
.o_group .o_field_widget.o_text_overflow {
|
||||
// Overrides another !important
|
||||
width: auto !important;
|
||||
}
|
||||
|
||||
// Make all input groups vertical
|
||||
.o_group_col_6 {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// Statusbar buttons dropdown for mobiles
|
||||
.o_statusbar_buttons_dropdown {
|
||||
border: {
|
||||
bottom: 0;
|
||||
radius: 0;
|
||||
top: 0;
|
||||
}
|
||||
height: 100%;
|
||||
}
|
||||
.o_statusbar_buttons > .btn {
|
||||
border-radius: 0;
|
||||
border: 0;
|
||||
width: 100%;
|
||||
margin-bottom: 0.2rem;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.o_statusbar_status {
|
||||
// Arrow from rightmost button exceeds allowed width
|
||||
.o_arrow_button:first-child::before {
|
||||
content: none;
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
// Full width in form sheets
|
||||
.o_form_sheet,
|
||||
.oe_chatter,
|
||||
{
|
||||
min-width: auto;
|
||||
max-width: 98%;
|
||||
}
|
||||
|
||||
// Settings pages
|
||||
.app_settings_block {
|
||||
.row {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.o_chatter {
|
||||
// Display send button on small screens
|
||||
.o_thread_composer {
|
||||
padding-left: $o-mail-thread-avatar-size*0.5;
|
||||
|
||||
.o_composer_button_send {
|
||||
display: initial !important; // Forced in core
|
||||
}
|
||||
.o_chatter_avatar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.o_chatter_topbar {
|
||||
height: auto;
|
||||
flex-wrap: wrap-reverse;
|
||||
|
||||
> .o_topbar_right_area {
|
||||
flex: 1 0 auto;
|
||||
flex-wrap: wrap;
|
||||
max-width: 100%;
|
||||
|
||||
// Display followers on small screens
|
||||
.o_followers {
|
||||
display: initial !important; // Forced in core
|
||||
|
||||
@include media-breakpoint-down(xs) {
|
||||
padding-bottom: 50px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sided chatter, if user wants
|
||||
.o_chatter_position_sided & {
|
||||
@include media-breakpoint-up(lg) {
|
||||
.o_form_view:not(.o_form_nosheet) {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
height: 100%;
|
||||
|
||||
.o_form_sheet_bg {
|
||||
flex: 1 1 auto;
|
||||
overflow: auto;
|
||||
|
||||
> .o_form_sheet {
|
||||
min-width: unset;
|
||||
|
||||
.oe_subtotal_footer .popover {
|
||||
/* This is an inaccurate workaround for a problem with how Odoo places the popover
|
||||
* See: https://github.com/odoo/odoo/blob/12.0/addons/account/static/src/js/account_payment_field.js#L83
|
||||
*/
|
||||
position: fixed !important;
|
||||
top: 11.4em !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_chatter {
|
||||
border-left: 1px solid gray('400');
|
||||
flex: 0 0 $chatter_zone_width;
|
||||
max-width: initial;
|
||||
min-width: initial;
|
||||
overflow: auto;
|
||||
|
||||
.o_chatter_header_container {
|
||||
padding-top: $grid-gutter-width * 0.5;
|
||||
top: 0;
|
||||
position: sticky;
|
||||
background-color: $o-view-background-color;
|
||||
z-index: 1;
|
||||
|
||||
.o_chatter_topbar {
|
||||
margin-top: 0;
|
||||
flex-wrap: wrap;
|
||||
height: auto;
|
||||
button:last-of-type {
|
||||
flex: 1 0 auto;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.o_topbar_right_area {
|
||||
order: -10;
|
||||
flex: 0 1 100%;
|
||||
border-bottom-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
// Scrollable input text to avoid hide conversation & buttons
|
||||
.o_composer_text_field {
|
||||
max-height: 120px;
|
||||
overflow-y: auto !important; /* Forced because Odoo uses inline style */
|
||||
}
|
||||
.o_attachments_list {
|
||||
overflow: auto;
|
||||
max-height: $o-mail-attachment-image-size * 3;
|
||||
margin-top: 0.4em;
|
||||
}
|
||||
.o_attachments_previews {
|
||||
overflow: auto;
|
||||
max-height: $o-mail-attachment-image-size * 6;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sticky Header & Footer in List View
|
||||
.table-responsive {
|
||||
overflow-x: initial;
|
||||
|
||||
.o_list_view {
|
||||
// th & td are here for compatibility with chrome
|
||||
thead, thead tr:nth-child(1) th {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
thead tr:nth-child(1) th {
|
||||
background-color: $o-list-footer-bg-color;
|
||||
}
|
||||
tfoot, tfoot tr:nth-child(1) td {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
}
|
||||
tfoot tr:nth-child(1) td {
|
||||
background-color: $o-list-footer-bg-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Big checkboxes
|
||||
.o_list_view {
|
||||
.o_list_record_selector {
|
||||
.custom-checkbox {
|
||||
margin-right: 10px;
|
||||
.custom-control-label {
|
||||
top: -19px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.o_data_cell:not(.o_boolean_toggle_cell) {
|
||||
.custom-checkbox .custom-control-label {
|
||||
top: -6px;
|
||||
}
|
||||
}
|
||||
.custom-checkbox {
|
||||
margin-right: 10px;
|
||||
|
||||
.custom-control-label {
|
||||
&::after {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
&::before {
|
||||
outline: none !important;
|
||||
border: 1px solid #4c4c4c;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Waiting Cursor
|
||||
.oe_wait {
|
||||
cursor: progress;
|
||||
}
|
||||
|
||||
// Document Viewer
|
||||
.o_web_client.o_chatter_position_sided {
|
||||
.o_modal_fullscreen.o_document_viewer {
|
||||
// On-top of navbar
|
||||
z-index: 10;
|
||||
|
||||
&.o_responsive_document_viewer {
|
||||
/* Show sided viewer on large screens */
|
||||
@include media-breakpoint-up(lg) {
|
||||
width: $chatter_zone_width;
|
||||
margin-left: auto;
|
||||
|
||||
/* Show/Hide control buttons (next, prev, etc..) */
|
||||
&:hover .arrow, &:hover .o_viewer_toolbar {
|
||||
display: unset;
|
||||
}
|
||||
.arrow, .o_viewer_toolbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.o_viewer_img_wrapper {
|
||||
position: relative;
|
||||
|
||||
.o_viewer_pdf {
|
||||
width: 95%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_minimize_btn {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
&:not(.o_responsive_document_viewer) {
|
||||
.o_maximize_btn {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@include media-breakpoint-down(lg) {
|
||||
.o_minimize_btn, .o_maximize_btn {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Max/Min buttons only are usefull in sided mode */
|
||||
.o_web_client:not(.o_chatter_position_sided) {
|
||||
.o_minimize_btn, .o_maximize_btn {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
// Apply improvements for Document Viewer on all modes
|
||||
.o_modal_fullscreen .o_viewer_content {
|
||||
.o_viewer-header {
|
||||
.o_image_caption {
|
||||
display: contents;
|
||||
|
||||
.o_split_pdf_area {
|
||||
padding: 16px;
|
||||
margin-bottom: 0;
|
||||
position: relative;
|
||||
top: -5px;
|
||||
|
||||
label {
|
||||
margin-bottom: unset;
|
||||
}
|
||||
|
||||
.o_page_number_input {
|
||||
border: 1px solid white;
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Hide 'split pdf' feature on small screens */
|
||||
@include media-breakpoint-down(xs) {
|
||||
.o_split_pdf_area {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now uses a container to have more buttons
|
||||
.o_buttons {
|
||||
min-width: 35px;
|
||||
text-align: right;
|
||||
|
||||
// Now close button ('X') it's a fa-icon
|
||||
> .o_close_btn {
|
||||
top: unset;
|
||||
left: unset;
|
||||
bottom: unset;
|
||||
right: unset;
|
||||
font-size: unset;
|
||||
font-weight: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_viewer_toolbar {
|
||||
width: max-content;
|
||||
}
|
||||
}
|
561
web_responsive/static/src/js/web_responsive.js
Normal file
561
web_responsive/static/src/js/web_responsive.js
Normal file
|
@ -0,0 +1,561 @@
|
|||
/* Copyright 2018 Tecnativa - Jairo Llopis
|
||||
* License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). */
|
||||
|
||||
odoo.define('web_responsive', function (require) {
|
||||
'use strict';
|
||||
|
||||
var ActionManager = require('web.ActionManager');
|
||||
var AbstractWebClient = require("web.AbstractWebClient");
|
||||
var AppsMenu = require("web.AppsMenu");
|
||||
var BasicController = require('web.BasicController');
|
||||
var config = require("web.config");
|
||||
var core = require("web.core");
|
||||
var FormRenderer = require('web.FormRenderer');
|
||||
var Menu = require("web.Menu");
|
||||
var RelationalFields = require('web.relational_fields');
|
||||
var Chatter = require('mail.Chatter');
|
||||
var DocumentViewer = require('mail.DocumentViewer');
|
||||
var ListRenderer = require('web.ListRenderer');
|
||||
|
||||
|
||||
/* Hide AppDrawer in desktop and mobile modes.
|
||||
* To avoid delays in pages with a lot of DOM nodes we make
|
||||
* sub-groups' with 'querySelector' to improve the performance.
|
||||
*/
|
||||
function closeAppDrawer () {
|
||||
_.defer(function () {
|
||||
// Need close AppDrawer?
|
||||
var menu_apps_dropdown = document.querySelector(
|
||||
'.o_menu_apps .dropdown');
|
||||
$(menu_apps_dropdown).has('.dropdown-menu.show')
|
||||
.find('> a').dropdown('toggle');
|
||||
// Need close Sections Menu?
|
||||
// TODO: Change to 'hide' in modern Bootstrap >4.1
|
||||
var menu_sections = document.querySelector(
|
||||
'.o_menu_sections li.show .dropdown-toggle');
|
||||
$(menu_sections).dropdown('toggle');
|
||||
// Need close Mobile?
|
||||
var menu_sections_mobile = document.querySelector(
|
||||
'.o_menu_sections.show');
|
||||
$(menu_sections_mobile).collapse('hide');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduce menu data to a searchable format understandable by fuzzy.js
|
||||
*
|
||||
* `AppsMenu.init()` gets `menuData` in a format similar to this (only
|
||||
* relevant data is shown):
|
||||
*
|
||||
* ```js
|
||||
* {
|
||||
* [...],
|
||||
* children: [
|
||||
* // This is a menu entry:
|
||||
* {
|
||||
* action: "ir.actions.client,94", // Or `false`
|
||||
* children: [... similar to above "children" key],
|
||||
* name: "Actions",
|
||||
* parent_id: [146, "Settings/Technical/Actions"], // Or `false`
|
||||
* },
|
||||
* ...
|
||||
* ]
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* This format is very hard to process to search matches, and it would
|
||||
* slow down the search algorithm, so we reduce it with this method to be
|
||||
* able to later implement a simpler search.
|
||||
*
|
||||
* @param {Object} memo
|
||||
* Reference to current result object, passed on recursive calls.
|
||||
*
|
||||
* @param {Object} menu
|
||||
* A menu entry, as described above.
|
||||
*
|
||||
* @returns {Object}
|
||||
* Reduced object, without entries that have no action, and with a
|
||||
* format like this:
|
||||
*
|
||||
* ```js
|
||||
* {
|
||||
* "Discuss": {Menu entry Object},
|
||||
* "Settings": {Menu entry Object},
|
||||
* "Settings/Technical/Actions/Actions": {Menu entry Object},
|
||||
* ...
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
function findNames (memo, menu) {
|
||||
if (menu.action) {
|
||||
var key = menu.parent_id ? menu.parent_id[1] + "/" : "";
|
||||
memo[key + menu.name] = menu;
|
||||
}
|
||||
if (menu.children.length) {
|
||||
_.reduce(menu.children, findNames, memo);
|
||||
}
|
||||
return memo;
|
||||
}
|
||||
|
||||
AppsMenu.include({
|
||||
events: _.extend({
|
||||
"keydown .search-input input": "_searchResultsNavigate",
|
||||
"input .search-input input": "_searchMenusSchedule",
|
||||
"click .o-menu-search-result": "_searchResultChosen",
|
||||
"shown.bs.dropdown": "_searchFocus",
|
||||
"hidden.bs.dropdown": "_searchReset",
|
||||
"hide.bs.dropdown": "_hideAppsMenu",
|
||||
}, AppsMenu.prototype.events),
|
||||
|
||||
/**
|
||||
* Rescue some menu data stripped out in original method.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
init: function (parent, menuData) {
|
||||
this._super.apply(this, arguments);
|
||||
// Keep base64 icon for main menus
|
||||
for (var n in this._apps) {
|
||||
this._apps[n].web_icon_data =
|
||||
menuData.children[n].web_icon_data;
|
||||
}
|
||||
// Store menu data in a format searchable by fuzzy.js
|
||||
this._searchableMenus = _.reduce(
|
||||
menuData.children,
|
||||
findNames,
|
||||
{}
|
||||
);
|
||||
// Search only after timeout, for fast typers
|
||||
this._search_def = $.Deferred();
|
||||
},
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
start: function () {
|
||||
this.$search_container = this.$(".search-container");
|
||||
this.$search_input = this.$(".search-input input");
|
||||
this.$search_results = this.$(".search-results");
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
|
||||
/**
|
||||
* Prevent the menu from being opened twice
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
_onAppsMenuItemClicked: function (ev) {
|
||||
this._super.apply(this, arguments);
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all info for a given menu.
|
||||
*
|
||||
* @param {String} key
|
||||
* Full path to requested menu.
|
||||
*
|
||||
* @returns {Object}
|
||||
* Menu definition, plus extra needed keys.
|
||||
*/
|
||||
_menuInfo: function (key) {
|
||||
var original = this._searchableMenus[key];
|
||||
return _.extend({
|
||||
action_id: parseInt(original.action.split(',')[1], 10),
|
||||
}, original);
|
||||
},
|
||||
|
||||
/**
|
||||
* Autofocus on search field on big screens.
|
||||
*/
|
||||
_searchFocus: function () {
|
||||
if (!config.device.isMobile) {
|
||||
this.$search_input.focus();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Reset search input and results
|
||||
*/
|
||||
_searchReset: function () {
|
||||
this.$search_container.removeClass("has-results");
|
||||
this.$search_results.empty();
|
||||
this.$search_input.val("");
|
||||
},
|
||||
|
||||
/**
|
||||
* Schedule a search on current menu items.
|
||||
*/
|
||||
_searchMenusSchedule: function () {
|
||||
this._search_def.reject();
|
||||
this._search_def = $.Deferred();
|
||||
setTimeout(this._search_def.resolve.bind(this._search_def), 50);
|
||||
this._search_def.done(this._searchMenus.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Search among available menu items, and render that search.
|
||||
*/
|
||||
_searchMenus: function () {
|
||||
var query = this.$search_input.val();
|
||||
if (query === "") {
|
||||
this.$search_container.removeClass("has-results");
|
||||
this.$search_results.empty();
|
||||
return;
|
||||
}
|
||||
var results = fuzzy.filter(
|
||||
query,
|
||||
_.keys(this._searchableMenus),
|
||||
{
|
||||
pre: "<b>",
|
||||
post: "</b>",
|
||||
}
|
||||
);
|
||||
this.$search_container.toggleClass(
|
||||
"has-results",
|
||||
Boolean(results.length)
|
||||
);
|
||||
this.$search_results.html(
|
||||
core.qweb.render(
|
||||
"web_responsive.MenuSearchResults",
|
||||
{
|
||||
results: results,
|
||||
widget: this,
|
||||
}
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Use chooses a search result, so we navigate to that menu
|
||||
*
|
||||
* @param {jQuery.Event} event
|
||||
*/
|
||||
_searchResultChosen: function (event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
var $result = $(event.currentTarget),
|
||||
text = $result.text().trim(),
|
||||
data = $result.data(),
|
||||
suffix = ~text.indexOf("/") ? "/" : "";
|
||||
// Load the menu view
|
||||
this.trigger_up("menu_clicked", {
|
||||
action_id: data.actionId,
|
||||
id: data.menuId,
|
||||
previous_menu_id: data.parentId,
|
||||
});
|
||||
// Find app that owns the chosen menu
|
||||
var app = _.find(this._apps, function (_app) {
|
||||
return text.indexOf(_app.name + suffix) === 0;
|
||||
});
|
||||
// Update navbar menus
|
||||
core.bus.trigger("change_menu_section", app.menuID);
|
||||
},
|
||||
|
||||
/**
|
||||
* Navigate among search results
|
||||
*
|
||||
* @param {jQuery.Event} event
|
||||
*/
|
||||
_searchResultsNavigate: function (event) {
|
||||
// Find current results and active element (1st by default)
|
||||
var all = this.$search_results.find(".o-menu-search-result"),
|
||||
pre_focused = all.filter(".active") || $(all[0]),
|
||||
offset = all.index(pre_focused),
|
||||
key = event.key;
|
||||
// Keyboard navigation only supports search results
|
||||
if (!all.length) {
|
||||
return;
|
||||
}
|
||||
// Transform tab presses in arrow presses
|
||||
if (key === "Tab") {
|
||||
event.preventDefault();
|
||||
key = event.shiftKey ? "ArrowUp" : "ArrowDown";
|
||||
}
|
||||
switch (key) {
|
||||
// Pressing enter is the same as clicking on the active element
|
||||
case "Enter":
|
||||
pre_focused.click();
|
||||
break;
|
||||
// Navigate up or down
|
||||
case "ArrowUp":
|
||||
offset--;
|
||||
break;
|
||||
case "ArrowDown":
|
||||
offset++;
|
||||
break;
|
||||
default:
|
||||
// Other keys are useless in this event
|
||||
return;
|
||||
}
|
||||
// Allow looping on results
|
||||
if (offset < 0) {
|
||||
offset = all.length + offset;
|
||||
} else if (offset >= all.length) {
|
||||
offset -= all.length;
|
||||
}
|
||||
// Switch active element
|
||||
var new_focused = $(all[offset]);
|
||||
pre_focused.removeClass("active");
|
||||
new_focused.addClass("active");
|
||||
this.$search_results.scrollTo(new_focused, {
|
||||
offset: {
|
||||
top: this.$search_results.height() * -0.5,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
/*
|
||||
* Control if AppDrawer can be closed
|
||||
*/
|
||||
_hideAppsMenu: function () {
|
||||
return !this.$('input').is(':focus');
|
||||
},
|
||||
});
|
||||
|
||||
BasicController.include({
|
||||
|
||||
/**
|
||||
* Close the AppDrawer if the data set is dirty and a discard dialog
|
||||
* is opened
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
canBeDiscarded: function (recordID) {
|
||||
if (this.model.isDirty(recordID || this.handle)) {
|
||||
closeAppDrawer();
|
||||
}
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
});
|
||||
|
||||
Menu.include({
|
||||
events: _.extend({
|
||||
// Clicking a hamburger menu item should close the hamburger
|
||||
"click .o_menu_sections [role=menuitem]": "_onClickMenuItem",
|
||||
// Opening any dropdown in the navbar should hide the hamburger
|
||||
"show.bs.dropdown .o_menu_systray, .o_menu_apps":
|
||||
"_hideMobileSubmenus",
|
||||
}, Menu.prototype.events),
|
||||
|
||||
start: function () {
|
||||
this.$menu_toggle = this.$(".o-menu-toggle");
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
|
||||
/**
|
||||
* Hide menus for current app if you're in mobile
|
||||
*/
|
||||
_hideMobileSubmenus: function () {
|
||||
if (
|
||||
config.device.isMobile &&
|
||||
this.$menu_toggle.is(":visible") &&
|
||||
this.$section_placeholder.is(":visible")
|
||||
) {
|
||||
this.$section_placeholder.collapse("hide");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Prevent hide the menu (should be closed when action is loaded)
|
||||
*
|
||||
* @param {ClickEvent} ev
|
||||
*/
|
||||
_onClickMenuItem: function (ev) {
|
||||
ev.stopPropagation();
|
||||
},
|
||||
|
||||
/**
|
||||
* No menu brand in mobiles
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
_updateMenuBrand: function () {
|
||||
if (!config.device.isMobile) {
|
||||
return this._super.apply(this, arguments);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
RelationalFields.FieldStatus.include({
|
||||
|
||||
/**
|
||||
* Fold all on mobiles.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
_setState: function () {
|
||||
this._super.apply(this, arguments);
|
||||
if (config.device.isMobile) {
|
||||
_.map(this.status_information, function (value) {
|
||||
value.fold = true;
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// Responsive view "action" buttons
|
||||
FormRenderer.include({
|
||||
|
||||
/**
|
||||
* In mobiles, put all statusbar buttons in a dropdown.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
_renderHeaderButtons: function () {
|
||||
var $buttons = this._super.apply(this, arguments);
|
||||
if (
|
||||
!config.device.isMobile ||
|
||||
!$buttons.is(":has(>:not(.o_invisible_modifier))")
|
||||
) {
|
||||
return $buttons;
|
||||
}
|
||||
|
||||
// $buttons must be appended by JS because all events are bound
|
||||
$buttons.addClass("dropdown-menu");
|
||||
var $dropdown = $(core.qweb.render(
|
||||
'web_responsive.MenuStatusbarButtons'
|
||||
));
|
||||
$buttons.addClass("dropdown-menu").appendTo($dropdown);
|
||||
return $dropdown;
|
||||
},
|
||||
});
|
||||
|
||||
// Chatter Hide Composer
|
||||
Chatter.include({
|
||||
_openComposer: function (options) {
|
||||
if (this._composer &&
|
||||
options.isLog === this._composer.options.isLog &&
|
||||
this._composer.$el.is(':visible')) {
|
||||
this._closeComposer(false);
|
||||
} else {
|
||||
this._super.apply(this, arguments);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// Hide AppDrawer or Menu when the action has been completed
|
||||
ActionManager.include({
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_appendController: function () {
|
||||
this._super.apply(this, arguments);
|
||||
closeAppDrawer();
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Use ALT+SHIFT instead of ALT as hotkey triggerer.
|
||||
*
|
||||
* HACK https://github.com/odoo/odoo/issues/30068 - See it to know why.
|
||||
*
|
||||
* Cannot patch in `KeyboardNavigationMixin` directly because it's a mixin,
|
||||
* not a `Class`, and altering a mixin's `prototype` doesn't alter it where
|
||||
* it has already been used.
|
||||
*
|
||||
* Instead, we provide an additional mixin to be used wherever you need to
|
||||
* enable this behavior.
|
||||
*/
|
||||
var KeyboardNavigationShiftAltMixin = {
|
||||
|
||||
/**
|
||||
* Alter the key event to require pressing Shift.
|
||||
*
|
||||
* This will produce a mocked event object where it will seem that
|
||||
* `Alt` is not pressed if `Shift` is not pressed.
|
||||
*
|
||||
* The reason for this is that original upstream code, found in
|
||||
* `KeyboardNavigationMixin` is very hardcoded against the `Alt` key,
|
||||
* so it is more maintainable to mock its input than to rewrite it
|
||||
* completely.
|
||||
*
|
||||
* @param {keyEvent} keyEvent
|
||||
* Original event object
|
||||
*
|
||||
* @returns {keyEvent}
|
||||
* Altered event object
|
||||
*/
|
||||
_shiftPressed: function (keyEvent) {
|
||||
var alt = keyEvent.altKey || keyEvent.key === "Alt",
|
||||
newEvent = _.extend({}, keyEvent),
|
||||
shift = keyEvent.shiftKey || keyEvent.key === "Shift";
|
||||
// Mock event to make it seem like Alt is not pressed
|
||||
if (alt && !shift) {
|
||||
newEvent.altKey = false;
|
||||
if (newEvent.key === "Alt") {
|
||||
newEvent.key = "Shift";
|
||||
}
|
||||
}
|
||||
return newEvent;
|
||||
},
|
||||
|
||||
_onKeyDown: function (keyDownEvent) {
|
||||
return this._super(this._shiftPressed(keyDownEvent));
|
||||
},
|
||||
|
||||
_onKeyUp: function (keyUpEvent) {
|
||||
return this._super(this._shiftPressed(keyUpEvent));
|
||||
},
|
||||
};
|
||||
|
||||
// Include the SHIFT+ALT mixin wherever
|
||||
// `KeyboardNavigationMixin` is used upstream
|
||||
AbstractWebClient.include(KeyboardNavigationShiftAltMixin);
|
||||
|
||||
// DocumentViewer: Add support to maximize/minimize
|
||||
DocumentViewer.include({
|
||||
// Widget 'keydown' and 'keyup' events are only dispatched when
|
||||
// this.$el is active, but now the modal have buttons that can obtain
|
||||
// the focus. For this reason we now listen core events, that are
|
||||
// dispatched every time.
|
||||
events: _.extend(_.omit(DocumentViewer.prototype.events, [
|
||||
'keydown',
|
||||
'keyup',
|
||||
]), {
|
||||
'click .o_maximize_btn': '_onClickMaximize',
|
||||
'click .o_minimize_btn': '_onClickMinimize',
|
||||
'shown.bs.modal': '_onShownModal',
|
||||
}),
|
||||
|
||||
start: function () {
|
||||
core.bus.on('keydown', this, this._onKeydown);
|
||||
core.bus.on('keyup', this, this._onKeyUp);
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
|
||||
destroy: function () {
|
||||
core.bus.off('keydown', this, this._onKeydown);
|
||||
core.bus.off('keyup', this, this._onKeyUp);
|
||||
this._super.apply(this, arguments);
|
||||
},
|
||||
|
||||
_onShownModal: function () {
|
||||
// Disable auto-focus to allow to use controls in edit mode.
|
||||
// This only affects the active modal.
|
||||
// More info: https://stackoverflow.com/a/14795256
|
||||
$(document).off('focusin.modal');
|
||||
},
|
||||
_onClickMaximize: function () {
|
||||
this.$el.removeClass('o_responsive_document_viewer');
|
||||
},
|
||||
_onClickMinimize: function () {
|
||||
this.$el.addClass('o_responsive_document_viewer');
|
||||
},
|
||||
});
|
||||
|
||||
// List Renderer: Big checkboxes
|
||||
ListRenderer.include({
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_renderSelector: function () {
|
||||
var $elm = this._super.apply(this, arguments);
|
||||
$elm.find(".custom-control-label").html('');
|
||||
return $elm;
|
||||
},
|
||||
});
|
||||
});
|
65
web_responsive/static/src/xml/apps.xml
Normal file
65
web_responsive/static/src/xml/apps.xml
Normal file
|
@ -0,0 +1,65 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Copyright 2018 Tecnativa - Jairo Llopis
|
||||
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). -->
|
||||
|
||||
<template>
|
||||
<t t-extend="AppsMenu">
|
||||
<!-- App icons must be clickable -->
|
||||
<t t-jquery=".o_app" t-operation="attributes">
|
||||
<attribute name="t-attf-href">#menu_id=#{app.menuID}&action_id=#{app.actionID}</attribute>
|
||||
</t>
|
||||
|
||||
<!-- App icons should be more than a text -->
|
||||
<t t-jquery=".o_app > t" t-operation="replace">
|
||||
<t t-call="web_responsive.AppIcon"/>
|
||||
</t>
|
||||
|
||||
<!-- Same hotkey as in EE -->
|
||||
<t t-jquery=".full" t-operation="attributes">
|
||||
<attribute name="accesskey">a</attribute>
|
||||
</t>
|
||||
|
||||
<!-- Search bar -->
|
||||
<t t-jquery="[t-as=app]" t-operation="before">
|
||||
<div class="search-container form-row align-items-center mb-4 col-12">
|
||||
<div class="search-input col-md-10 ml-auto mr-auto mb-2">
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<div class="input-group-text">
|
||||
<i class="fa fa-search"/>
|
||||
</div>
|
||||
</div>
|
||||
<input type="search"
|
||||
autocomplete="off"
|
||||
placeholder="Search menus..."
|
||||
class="form-control"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="search-results col-md-10 ml-auto mr-auto"/>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<!-- Separate app icon template, for easier inheritance -->
|
||||
<t t-name="web_responsive.AppIcon">
|
||||
<img class="o-app-icon"
|
||||
t-attf-src="data:image/png;base64,#{app.web_icon_data}"/>
|
||||
<span class="o-app-name">
|
||||
<t t-esc="app.name"/>
|
||||
</span>
|
||||
</t>
|
||||
|
||||
<!-- A search result -->
|
||||
<t t-name="web_responsive.MenuSearchResults">
|
||||
<t t-foreach="results" t-as="result">
|
||||
<t t-set="menu" t-value="widget._menuInfo(result.original)"/>
|
||||
<a t-attf-class="o-menu-search-result dropdown-item col-12 ml-auto mr-auto #{result_first ? 'active' : ''}"
|
||||
t-attf-style="background-image:url('data:image/png;base64,#{menu.web_icon_data}')"
|
||||
t-attf-href="#menu_id=#{menu.id}&action_id=#{menu.action_id}"
|
||||
t-att-data-menu-id="menu.id"
|
||||
t-att-data-action-id="menu.action_id"
|
||||
t-att-data-parent-id="menu.parent_id[0]"
|
||||
t-raw="result.string"/>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
22
web_responsive/static/src/xml/document_viewer.xml
Normal file
22
web_responsive/static/src/xml/document_viewer.xml
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Copyright 2019 Tecnativa - Alexandre Díaz
|
||||
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). -->
|
||||
|
||||
<template>
|
||||
<t t-extend="DocumentViewer">
|
||||
<t t-jquery=".o_modal_fullscreen" t-operation="attributes">
|
||||
<attribute name="class">modal o_modal_fullscreen o_document_viewer o_responsive_document_viewer</attribute>
|
||||
<attribute name="data-backdrop">false</attribute>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<t t-extend="DocumentViewer.Content">
|
||||
<t t-jquery=".o_close_btn" t-operation="replace">
|
||||
<div class="o_buttons float-right mr8">
|
||||
<a role="button" class="mr8 o_maximize_btn" tabindex="0" aria-label="Maximize" title="Maximize"><i class="fa fa-window-maximize"></i></a>
|
||||
<a role="button" class="mr8 o_minimize_btn" tabindex="0" aria-label="Minimize" title="Minimize"><i class="fa fa-window-minimize"></i></a>
|
||||
<a role="button" class="o_close_btn" tabindex="0" aria-label="Close" title="Close"><i class="fa fa-close"></i></a>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
151
web_responsive/static/src/xml/form_view.xml
Normal file
151
web_responsive/static/src/xml/form_view.xml
Normal file
|
@ -0,0 +1,151 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright 2017 LasLabs Inc.
|
||||
Copyright 2018 Alexandre Díaz
|
||||
Copyright 2018 Tecnativa - Jairo Llopis
|
||||
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html).
|
||||
-->
|
||||
|
||||
<templates id="form_view" xml:space="preserve">
|
||||
|
||||
<!-- Template for buttons that display only the icon in xs -->
|
||||
<t t-name="web_responsive.icon_button">
|
||||
<i t-attf-class="fa fa-#{icon}"
|
||||
t-att-title="label"/>
|
||||
<span class="d-none d-sm-inline" t-esc="label"/>
|
||||
</t>
|
||||
|
||||
<t t-name="web_responsive.MenuStatusbarButtons">
|
||||
<div class="dropdown">
|
||||
<button class="o_statusbar_buttons_dropdown btn btn-secondary dropdown-toggle"
|
||||
type="button"
|
||||
data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false">
|
||||
<t t-call="web_responsive.icon_button">
|
||||
<t t-set="icon" t-value="'cogs'"/>
|
||||
<t t-set="label">Quick actions</t>
|
||||
</t>
|
||||
</button>
|
||||
<!-- A div.o_statusbar_buttons.dropdown-menu
|
||||
is appended here from JS -->
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-extend="FormView.buttons">
|
||||
<!-- Change "Edit" button hotkey to "E" -->
|
||||
<t t-jquery=".o_form_button_edit" t-operation="attributes">
|
||||
<attribute name="accesskey">e</attribute>
|
||||
</t>
|
||||
|
||||
<!-- Change "Discard" button hotkey to "D" -->
|
||||
<t t-jquery=".o_form_button_cancel" t-operation="attributes">
|
||||
<attribute name="accesskey">d</attribute>
|
||||
</t>
|
||||
|
||||
<!-- Add responsive icons to buttons -->
|
||||
<t t-jquery=".o_form_button_edit" t-operation="inner">
|
||||
<t t-call="web_responsive.icon_button">
|
||||
<t t-set="icon" t-value="'pencil'"/>
|
||||
<t t-set="label">Edit</t>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<t t-jquery=".o_form_button_create" t-operation="inner">
|
||||
<t t-call="web_responsive.icon_button">
|
||||
<t t-set="icon" t-value="'plus'"/>
|
||||
<t t-set="label">Create</t>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<t t-jquery=".o_form_button_save" t-operation="inner">
|
||||
<t t-call="web_responsive.icon_button">
|
||||
<t t-set="icon" t-value="'check'"/>
|
||||
<t t-set="label">Save</t>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<t t-jquery=".o_form_button_cancel" t-operation="inner">
|
||||
<t t-call="web_responsive.icon_button">
|
||||
<t t-set="icon" t-value="'times'"/>
|
||||
<t t-set="label">Discard</t>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<t t-extend="KanbanView.buttons">
|
||||
<!-- Add responsive icons to buttons -->
|
||||
<t t-jquery="button" t-operation="inner">
|
||||
<t t-call="web_responsive.icon_button">
|
||||
<t t-set="icon" t-value="'plus'"/>
|
||||
<t t-set="label" t-value="create_text || _t('Create')"/>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<t t-extend="ListView.buttons">
|
||||
<!-- Change "Discard" button hotkey to "D" -->
|
||||
<t t-jquery=".o_list_button_discard" t-operation="attributes">
|
||||
<attribute name="accesskey">d</attribute>
|
||||
</t>
|
||||
|
||||
<!-- Add responsive icons to buttons -->
|
||||
<t t-jquery=".o_list_button_add" t-operation="inner">
|
||||
<t t-call="web_responsive.icon_button">
|
||||
<t t-set="icon" t-value="'plus'"/>
|
||||
<t t-set="label">Create</t>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<t t-jquery=".o_list_button_save" t-operation="inner">
|
||||
<t t-call="web_responsive.icon_button">
|
||||
<t t-set="icon" t-value="'check'"/>
|
||||
<t t-set="label">Save</t>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<t t-jquery=".o_list_button_discard" t-operation="inner">
|
||||
<t t-call="web_responsive.icon_button">
|
||||
<t t-set="icon" t-value="'times'"/>
|
||||
<t t-set="label">Discard</t>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<t t-extend="Sidebar">
|
||||
<!-- Replace some common sections by icons in mobile -->
|
||||
<t t-jquery=".o_dropdown_toggler_btn t[t-esc='section.label']"
|
||||
t-operation="replace">
|
||||
<t t-set="label" t-value="section.label"/>
|
||||
<t t-if="section.name == 'files'">
|
||||
<t t-call="web_responsive.icon_button">
|
||||
<t t-set="icon" t-value="'paperclip'"/>
|
||||
</t>
|
||||
</t>
|
||||
<t t-elif="section.name == 'print'">
|
||||
<t t-call="web_responsive.icon_button">
|
||||
<t t-set="icon" t-value="'print'"/>
|
||||
</t>
|
||||
</t>
|
||||
<t t-elif="section.name == 'other'">
|
||||
<t t-call="web_responsive.icon_button">
|
||||
<t t-set="icon" t-value="'wrench'"/>
|
||||
</t>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<span t-esc="label"/>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<t t-extend="mail.Chatter">
|
||||
<t t-jquery=".o_chatter_topbar" t-operation="replace">
|
||||
<div class="o_chatter_header_container">
|
||||
<div class="o_chatter_topbar">
|
||||
<div class="o_topbar_right_area"/>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
</templates>
|
58
web_responsive/static/src/xml/menu.xml
Normal file
58
web_responsive/static/src/xml/menu.xml
Normal file
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-extend="UserMenu">
|
||||
<t t-jquery=".dropdown-toggle" t-operation="attributes">
|
||||
<attribute name="accesskey">u</attribute>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<div t-extend="UserMenu.shortcuts">
|
||||
<!-- Replace "a" shortcut by "e" (for 'Edit a Record') -->
|
||||
<t t-jquery="table.o_shortcut_table > tbody > tr > td:nth-child(2) > span:nth-child(2).o_key:contains('a')" t-operation="replace">
|
||||
<span class="o_key">e</span>
|
||||
</t>
|
||||
<t t-jquery="table.o_shortcut_table > tbody > tr > td:nth-child(3) > span:nth-child(3).o_key:contains('a')" t-operation="replace">
|
||||
<span class="o_key">e</span>
|
||||
</t>
|
||||
|
||||
<!-- Replace "j" shortcut by "d" (for 'Discard a record modification') -->
|
||||
<t t-jquery="table.o_shortcut_table > tbody > tr > td:nth-child(2) > span:nth-child(2).o_key:contains('j')" t-operation="replace">
|
||||
<span class="o_key">d</span>
|
||||
</t>
|
||||
<t t-jquery="table.o_shortcut_table > tbody > tr > td:nth-child(3) > span:nth-child(3).o_key:contains('j')" t-operation="replace">
|
||||
<span class="o_key">d</span>
|
||||
</t>
|
||||
|
||||
<!-- Add Shift for all windows / linux shortcuts -->
|
||||
<t t-jquery="table.o_shortcut_table > tbody > tr > td:nth-child(2) > span:first-child" t-operation="after">
|
||||
+ <span class="o_key">Shift</span>
|
||||
</t>
|
||||
|
||||
<!-- Add new accesskeys -->
|
||||
<t t-jquery=".o_shortcut_table tbody" t-operation="append">
|
||||
<tr>
|
||||
<td align="left">Select menu</td>
|
||||
<td>
|
||||
<span class="o_key">Alt</span> + <span class="o_key">Shift</span> + <span class="o_key">a</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="o_key">Control</span> + <span class="o_key">Alt</span> + <span class="o_key">a</span>
|
||||
</td>
|
||||
</tr>
|
||||
</t>
|
||||
|
||||
<t t-jquery=".o_shortcut_table tbody" t-operation="append">
|
||||
<tr>
|
||||
<td align="left">User menu</td>
|
||||
<td>
|
||||
<span class="o_key">Alt</span> + <span class="o_key">Shift</span> + <span class="o_key">u</span>
|
||||
</td>
|
||||
<td>
|
||||
<span class="o_key">Control</span> + <span class="o_key">Alt</span> + <span class="o_key">u</span>
|
||||
</td>
|
||||
</tr>
|
||||
</t>
|
||||
</div>
|
||||
|
||||
</templates>
|
16
web_responsive/static/src/xml/navbar.xml
Normal file
16
web_responsive/static/src/xml/navbar.xml
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Copyright 2017-2018 Tecnativa - Jairo Llopis
|
||||
License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). -->
|
||||
|
||||
<template>
|
||||
<t t-extend="Menu">
|
||||
<t t-jquery=".o_menu_apps" t-operation="after">
|
||||
<!-- Hamburger button to show submenus in sm screens -->
|
||||
<button class="o-menu-toggle d-md-none"
|
||||
data-toggle="collapse"
|
||||
data-target=".o_main_navbar .o_menu_sections">
|
||||
<i class="fa fa-bars"/>
|
||||
</button>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
Loading…
Add table
Add a link
Reference in a new issue