[REF] web,*: ControlPanel layout
This commit brings the ControlPanel into a single line with 3 main sections: - buttons & breadcrumb - layout related actions (ie. the SearchBar in multirecord view or ButtonBox in formView) - navigation (pager, switch view...) Add new search bar menu, this is a merge of the following components into one big component display in column: * comparison_menu * favorite_menu * filter_menu * group_by_menu Also adapt navigation hook. Part-of: odoo/odoo#116641
This commit is contained in:
@@ -51,13 +51,13 @@
|
||||
</t>
|
||||
|
||||
<t t-name="account.ListView.Buttons" t-inherit="web.ListView.Buttons" t-inherit-mode="primary" owl="1">
|
||||
<xpath expr="//*[@class='btn btn-primary o_list_button_add']" position="after">
|
||||
<xpath expr="//div[hasclass('o_list_buttons')]" position="inside">
|
||||
<t t-call="account.AccountViewUploadButton"/>
|
||||
</xpath>
|
||||
</t>
|
||||
|
||||
<t t-name="account.KanbanView.Buttons" t-inherit="web.KanbanView.Buttons" t-inherit-mode="primary" owl="1">
|
||||
<xpath expr="//*[@class='btn btn-primary o-kanban-button-new']" position="after">
|
||||
<xpath expr="//div[hasclass('o_cp_buttons')]" position="inside">
|
||||
<t t-call="account.AccountViewUploadButton"/>
|
||||
</xpath>
|
||||
</t>
|
||||
|
||||
@@ -63,11 +63,7 @@ export class ImportAction extends Component {
|
||||
|
||||
get display() {
|
||||
return {
|
||||
controlPanel: {
|
||||
"top-right": false,
|
||||
"bottom-left": true,
|
||||
"bottom-right": false,
|
||||
},
|
||||
controlPanel: {},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates>
|
||||
<t t-name="DataRecycle.buttons" t-inherit="web.ListView.Buttons" t-inherit-mode="primary" owl="1">
|
||||
<xpath expr="//t[@t-if='props.showButtons']">
|
||||
<xpath expr="//div[hasclass('o_list_buttons')]" position="inside">
|
||||
<t t-if="nbSelected">
|
||||
<button type="button" class="btn btn-primary o_data_recycle_validate_button" t-on-click="onValidateClick">
|
||||
Validate
|
||||
|
||||
@@ -18,20 +18,14 @@
|
||||
</t>
|
||||
|
||||
<t t-name="hr_expense.KanbanButtons" t-inherit="web.KanbanView.Buttons" t-inherit-mode="primary" owl="1">
|
||||
<xpath expr="//t[@t-if='canCreate']" position="after">
|
||||
<xpath expr="//div[hasclass('o_cp_buttons')]" position="inside">
|
||||
<input type="file" name="ufile" class="d-none" t-ref="fileInput" multiple="1" accept="*" t-on-change="onChangeFileInput" />
|
||||
<button type="button" class="d-none d-md-inline o_button_upload_expense btn btn-primary mx-1" t-on-click.prevent="uploadDocument">
|
||||
Upload
|
||||
</button>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//t[@t-if='canCreate']" position="before">
|
||||
<button type="button" class="d-inline d-md-none o_button_upload_expense btn btn-primary mx-1" t-on-click.prevent="uploadDocument">
|
||||
Scan
|
||||
</button>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//div" position="inside">
|
||||
<input type="file" name="ufile" class="d-none" t-ref="fileInput" multiple="1" accept="*" t-on-change="onChangeFileInput" />
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
<t t-name="hr_expense.ListButtons" t-inherit="web.ListView.Buttons" t-inherit-mode="primary" owl="1">
|
||||
|
||||
<!-- hr.expense and hr.expense.sheet -->
|
||||
<xpath expr="//button[hasclass('o_list_button_add')]" position="after">
|
||||
<xpath expr="//div[hasclass('o_list_buttons')]" position="inside">
|
||||
<input type="file" name="ufile" class="d-none" t-ref="fileInput" multiple="1" accept="*" t-on-change="onChangeFileInput"/>
|
||||
<button type="button" class="d-none d-md-inline o_button_upload_expense btn btn-primary mx-1" t-on-click.prevent="uploadDocument">
|
||||
Scan
|
||||
</button>
|
||||
@@ -11,32 +12,15 @@
|
||||
t-on-click="() => this.action_show_expenses_to_submit()">
|
||||
Create Report
|
||||
</button>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//div" position="inside">
|
||||
<input type="file" name="ufile" class="d-none" t-ref="fileInput" multiple="1" accept="*" t-on-change="onChangeFileInput"/>
|
||||
</xpath>
|
||||
|
||||
<!-- hr.expense.sheet -->
|
||||
<xpath expr="//button[hasclass('o_button_upload_expense')]" position="after">
|
||||
<button t-if="displaySubmit()" class="d-none d-md-block btn btn-secondary" t-on-click="() => this.onClick('action_submit_sheet')">
|
||||
Submit
|
||||
</button>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//button[hasclass('o_button_upload_expense')]" position="after">
|
||||
<button t-if="displayApprove()" class="d-none d-md-block btn btn-secondary" t-on-click="() => this.onClick('action_approve_expense_sheets')">
|
||||
Approve Report
|
||||
</button>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//button[hasclass('o_button_upload_expense')]" position="after">
|
||||
<button t-if="displayPost()" class="d-none d-md-block btn btn-secondary" t-on-click="() => this.onClick('action_sheet_move_create')">
|
||||
Post Entries
|
||||
</button>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//button[hasclass('o_button_upload_expense')]" position="after">
|
||||
<button t-if="displayPayment()" class="d-none d-md-block btn btn-secondary" t-on-click="() => this.onClick('action_register_payment')">
|
||||
Register Payment
|
||||
</button>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates>
|
||||
<t t-name="hr_fleet.HrFleetKanbanController.Buttons" t-inherit="web.KanbanView.Buttons" t-inherit-mode="primary" owl="1">
|
||||
<xpath expr="//button[hasclass('o-kanban-button-new')]" position="replace">
|
||||
<xpath expr="//div[hasclass('o_cp_buttons')]" position="inside">
|
||||
<input type="file" multiple="true" t-ref="uploadFileInput" class="o_input_file o_hidden" t-on-change.stop="onInputChange"/>
|
||||
<button type="button" t-att-class="'d-none d-md-block btn' + (!env.isSmall ? ' btn-primary' : 'btn-secondary')" t-on-click="() => this.uploadFileInput.el.click()">
|
||||
Upload
|
||||
|
||||
@@ -57,8 +57,7 @@
|
||||
</t>
|
||||
|
||||
<t t-name="loyalty.LoyaltyCardListView.buttons" owl="1" t-inherit-mode="primary" t-inherit="web.ListView.Buttons">
|
||||
<xpath expr="//button[hasclass('o_list_button_add')]" position="replace"/>
|
||||
<xpath expr="//t[contains(@t-if, 'isExportEnable')]" position="before">
|
||||
<xpath expr="//div[hasclass('o_list_buttons')]" position="inside">
|
||||
<t t-set="supportedProgramTypes" t-value="['coupons', 'gift_card', 'ewallet']"/>
|
||||
<button t-if="supportedProgramTypes.includes(props.context.program_type)" type="button" class="btn btn-primary o_loyalty_card_list_button_generate" t-attf-data-tooltip="Generate {{props.context.program_item_name}}"
|
||||
t-on-click.stop.prevent="() => this.actionService.doAction('loyalty.loyalty_generate_wizard_action', { additionalContext: this.props.context, onClose: () => {this.model.load()} })">
|
||||
|
||||
@@ -10,9 +10,10 @@ import { SelectCreateDialog } from "@web/views/view_dialogs/select_create_dialog
|
||||
import { sprintf } from "@web/core/utils/strings";
|
||||
import { useModel } from "@web/views/model";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { SearchBar } from "@web/search/search_bar/search_bar";
|
||||
|
||||
export class ActivityController extends Component {
|
||||
static components = { Layout };
|
||||
static components = { Layout, SearchBar };
|
||||
static props = {
|
||||
...standardViewProps,
|
||||
Model: Function,
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
<t t-name="mail.ActivityController" owl="1">
|
||||
<div t-att-class="props.className + (env.isSmall ? ' o_action_delegate_scroll': '')">
|
||||
<Layout display="props.display">
|
||||
<t t-set-slot="layout-actions">
|
||||
<SearchBar/>
|
||||
</t>
|
||||
<t t-component="props.Renderer" t-props="rendererProps" />
|
||||
</Layout>
|
||||
</div>
|
||||
|
||||
+2
-2
@@ -1,12 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates>
|
||||
<t t-name="mrp.MrpDocumentsKanbanView.Buttons" t-inherit="web.KanbanView.Buttons" t-inherit-mode="primary" owl="1">
|
||||
<div role="toolbar" position="inside">
|
||||
<xpath expr="//div[hasclass('o_cp_buttons')]" position="inside">
|
||||
<input type="file" multiple="true" t-ref="uploadFileInput" class="o_input_file o_hidden" t-on-change.stop="onFileInputChange"/>
|
||||
<button type="button" t-attf-class="btn btn-primary o_mrp_documents_kanban_upload"
|
||||
t-on-click.stop.prevent="() => this.uploadFileInputRef.el.click()">
|
||||
Upload
|
||||
</button>
|
||||
</div>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="mrp_subcontracting.PickingFormController" t-inherit="web.FormView" t-inherit-mode="primary" owl="1">
|
||||
<xpath expr="//ActionMenus" position="replace"/>
|
||||
<t t-set-slot="control-panel-additional-actions"/>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
||||
+6
-33
@@ -10,45 +10,18 @@
|
||||
|
||||
<t t-name="project.ProjectControlPanelContent" owl="1">
|
||||
<t t-if="showProjectUpdate">
|
||||
<li t-if="isProjectUser" class="o_project_updates_breadcrumb ps-3" t-on-click="onStatusClick">
|
||||
<div t-if="isProjectUser" class="o_project_updates_breadcrumb" t-on-click="onStatusClick">
|
||||
<t t-call="project.ProjectControlPanelContentBadge"></t>
|
||||
</li>
|
||||
<li t-else="" class="o_project_updates_breadcrumb ps-3">
|
||||
</div>
|
||||
<div t-else="" class="o_project_updates_breadcrumb">
|
||||
<t t-call="project.ProjectControlPanelContentBadge"></t>
|
||||
</li>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<t t-name="project.Breadcrumbs" t-inherit="web.Breadcrumbs" t-inherit-mode="primary" owl="1">
|
||||
<xpath expr="//ol" position="inside">
|
||||
<t t-call="project.ProjectControlPanelContent"/>
|
||||
</xpath>
|
||||
</t>
|
||||
|
||||
<t t-name="project.Breadcrumbs.Small" t-inherit="web.Breadcrumbs.Small" t-inherit-mode="primary" owl="1">
|
||||
<xpath expr="//ol" position="inside">
|
||||
<t t-call="project.ProjectControlPanelContent"/>
|
||||
</xpath>
|
||||
</t>
|
||||
|
||||
<t t-name="project.ProjectControlPanel.Regular" t-inherit="web.ControlPanel.Regular" t-inherit-mode="primary" owl="1">
|
||||
<xpath expr="//t[@t-call='web.Breadcrumbs']" position="replace">
|
||||
<t t-call="project.Breadcrumbs"/>
|
||||
</xpath>
|
||||
</t>
|
||||
|
||||
<t t-name="project.ProjectControlPanel.Small" t-inherit="web.ControlPanel.Small" t-inherit-mode="primary" owl="1">
|
||||
<xpath expr="//t[@t-call='web.Breadcrumbs.Small']" position="replace">
|
||||
<t t-call="project.Breadcrumbs.Small"/>
|
||||
</xpath>
|
||||
</t>
|
||||
|
||||
<t t-name="project.ProjectControlPanel" t-inherit="web.ControlPanel" t-inherit-mode="primary" owl="1">
|
||||
<xpath expr="//t[@t-call='web.ControlPanel.Regular']" position="replace">
|
||||
<t t-call="project.ProjectControlPanel.Regular"/>
|
||||
</xpath>
|
||||
<xpath expr="//t[@t-call='web.ControlPanel.Small']" position="replace">
|
||||
<t t-call="project.ProjectControlPanel.Small"/>
|
||||
<xpath expr="//t[@t-call='web.Breadcrumbs']" position="after">
|
||||
<t t-call="project.ProjectControlPanelContent"/>
|
||||
</xpath>
|
||||
</t>
|
||||
|
||||
|
||||
@@ -17,12 +17,7 @@ import { Component, onWillStart, useState, useEffect } from "@odoo/owl";
|
||||
export class SpreadsheetDashboardAction extends Component {
|
||||
setup() {
|
||||
this.Status = Status;
|
||||
this.controlPanelDisplay = {
|
||||
"top-left": true,
|
||||
"top-right": true,
|
||||
"bottom-left": false,
|
||||
"bottom-right": false,
|
||||
};
|
||||
this.controlPanelDisplay = {};
|
||||
this.orm = useService("orm");
|
||||
this.router = useService("router");
|
||||
// Use the non-protected orm service (`this.env.services.orm` instead of `useService("orm")`)
|
||||
|
||||
@@ -63,7 +63,7 @@ export class TraceabilityReport extends Component {
|
||||
});
|
||||
|
||||
this.display = {
|
||||
controlPanel: { "top-right": false, "bottom-right": false },
|
||||
controlPanel: {},
|
||||
searchPanel: false,
|
||||
};
|
||||
}
|
||||
|
||||
+1
-4
@@ -9,10 +9,7 @@ const { Component, onWillStart, useState } = owl;
|
||||
|
||||
export class ReceptionReportMain extends Component {
|
||||
setup() {
|
||||
this.controlPanelDisplay = {
|
||||
"top-right": false,
|
||||
"bottom-right": false,
|
||||
};
|
||||
this.controlPanelDisplay = {};
|
||||
this.ormService = useService("orm");
|
||||
this.actionService = useService("action");
|
||||
this.reportName = "stock.report_reception";
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="InventoryReport.Buttons" t-inherit="web.ListView.Buttons" t-inherit-mode="primary" owl="1">
|
||||
<xpath expr="(//div/*)[last()]" position="after">
|
||||
<xpath expr="//div[hasclass('o_list_buttons')]" position="inside">
|
||||
<button t-if="props.context.inventory_mode and !props.context.inventory_report_mode and nbSelected == 0"
|
||||
class="btn btn-secondary" type="button" t-on-click="onClickApplyAll">
|
||||
Apply All
|
||||
|
||||
@@ -2,10 +2,7 @@
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-name="stock.StockOrderpoint.Buttons" t-inherit="web.ListView.Buttons" owl="1">
|
||||
<xpath expr="//button[hasclass('o_list_button_add')]" position="attributes">
|
||||
<attribute name="class">btn btn-secondary o_list_button_add</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//t[@t-if='props.showButtons']" position="before">
|
||||
<xpath expr="//div[hasclass('o_list_buttons')]" position="inside">
|
||||
<button t-if="nbSelected" type="button" t-on-click="onClickOrder"
|
||||
class="o_button_order btn btn-primary me-1">
|
||||
Order
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
|
||||
<templates>
|
||||
<t t-name="transifex.CodeTranslationListView.Buttons" t-inherit="web.ListView.Buttons" t-inherit-mode="primary" owl="1">
|
||||
<!-- Before the export button -->
|
||||
<xpath expr="//t[contains(@t-if, 'isExportEnable')]" position="before">
|
||||
<xpath expr="//div[hasclass('o_list_buttons')]" position="inside">
|
||||
<button type="button" class="btn btn-primary" t-on-click="onClickReloadCodeTranslations">Reload</button>
|
||||
</xpath>
|
||||
</t>
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
t-att-aria-expanded="state.open ? 'true' : 'false'"
|
||||
t-ref="togglerRef"
|
||||
>
|
||||
<t t-slot="toggler" />
|
||||
<t t-slot="toggler" open="state.open"/>
|
||||
</button>
|
||||
<div
|
||||
t-if="state.open"
|
||||
|
||||
@@ -21,7 +21,12 @@ import { useComponent, useEffect, useRef } from "@odoo/owl";
|
||||
*/
|
||||
|
||||
const ACTIVE_MENU_ELEMENT_CLASS = "focus";
|
||||
const MENU_ELEMENTS_SELECTORS = [":scope > .dropdown-item", ":scope > .dropdown"];
|
||||
const MENU_ELEMENTS_SELECTORS = [
|
||||
":scope > .dropdown-item",
|
||||
":scope > .dropdown",
|
||||
":scope > .o_dropdown_container > .dropdown-item",
|
||||
":scope > .o_dropdown_container > .dropdown",
|
||||
];
|
||||
const NEXT_ACTIVE_INDEX_FNS = {
|
||||
FIRST: () => 0,
|
||||
LAST: (list) => list.length - 1,
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<span class="o_pager_limit" t-esc="props.total"/>
|
||||
</t>
|
||||
</span>
|
||||
<span class="btn-group" aria-atomic="true">
|
||||
<span class="btn-group d-print-none" aria-atomic="true">
|
||||
<!-- accesskeys not wanted in X2Many widgets -->
|
||||
<button type="button" class="fa fa-chevron-left btn btn-secondary o_pager_previous px-2 rounded-start" aria-label="Previous" data-tooltip="Previous" tabindex="-1" t-att-data-hotkey="props.withAccessKey ? 'p' : false" t-att-disabled="state.isDisabled or isSinglePage" t-on-click.stop="() => this.navigate(-1)" />
|
||||
<button type="button" class="fa fa-chevron-right btn btn-secondary o_pager_next px-2 rounded-end" aria-label="Next" data-tooltip="Next" tabindex="-1" t-att-data-hotkey="props.withAccessKey ? 'n' : false" t-att-disabled="state.isDisabled or isSinglePage" t-on-click.stop="() => this.navigate(1)" />
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
<!-- Owl Templates -->
|
||||
|
||||
<t t-name="web.Legacy.ControlPanel" owl="1">
|
||||
<div class="o_control_panel" data-command-category="actions">
|
||||
<div class="o_cp_top">
|
||||
<div class="o_control_panel px-3" data-command-category="actions">
|
||||
<div class="o_cp_top mb-1">
|
||||
<div class="o_cp_top_left">
|
||||
<ol t-if="props.withBreadcrumbs" class="breadcrumb" role="navigation">
|
||||
<li t-foreach="props.breadcrumbs" t-as="bc" t-key="bc.controllerID"
|
||||
|
||||
@@ -197,6 +197,11 @@ $utilities: map-merge(
|
||||
}
|
||||
}
|
||||
|
||||
// Hide empty elements
|
||||
.d-empty-none:empty {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
// Define a smaller font-size
|
||||
.smaller {
|
||||
font-size: $o-font-size-base-smaller;
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="web.ActionMenus" owl="1">
|
||||
<div class="o_cp_action_menus pe-2">
|
||||
<Dropdown t-if="printItems.length" class="'d-inline-block'" togglerClass="'btn btn-light'" hotkey="'shift+u'">
|
||||
<div class="o_cp_action_menus d-flex pe-2 gap-1">
|
||||
<Dropdown t-if="printItems.length" class="'d-inline-block'" togglerClass="'btn btn-secondary'" hotkey="'shift+u'">
|
||||
<t t-set-slot="toggler">
|
||||
<i class="me-md-1 fa fa-print"/>
|
||||
<span class="o_dropdown_title">Print</span>
|
||||
@@ -15,7 +15,7 @@
|
||||
</t>
|
||||
</Dropdown>
|
||||
|
||||
<Dropdown t-if="actionItems.length" class="'d-inline-block'" togglerClass="'btn btn-light'" hotkey="'u'">
|
||||
<Dropdown t-if="actionItems.length" class="'d-inline-block'" togglerClass="'btn btn-secondary'" hotkey="'u'">
|
||||
<t t-set-slot="toggler">
|
||||
<i class="me-md-1 fa fa-cog"/>
|
||||
<span class="o_dropdown_title">Action</span>
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { ActionMenus } from "@web/search/action_menus/action_menus";
|
||||
|
||||
/**
|
||||
* Combined Action menus (or Action/Print bar, previously called 'Sidebar')
|
||||
*
|
||||
* This is a variation of the ActionMenus, combined into a single DropDown.
|
||||
*
|
||||
* The side bar is the group of dropdown menus located on the left side of the
|
||||
* control panel. Its role is to display a list of items depending on the view
|
||||
* type and selected records and to execute a set of actions on active records.
|
||||
* It is made out of 2 dropdown: Print and Action.
|
||||
*
|
||||
* @extends Component
|
||||
*/
|
||||
export class CombinedActionMenus extends ActionMenus {
|
||||
static template = "web.CombinedActionMenus";
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="web.CombinedActionMenus" owl="1">
|
||||
<div class="o_cp_action_menus d-flex pe-2 gap-1">
|
||||
<Dropdown togglerClass="'d-print-none btn btn-text ms-1 lh-sm'">
|
||||
<t t-set-slot="toggler">
|
||||
<i class="fa fa-cog"/>
|
||||
</t>
|
||||
<t t-if="printItems.length">
|
||||
<div class="dropdown-menu_group dropdown-header">
|
||||
<i class="me-md-1 fa fa-print"/>
|
||||
Print
|
||||
</div>
|
||||
<t t-foreach="printItems" t-as="item" t-key="item.key">
|
||||
<DropdownItem class="'o_menu_item'" onSelected="() => this.onItemSelected(item)">
|
||||
<t t-esc="item.description"/>
|
||||
</DropdownItem>
|
||||
</t>
|
||||
</t>
|
||||
<t t-if="actionItems.length">
|
||||
<div class="dropdown-menu_group dropdown-header">
|
||||
<i class="me-md-1 fa fa-cog"/>
|
||||
Action
|
||||
</div>
|
||||
<t t-foreach="actionItems" t-as="item" t-key="item.key">
|
||||
<t t-if="item.Component" t-component="item.Component" t-props="item.props" />
|
||||
<DropdownItem t-else="" class="'o_menu_item'" onSelected="() => this.onItemSelected(item)">
|
||||
<t t-esc="item.description"/>
|
||||
</DropdownItem>
|
||||
</t>
|
||||
</t>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -4,48 +4,19 @@ import { browser } from "@web/core/browser/browser";
|
||||
import { getActiveHotkey } from "@web/core/hotkeys/hotkey_service";
|
||||
import { Pager } from "@web/core/pager/pager";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { ComparisonMenu } from "../comparison_menu/comparison_menu";
|
||||
import { FavoriteMenu } from "../favorite_menu/favorite_menu";
|
||||
import { FilterMenu } from "../filter_menu/filter_menu";
|
||||
import { GroupByMenu } from "../group_by_menu/group_by_menu";
|
||||
import { SearchBar } from "../search_bar/search_bar";
|
||||
import { Dropdown } from "@web/core/dropdown/dropdown";
|
||||
import { useCommand } from "@web/core/commands/command_hook";
|
||||
import { sprintf } from "@web/core/utils/strings";
|
||||
import { Dialog } from "@web/core/dialog/dialog";
|
||||
import { DropdownItem } from "@web/core/dropdown/dropdown_item";
|
||||
|
||||
import {
|
||||
Component,
|
||||
useState,
|
||||
onMounted,
|
||||
useExternalListener,
|
||||
useRef,
|
||||
useEffect,
|
||||
useSubEnv,
|
||||
} from "@odoo/owl";
|
||||
|
||||
const MAPPING = {
|
||||
filter: FilterMenu,
|
||||
groupBy: GroupByMenu,
|
||||
comparison: ComparisonMenu,
|
||||
favorite: FavoriteMenu,
|
||||
};
|
||||
import { Component, useState, onMounted, useExternalListener, useRef, useEffect } from "@odoo/owl";
|
||||
|
||||
const STICKY_CLASS = "o_mobile_sticky";
|
||||
|
||||
export class ControlPanelSearchDialog extends Component {
|
||||
setup() {
|
||||
useSubEnv(this.props.env);
|
||||
}
|
||||
}
|
||||
ControlPanelSearchDialog.template = "web.ControlPanelSearchDialog";
|
||||
ControlPanelSearchDialog.props = ["close", "slots?", "display", "env", "searchMenus"];
|
||||
ControlPanelSearchDialog.components = { Dialog, SearchBar };
|
||||
|
||||
export class ControlPanel extends Component {
|
||||
setup() {
|
||||
this.actionService = useService("action");
|
||||
this.dialog = useService("dialog");
|
||||
this.pagerProps = this.env.config.pagerProps
|
||||
? useState(this.env.config.pagerProps)
|
||||
: undefined;
|
||||
@@ -55,6 +26,7 @@ export class ControlPanel extends Component {
|
||||
|
||||
this.state = useState({
|
||||
showSearchBar: false,
|
||||
showMobileSearch: false,
|
||||
showViewSwitcher: false,
|
||||
});
|
||||
|
||||
@@ -110,6 +82,7 @@ export class ControlPanel extends Component {
|
||||
resetSearchState() {
|
||||
Object.assign(this.state, {
|
||||
showSearchBar: false,
|
||||
showMobileSearch: false,
|
||||
showViewSwitcher: false,
|
||||
});
|
||||
}
|
||||
@@ -118,49 +91,10 @@ export class ControlPanel extends Component {
|
||||
* @returns {Object}
|
||||
*/
|
||||
get display() {
|
||||
const display = Object.assign(
|
||||
{
|
||||
"top-left": true,
|
||||
"top-right": true,
|
||||
"bottom-left": true,
|
||||
"bottom-left-buttons": true,
|
||||
"bottom-right": true,
|
||||
},
|
||||
this.props.display
|
||||
);
|
||||
display.top = display["top-left"] || display["top-right"];
|
||||
display.bottom = display["bottom-left"] || display["bottom-right"];
|
||||
return display;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Component[]}
|
||||
*/
|
||||
get searchMenus() {
|
||||
const searchMenus = [];
|
||||
for (const key of this.env.searchModel.searchMenuTypes) {
|
||||
// look in display instead?
|
||||
if (
|
||||
key === "comparison" &&
|
||||
this.env.searchModel.getSearchItems((i) => i.type === "comparison").length === 0
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
searchMenus.push({ Component: MAPPING[key], key });
|
||||
}
|
||||
return searchMenus;
|
||||
}
|
||||
|
||||
openSearchDialog() {
|
||||
this.dialog.add(ControlPanelSearchDialog, {
|
||||
slots: this.props.slots,
|
||||
display: this.display,
|
||||
searchMenus: this.searchMenus,
|
||||
env: {
|
||||
searchModel: this.env.searchModel,
|
||||
config: this.env.config,
|
||||
},
|
||||
});
|
||||
return {
|
||||
layoutActions: true,
|
||||
...this.props.display,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -245,10 +179,10 @@ export class ControlPanel extends Component {
|
||||
}
|
||||
|
||||
ControlPanel.components = {
|
||||
...Object.values(MAPPING),
|
||||
Pager,
|
||||
SearchBar,
|
||||
Dropdown,
|
||||
DropdownItem,
|
||||
};
|
||||
ControlPanel.template = "web.ControlPanel";
|
||||
ControlPanel.props = {
|
||||
|
||||
@@ -1,211 +1,31 @@
|
||||
.o_control_panel {
|
||||
border-bottom: var(--ControlPanel-border-bottom, #{$o-control-panel-border-bottom});
|
||||
@include o-webclient-padding($top: map-get($spacers, 2), $bottom: map-get($spacers, 2));
|
||||
background-color: $o-control-panel-background-color;
|
||||
|
||||
> div {
|
||||
display: flex;
|
||||
min-height: $o-statusbar-height;
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
.o_cp_top_left, .o_cp_top_right,
|
||||
.o_cp_bottom_left, .o_cp_bottom_right {
|
||||
width: 50%;
|
||||
}
|
||||
.o_control_panel_breadcrumbs {
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(md) {
|
||||
> div.o_cp_bottom {
|
||||
justify-content: space-between;
|
||||
min-height: auto;
|
||||
}
|
||||
|
||||
&.o_mobile_sticky {
|
||||
@include o-position-sticky();
|
||||
z-index: 10;
|
||||
}
|
||||
}
|
||||
|
||||
.o_cp_action_menus {
|
||||
.o_dropdown_title {
|
||||
@include visually-hidden;
|
||||
}
|
||||
|
||||
.o_dropdown_chevron, .o_dropdown_caret {
|
||||
display: none;
|
||||
}
|
||||
@include media-breakpoint-up(lg) {
|
||||
.o_control_panel_breadcrumbs, .o_control_panel_navigation {
|
||||
flex: 1
|
||||
}
|
||||
|
||||
.o_cp_searchview {
|
||||
min-height: 35px;
|
||||
|
||||
&.o_searchview_quick {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
|
||||
> .o_searchview {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid $o-brand-secondary;
|
||||
|
||||
> .o_searchview_input_container {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
> .o_enable_searchview {
|
||||
margin: 0;
|
||||
padding-left: 0;
|
||||
&, &:hover {
|
||||
color: $gray-600;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_cp_switch_buttons {
|
||||
&.show > .dropdown-menu {
|
||||
display: inline-flex;
|
||||
min-width: 0px;
|
||||
}
|
||||
.o_control_panel_actions {
|
||||
min-width: MIN(500px, 33%);
|
||||
}
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
font-size: 18px;
|
||||
min-width: 0;
|
||||
|
||||
> li {
|
||||
@include o-text-overflow();
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(md) {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
flex-wrap: nowrap;
|
||||
|
||||
> li {
|
||||
margin: 0;
|
||||
|
||||
&:before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.o_back_button {
|
||||
flex-shrink: 0;
|
||||
&, & ~ li {
|
||||
margin: auto 0;
|
||||
}
|
||||
|
||||
&:before {
|
||||
font-family: FontAwesome;
|
||||
content: ""; // fa-arrow-left
|
||||
display: inline-block;
|
||||
|
||||
padding: 0; // override bootstrap
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
&.btn {
|
||||
padding: $o-cp-button-sm-no-border-padding;
|
||||
}
|
||||
|
||||
> a {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_cp_top_right {
|
||||
min-height: $o-cp-breadcrumb-height;
|
||||
}
|
||||
|
||||
.o_cp_bottom_left {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: map-get($spacers, 3) map-get($spacers, 2);
|
||||
|
||||
> .o_cp_action_menus {
|
||||
margin-left: auto;
|
||||
padding-right: 0;
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(sm) {
|
||||
display: flex;
|
||||
padding-right: 25px;
|
||||
|
||||
> .btn-group {
|
||||
margin: auto 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.dropdown-toggle {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.o_hidden_input_file {
|
||||
position: relative;
|
||||
input.o_input_file {
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
opacity: 0;
|
||||
width: 100%;
|
||||
height: 26px;
|
||||
}
|
||||
.o_form_binary_form span {
|
||||
padding: 3px 25px;
|
||||
color: $o-brand-primary;
|
||||
}
|
||||
.o_form_binary_form:hover {
|
||||
background-color: $table-hover-bg;
|
||||
}
|
||||
}
|
||||
.o_sidebar_delete_attachment {
|
||||
padding: 0px;
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 10px;
|
||||
}
|
||||
.dropdown-toggle {
|
||||
margin-right: 0;
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
margin-right: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_cp_bottom_right {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
column-gap: $o-horizontal-padding;
|
||||
}
|
||||
|
||||
> .o_form_status_indicator {
|
||||
@include media-breakpoint-down(md) {
|
||||
.o_form_status_indicator_buttons > .btn {
|
||||
padding: $o-cp-button-sm-no-border-padding;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .o_cp_pager {
|
||||
padding-left: 5px;
|
||||
text-align: center;
|
||||
user-select: none;
|
||||
.o_pager_limit_fetch:not(.disabled), .o_pager_value {
|
||||
cursor: pointer;
|
||||
}
|
||||
@include media-breakpoint-up(xl) {
|
||||
.o_control_panel_actions {
|
||||
min-width: MIN(600px, 33%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,206 +2,95 @@
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="web.ControlPanel" owl="1">
|
||||
<div class="o_control_panel" t-ref="root">
|
||||
<t t-if="env.isSmall">
|
||||
<t t-call="web.ControlPanel.Small" />
|
||||
</t>
|
||||
<t t-else="">
|
||||
<t t-call="web.ControlPanel.Regular" />
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="web.ControlPanel.Regular" owl="1">
|
||||
<div t-if="display['top']" class="o_cp_top">
|
||||
<div class="o_cp_top_left">
|
||||
<t t-slot="control-panel-top-left" t-if="display['top-left']">
|
||||
<t t-call="web.Breadcrumbs" t-if="!env.config.noBreadcrumbs"/>
|
||||
</t>
|
||||
</div>
|
||||
<div class="o_cp_top_right">
|
||||
<t t-slot="control-panel-top-right" t-if="display['top-right']">
|
||||
<SearchBar/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
<div t-if="display['bottom']" class="o_cp_bottom">
|
||||
<div class="o_cp_bottom_left" t-on-keydown="onBottomLeftKeydown">
|
||||
<t t-slot="control-panel-bottom-left-buttons" t-if="display['bottom-left'] and display['bottom-left-buttons']" />
|
||||
<t t-slot="control-panel-bottom-left" t-if="display['bottom-left']"/>
|
||||
</div>
|
||||
<div t-if="display['bottom-right']" class="o_cp_bottom_right">
|
||||
<t t-slot="control-panel-bottom-right">
|
||||
<div class="btn-group o_search_options position-static" role="search">
|
||||
<t t-foreach="searchMenus" t-as="menu" t-key="menu.key">
|
||||
<t t-component="menu.Component"/>
|
||||
</t>
|
||||
<div class="o_control_panel d-flex flex-column gap-3 gap-lg-1 px-3 pt-2 pb-3" t-ref="root">
|
||||
<div class="o_control_panel_main d-flex flex-wrap flex-lg-nowrap justify-content-between align-items-lg-start gap-3">
|
||||
<div class="o_control_panel_breadcrumbs d-flex align-items-center w-100 w-md-50 w-lg-auto">
|
||||
<div class="o_control_panel_create_button d-empty-none d-print-none me-2">
|
||||
<t t-slot="control-panel-create-button"/>
|
||||
<t t-slot="control-panel-always-buttons"/>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<div t-if="pagerProps and pagerProps.total > 0" class="o_cp_pager" role="search">
|
||||
<Pager t-props="pagerProps"/>
|
||||
<t t-if="env.config.noBreadcrumbs">
|
||||
<section class="o_control_panel_breadcrumbs_actions d-contents">
|
||||
<t t-slot="control-panel-additional-actions"/>
|
||||
<t t-slot="control-panel-status-indicator" />
|
||||
</section>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<t t-call="web.Breadcrumbs"/>
|
||||
</t>
|
||||
<span class="d-none d-xl-block me-auto"/> <!-- Spacer -->
|
||||
</div>
|
||||
|
||||
<t t-if="env.config.viewSwitcherEntries and env.config.viewSwitcherEntries.length > 1">
|
||||
<nav class="btn-group o_cp_switch_buttons ms-auto">
|
||||
<div t-if="display.layoutActions" class="o_control_panel_actions d-flex align-items-center justify-content-around w-100 w-lg-auto order-3 order-lg-2">
|
||||
<t t-slot="layout-actions"/>
|
||||
</div>
|
||||
|
||||
<div class="o_control_panel_navigation d-flex flex-wrap flex-md-nowrap align-items-center justify-content-between justify-content-md-end w-100 w-md-auto gap-3 gap-lg-1 gap-xl-3 order-2 order-lg-3">
|
||||
<span class="d-none d-xl-block ms-auto"/> <!-- Spacer -->
|
||||
<div t-if="pagerProps and pagerProps.total > 0" class="o_cp_pager text-nowrap order-2 order-md-1" role="search">
|
||||
<Pager t-props="pagerProps"/>
|
||||
</div>
|
||||
<nav t-if="env.config.viewSwitcherEntries?.length > 1"
|
||||
class="o_cp_switch_buttons d-print-none btn-group order-1"
|
||||
t-att-class="{'w-100 w-sm-auto': env.config.viewSwitcherEntries?.length > 5}">
|
||||
<t t-foreach="env.config.viewSwitcherEntries" t-as="view" t-key="view.type">
|
||||
<button class="btn btn-light o_switch_view "
|
||||
<button class="btn btn-secondary o_switch_view "
|
||||
t-attf-class="o_{{view.type}} {{view.icon}} {{view.active ? 'active' : ''}}"
|
||||
t-att-data-tooltip="view.name"
|
||||
t-on-click="() => this.onViewClicked(view.type)"
|
||||
/>
|
||||
</t>
|
||||
</nav>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
<div class="o_control_panel_secondary d-flex gap-2">
|
||||
<t t-slot="layout-buttons"/>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="web.ControlPanel.Small" owl="1">
|
||||
<div t-if="display['top']" class="o_cp_top">
|
||||
<t t-if="display['top-left'] and !state.showSearchBar">
|
||||
<t t-slot="control-panel-top-left">
|
||||
<t t-call="web.Breadcrumbs.Small" />
|
||||
</t>
|
||||
</t>
|
||||
<t t-if="display['top-right']">
|
||||
<button type="button" class="o_enable_searchview btn btn-link"
|
||||
t-att-class="state.showSearchBar ? 'fa fa-arrow-left' : 'oi oi-search'"
|
||||
t-on-click="() => state.showSearchBar = !state.showSearchBar"
|
||||
/>
|
||||
<t t-if="state.showSearchBar or !display['top-left']">
|
||||
<t t-slot="control-panel-top-right">
|
||||
<SearchBar />
|
||||
<button
|
||||
type="button"
|
||||
class="o_toggle_searchview_full btn fa fa-filter"
|
||||
t-on-click="() => this.openSearchDialog()"
|
||||
/>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
</div>
|
||||
<div t-if="display['bottom']" class="o_cp_bottom">
|
||||
<div t-if="display['bottom-left']" class="o_cp_bottom_left">
|
||||
<t t-slot="control-panel-bottom-left-buttons" t-if="display['bottom-left-buttons']"/>
|
||||
<t t-slot="control-panel-bottom-left"/>
|
||||
</div>
|
||||
<div t-if="display['bottom-right']" class="o_cp_bottom_right">
|
||||
<div t-if="pagerProps and pagerProps.total > 0" class="o_cp_pager" role="search">
|
||||
<Pager t-props="pagerProps"/>
|
||||
</div>
|
||||
|
||||
<t t-if="env.config.viewSwitcherEntries and env.config.viewSwitcherEntries.length > 1">
|
||||
<nav class="btn-group o_cp_switch_buttons">
|
||||
<t t-set="view" t-value="env.config.viewSwitcherEntries.find((v) => v.active)" />
|
||||
|
||||
<Dropdown
|
||||
position="'bottom-end'"
|
||||
menuClass="'d-inline-flex'"
|
||||
togglerClass="'btn btn-link'"
|
||||
>
|
||||
<t t-set-slot="toggler">
|
||||
<i
|
||||
class="fa-lg o_switch_view"
|
||||
t-attf-class="o_{{view.type}} {{view.icon}} {{view.active ? 'active' : ''}}"
|
||||
/>
|
||||
</t>
|
||||
<t t-foreach="env.config.viewSwitcherEntries" t-as="view" t-key="view.type">
|
||||
<button class="btn btn-light fa-lg o_switch_view"
|
||||
t-attf-class="o_{{view.type}} {{view.icon}} {{view.active ? 'active' : ''}}"
|
||||
t-att-data-tooltip="view.name"
|
||||
t-on-click="() => this.onViewClicked(view.type)"
|
||||
/>
|
||||
</t>
|
||||
</Dropdown>
|
||||
</nav>
|
||||
</t>
|
||||
<t t-slot="control-panel-bottom-right"/>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="web.ControlPanelSearchDialog" owl="1">
|
||||
<Dialog>
|
||||
<div class="o_searchview o_mobile_search">
|
||||
<div class="o_mobile_search_header">
|
||||
<button
|
||||
type="button"
|
||||
class="o_mobile_search_button btn"
|
||||
t-on-click="() => this.props.close()"
|
||||
>
|
||||
<i class="fa fa-arrow-left"/>
|
||||
<strong class="ms-2">FILTER</strong>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="o_mobile_search_button btn"
|
||||
t-on-click="() => env.searchModel.clearQuery()"
|
||||
>
|
||||
CLEAR
|
||||
</button>
|
||||
</div>
|
||||
<div class="o_mobile_search_content">
|
||||
<t t-if="props.display['top-right']">
|
||||
<t t-slot="control-panel-top-right">
|
||||
<SearchBar/>
|
||||
</t>
|
||||
</t>
|
||||
<t t-if="props.display['bottom-right']">
|
||||
<t t-slot="control-panel-bottom-right">
|
||||
<div class="o_mobile_search_filter o_search_options">
|
||||
<t t-foreach="props.searchMenus" t-as="menu" t-key="menu.key">
|
||||
<t t-component="menu.Component"/>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary o_mobile_search_footer"
|
||||
t-on-click="() => this.props.close()"
|
||||
>
|
||||
SEE RESULT
|
||||
</button>
|
||||
</div>
|
||||
</Dialog>
|
||||
</t>
|
||||
|
||||
<t t-name="web.Breadcrumbs" owl="1">
|
||||
<ol class="breadcrumb">
|
||||
<t t-foreach="breadcrumbs" t-as="breadcrumb" t-key="breadcrumb.jsId">
|
||||
<t t-set="isPenultimate" t-value="breadcrumb_index === breadcrumbs.length - 2"/>
|
||||
<li t-if="!breadcrumb_last" class="breadcrumb-item" t-att-data-hotkey="isPenultimate and 'b'" t-att-class="{ o_back_button: isPenultimate}" t-on-click.prevent="() => this.onBreadcrumbClicked(breadcrumb.jsId)">
|
||||
<a href="#">
|
||||
<t t-if="breadcrumb.name" t-esc="breadcrumb.name"/>
|
||||
<em t-else="" class="text-warning">Unnamed</em>
|
||||
</a>
|
||||
<t t-set="currentBreadcrumbs" t-value="breadcrumbs.slice(-1)"/>
|
||||
<t t-set="visiblePathBreadcrumbs" t-value="breadcrumbs.slice(-3, -1)"/>
|
||||
<t t-set="collapsedBreadcrumbs" t-value="breadcrumbs.slice(0, -3)"/>
|
||||
<t t-set="breadcrumb" t-value="currentBreadcrumbs[0] || {}"/>
|
||||
|
||||
<div t-if="collapsedBreadcrumbs.length || visiblePathBreadcrumbs.length" class="d-flex flex-column align-self-stretch justify-content-between min-w-0">
|
||||
<ol class="breadcrumb flex-nowrap text-nowrap small lh-1">
|
||||
<li t-if="collapsedBreadcrumbs.length" class="breadcrumb-item d-inline-flex min-w-0">
|
||||
<Dropdown togglerClass="'btn btn-light btn-sm p-0'">
|
||||
<t t-set-slot="toggler">
|
||||
<i class="fa fa-ellipsis-h"/>
|
||||
</t>
|
||||
<t t-foreach="collapsedBreadcrumbs" t-as="breadcrumb" t-key="breadcrumb.jsId">
|
||||
<DropdownItem onSelected="() => this.onBreadcrumbClicked(breadcrumb.jsId)">
|
||||
<t t-call="web.Breadcrumb.Name"/>
|
||||
</DropdownItem>
|
||||
</t>
|
||||
</Dropdown>
|
||||
</li>
|
||||
<li t-else="" class="breadcrumb-item active d-flex align-items-center">
|
||||
<span class="text-truncate" t-if="breadcrumb.name" t-esc="breadcrumb.name"/>
|
||||
<em t-else="" class="text-warning">Unnamed</em>
|
||||
<t t-slot="control-panel-status-indicator" />
|
||||
</li>
|
||||
</t>
|
||||
</ol>
|
||||
<t t-foreach="visiblePathBreadcrumbs" t-as="breadcrumb" t-key="breadcrumb.jsId">
|
||||
<li class="breadcrumb-item d-inline-flex min-w-0" t-att-data-hotkey="breadcrumb_last and 'b'" t-on-click.prevent="() => this.onBreadcrumbClicked(breadcrumb.jsId)">
|
||||
<a href="#" class="fw-bold text-truncate" t-att-data-tooltip="'Back to "' + breadcrumb.name + '"'"><t t-call="web.Breadcrumb.Name"/></a>
|
||||
</li>
|
||||
</t>
|
||||
</ol>
|
||||
<div class="active d-flex gap-2 align-items-center min-w-0 lh-sm">
|
||||
<span class="min-w-0 text-truncate" t-call="web.Breadcrumb.Name"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div t-else="" class="active d-flex fs-4 min-w-0">
|
||||
<span class="min-w-0 text-truncate" t-call="web.Breadcrumb.Name"/>
|
||||
</div>
|
||||
|
||||
<t t-slot="control-panel-additional-actions"/>
|
||||
<t t-slot="control-panel-status-indicator" />
|
||||
</t>
|
||||
|
||||
<t t-name="web.Breadcrumbs.Small" owl="1">
|
||||
<ol class="breadcrumb">
|
||||
<t t-if="breadcrumbs.length > 1">
|
||||
<t t-set="breadcrumb" t-value="breadcrumbs[breadcrumbs.length - 2]" />
|
||||
<li class="breadcrumb-item o_back_button btn btn-secondary"
|
||||
t-on-click.prevent="() => this.onBreadcrumbClicked(breadcrumb.jsId)"
|
||||
/>
|
||||
</t>
|
||||
<li t-if="breadcrumbs.length > 0" class="breadcrumb-item active">
|
||||
<t t-set="breadcrumb" t-value="breadcrumbs[breadcrumbs.length - 1]" />
|
||||
<t t-if="breadcrumb.name" t-esc="breadcrumb.name"/>
|
||||
<em t-else="" class="text-warning">Unnamed</em>
|
||||
</li>
|
||||
</ol>
|
||||
<t t-name="web.Breadcrumb.Name" owl="1">
|
||||
<t t-if="breadcrumb.name" t-out="breadcrumb.name"/>
|
||||
<em t-else="" class="text-warning">Unnamed</em>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
||||
@@ -26,8 +26,6 @@ export class Layout extends Component {
|
||||
}
|
||||
get controlPanelSlots() {
|
||||
const slots = { ...this.props.slots };
|
||||
slots["control-panel-bottom-left-buttons"] = slots["layout-buttons"];
|
||||
delete slots["layout-buttons"];
|
||||
delete slots.default;
|
||||
return slots;
|
||||
}
|
||||
@@ -38,11 +36,7 @@ export class Layout extends Component {
|
||||
}
|
||||
return {
|
||||
...this.props.display,
|
||||
controlPanel: {
|
||||
...controlPanel,
|
||||
"top-left": false,
|
||||
"bottom-left-buttons": false,
|
||||
},
|
||||
controlPanel,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,16 @@ import { useAutofocus, useBus, useService } from "@web/core/utils/hooks";
|
||||
import { DomainSelectorDialog } from "@web/core/domain_selector_dialog/domain_selector_dialog";
|
||||
import { fuzzyTest } from "@web/core/utils/search";
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { SearchBarMenu } from "../search_bar_menu/search_bar_menu";
|
||||
|
||||
import { Component, useExternalListener, useRef, useState } from "@odoo/owl";
|
||||
import {
|
||||
Component,
|
||||
onMounted,
|
||||
onWillUnmount,
|
||||
useExternalListener,
|
||||
useRef,
|
||||
useState,
|
||||
} from "@odoo/owl";
|
||||
const parsers = registry.category("parsers");
|
||||
|
||||
const CHAR_FIELDS = ["char", "html", "many2many", "many2one", "one2many", "text", "properties"];
|
||||
@@ -29,6 +37,7 @@ export class SearchBar extends Component {
|
||||
expanded: [],
|
||||
focusedIndex: 0,
|
||||
query: "",
|
||||
showSearchBar: !this.env.isSmall,
|
||||
});
|
||||
|
||||
// derived state
|
||||
@@ -363,9 +372,9 @@ export class SearchBar extends Component {
|
||||
// Handlers
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
onFacetClick(target, facet) {
|
||||
onFacetLabelClick(target, facet) {
|
||||
const { domain, groupId } = facet;
|
||||
if (!target.classList.contains("o_searchview_facet_label") || !domain) {
|
||||
if (!domain) {
|
||||
return;
|
||||
}
|
||||
const { resModel } = this.env.searchModel;
|
||||
@@ -543,6 +552,10 @@ export class SearchBar extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
onToggleSearchBar() {
|
||||
this.state.showSearchBar = !this.state.showSearchBar;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
@@ -563,4 +576,7 @@ export class SearchBar extends Component {
|
||||
}
|
||||
|
||||
SearchBar.template = "web.SearchBar";
|
||||
SearchBar.components = {
|
||||
SearchBarMenu,
|
||||
};
|
||||
SearchBar.props = {};
|
||||
|
||||
@@ -2,16 +2,12 @@
|
||||
// ============================================================================
|
||||
|
||||
.o_searchview_facet {
|
||||
cursor: default;
|
||||
.o_facet_values {
|
||||
border: $border-width solid var(--SearchBar-facet-background, #{$o-brand-odoo});
|
||||
@keyframes animate {
|
||||
from {
|
||||
opacity: .5;
|
||||
transform: translateY(20%);
|
||||
}
|
||||
}
|
||||
.o_searchview_facet_label {
|
||||
background: var(--SearchBar-facet-background, #{$o-brand-odoo});
|
||||
color: var(--SearchBar-facet-color, #{$o-white});
|
||||
}
|
||||
&.o_facet_with_domain:hover .o_searchview_facet_label {
|
||||
cursor: pointer;
|
||||
background: var(--SearchBar-facet-background, $primary);
|
||||
}
|
||||
}
|
||||
|
||||
animation: animate .25s ease-out;
|
||||
}
|
||||
@@ -3,41 +3,45 @@
|
||||
|
||||
<t t-name="web.SearchBar.Facets" owl="1">
|
||||
<t t-foreach="env.searchModel.facets" t-as="facet" t-key="facet_index">
|
||||
<div class="o_searchview_facet"
|
||||
<div class="o_searchview_facet d-inline-flex align-items-stretch text-nowrap position-relative"
|
||||
t-att-class="{o_facet_with_domain: facet.domain }"
|
||||
role="img"
|
||||
role="listitem"
|
||||
aria-label="search"
|
||||
tabindex="0"
|
||||
t-on-keydown="ev => this.onFacetKeydown(facet, facet_index, ev)"
|
||||
t-on-click="(ev) => this.onFacetClick(ev.target, facet)"
|
||||
>
|
||||
<t t-if="facet.icon">
|
||||
<span t-attf-class="o_searchview_facet_label {{ facet.icon }}"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<span class="o_searchview_facet_label" t-esc="facet.title"/>
|
||||
</t>
|
||||
<div class="o_facet_values border-start-0">
|
||||
<t t-foreach="facet.values" t-as="facetValue" t-key="facetValue_index">
|
||||
<t t-if="!facetValue_first">
|
||||
<span class="o_facet_values_sep" t-esc="facet.separator"/>
|
||||
</t>
|
||||
<span class="o_facet_value" t-esc="facetValue"/>
|
||||
</t>
|
||||
<div class="o_searchview_facet_label rounded-start-2"
|
||||
t-on-click="(ev) => this.onFacetLabelClick(ev.target, facet)"
|
||||
t-att-role="facet.type != 'groupBy' ? 'button' : 'img'"
|
||||
t-att-class="{
|
||||
'text-bg-action d-flex align-items-center': facet.type == 'groupBy',
|
||||
'btn btn-primary rounded-end-0 p-0': facet.type == 'field' || facet.type == 'filter',
|
||||
'btn btn-favourite rounded-end-0 p-0': facet.type == 'favorite'
|
||||
}"
|
||||
>
|
||||
<i t-if="facet.icon" class="px-1 small" t-att-class="facet.icon" role="image"/>
|
||||
<small t-else="" class="px-2" t-esc="facet.title"/>
|
||||
</div>
|
||||
|
||||
<div class="o_facet_values d-flex align-items-center ps-2 bg-200 rounded-end-2">
|
||||
<t t-foreach="facet.values" t-as="facetValue" t-key="facetValue_index">
|
||||
<em t-if="!facetValue_first" class="o_facet_values_sep small fw-bold mx-1 opacity-50" t-esc="facet.separator"/>
|
||||
<small class="o_facet_value" t-esc="facetValue"/>
|
||||
</t>
|
||||
<button class="o_facet_remove oi oi-close btn btn-link py-0 px-2 opacity-50 opacity-100-hover text-900"
|
||||
role="button"
|
||||
aria-label="Remove"
|
||||
title="Remove"
|
||||
t-on-click="() => this.onFacetRemove(facet)"
|
||||
/>
|
||||
</div>
|
||||
<i class="o_facet_remove oi oi-close btn btn-link opacity-50 opacity-100-hover text-900"
|
||||
role="img"
|
||||
aria-label="Remove"
|
||||
title="Remove"
|
||||
t-on-click="() => this.onFacetRemove(facet)"
|
||||
/>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<t t-name="web.SearchBar.Input" owl="1">
|
||||
<input type="text"
|
||||
class="o_searchview_input"
|
||||
class="o_searchview_input d-print-none flex-grow-1 w-auto border-0"
|
||||
accesskey="Q"
|
||||
placeholder="Search..."
|
||||
role="searchbox"
|
||||
@@ -76,14 +80,14 @@
|
||||
</t>
|
||||
|
||||
<t t-name="web.SearchBar" owl="1">
|
||||
<div class="o_cp_searchview d-flex flex-grow-1" role="search" t-ref="root">
|
||||
<div class="o_searchview pb-1 align-self-center border-bottom flex-grow-1" role="search" aria-autocomplete="list">
|
||||
<i class="o_searchview_icon oi oi-search"
|
||||
<div t-if="state.showSearchBar" class="o_cp_searchview d-flex input-group" role="search" t-ref="root">
|
||||
<div class="o_searchview form-control d-print-contents d-flex align-items-center py-1 border-end-0" role="search" aria-autocomplete="list">
|
||||
<i class="o_searchview_icon d-print-none oi oi-search me-2"
|
||||
role="img"
|
||||
aria-label="Search..."
|
||||
title="Search..."
|
||||
/>
|
||||
<div class="o_searchview_input_container">
|
||||
<div class="o_searchview_input_container d-flex flex-grow-1 flex-wrap gap-1">
|
||||
<t t-call="web.SearchBar.Facets"/>
|
||||
<t t-call="web.SearchBar.Input"/>
|
||||
<t t-if="items.length">
|
||||
@@ -91,7 +95,13 @@
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
<SearchBarMenu/>
|
||||
</div>
|
||||
<t t-if="env.isSmall" t-portal="'.o_control_panel_navigation'">
|
||||
<button class="btn btn-secondary" t-on-click="onToggleSearchBar">
|
||||
<i class="fa fa-fw fa-search"/>
|
||||
</button>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { AdvancedSearchDialog } from "@web/search/filter_menu/advanced_search_dialog";
|
||||
import { Component } from "@odoo/owl";
|
||||
import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
|
||||
import { Domain } from "@web/core/domain";
|
||||
import { Dropdown } from "@web/core/dropdown/dropdown";
|
||||
import { DropdownItem } from "@web/core/dropdown/dropdown_item";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { sortBy } from "@web/core/utils/arrays";
|
||||
import { useBus, useService } from "@web/core/utils/hooks";
|
||||
import { CustomGroupByItem } from "@web/search/group_by_menu/custom_group_by_item";
|
||||
import { SearchDropdownItem } from "@web/search/search_dropdown_item/search_dropdown_item";
|
||||
import { FACET_ICONS, GROUPABLE_TYPES } from "@web/search/utils/misc";
|
||||
|
||||
const favoriteMenuRegistry = registry.category("favoriteMenu");
|
||||
|
||||
export class SearchBarMenu extends Component {
|
||||
static template = "web.SearchBarMenu";
|
||||
static components = {
|
||||
Dropdown,
|
||||
DropdownItem,
|
||||
SearchDropdownItem,
|
||||
CustomGroupByItem,
|
||||
};
|
||||
static props = {};
|
||||
|
||||
setup() {
|
||||
this.facet_icons = FACET_ICONS;
|
||||
// Filter
|
||||
this.dialogService = useService("dialog");
|
||||
this.notification = useService("notification");
|
||||
// GroupBy
|
||||
const fields = [];
|
||||
for (const [fieldName, field] of Object.entries(this.env.searchModel.searchViewFields)) {
|
||||
if (this.validateField(fieldName, field)) {
|
||||
fields.push(Object.assign({ name: fieldName }, field));
|
||||
}
|
||||
}
|
||||
this.fields = sortBy(fields, "string");
|
||||
// Favorite
|
||||
this.dialogService = useService("dialog");
|
||||
|
||||
useBus(this.env.searchModel, "update", this.render);
|
||||
}
|
||||
|
||||
// Filter Panel
|
||||
get filterItems() {
|
||||
return this.env.searchModel.getSearchItems((searchItem) =>
|
||||
["filter", "dateFilter"].includes(searchItem.type)
|
||||
);
|
||||
}
|
||||
|
||||
onAdvancedSearchClick() {
|
||||
const domains = [this.env.searchModel._getDomain({ withGlobal: false })];
|
||||
if (this.env.searchModel.comparison && !this.env.searchModel.globalComparison) {
|
||||
const { range } = this.env.searchModel.getFullComparison();
|
||||
domains.push(range);
|
||||
}
|
||||
const domain = Domain.and(domains).toString();
|
||||
this.dialogService.add(AdvancedSearchDialog, {
|
||||
domain,
|
||||
onConfirm: (domain) => this.onConfirm(domain),
|
||||
resModel: this.env.searchModel.resModel,
|
||||
isDebugMode: !!this.env.debug,
|
||||
});
|
||||
}
|
||||
|
||||
async onConfirm(domain) {
|
||||
const isValid = await this.env.searchModel.isValidDomain(domain);
|
||||
if (!isValid) {
|
||||
this.notification.add(this.env._t("Domain is invalid. Please correct it"), {
|
||||
type: "danger",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
this.env.searchModel.createAdvancedDomain(domain);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} param0
|
||||
* @param {number} param0.itemId
|
||||
* @param {number} [param0.optionId]
|
||||
*/
|
||||
onFilterSelected({ itemId, optionId }) {
|
||||
if (optionId) {
|
||||
this.env.searchModel.toggleDateFilter(itemId, optionId);
|
||||
} else {
|
||||
this.env.searchModel.toggleSearchItem(itemId);
|
||||
}
|
||||
}
|
||||
|
||||
// GroupBy Panel
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get hideCustomGroupBy() {
|
||||
return this.env.searchModel.hideCustomGroupBy || false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Object[]}
|
||||
*/
|
||||
get groupByItems() {
|
||||
return this.env.searchModel.getSearchItems((searchItem) =>
|
||||
["groupBy", "dateGroupBy"].includes(searchItem.type)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} fieldName
|
||||
* @param {Object} field
|
||||
* @returns {boolean}
|
||||
*/
|
||||
validateField(fieldName, field) {
|
||||
const { sortable, store, type } = field;
|
||||
return (
|
||||
(type === "many2many" ? store : sortable) &&
|
||||
fieldName !== "id" &&
|
||||
GROUPABLE_TYPES.includes(type)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} param0
|
||||
* @param {number} param0.itemId
|
||||
* @param {number} [param0.optionId]
|
||||
*/
|
||||
onGroupBySelected({ itemId, optionId }) {
|
||||
if (optionId) {
|
||||
this.env.searchModel.toggleDateGroupBy(itemId, optionId);
|
||||
} else {
|
||||
this.env.searchModel.toggleSearchItem(itemId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} fieldName
|
||||
*/
|
||||
onAddCustomGroup(fieldName) {
|
||||
this.env.searchModel.createNewGroupBy(fieldName);
|
||||
}
|
||||
|
||||
// Comparison Panel
|
||||
get showComparisonMenu() {
|
||||
return (
|
||||
this.env.searchModel.searchMenuTypes.has("comparison") &&
|
||||
this.env.searchModel.getSearchItems((i) => i.type === "comparison").length > 0
|
||||
);
|
||||
}
|
||||
get comparisonItems() {
|
||||
return this.env.searchModel.getSearchItems(
|
||||
(searchItem) => searchItem.type === "comparison"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} itemId
|
||||
*/
|
||||
onComparisonSelected(itemId) {
|
||||
this.env.searchModel.toggleSearchItem(itemId);
|
||||
}
|
||||
|
||||
// Favorite Panel
|
||||
/**
|
||||
* @returns {Array}
|
||||
*/
|
||||
get favoriteItems() {
|
||||
const favorites = this.env.searchModel.getSearchItems(
|
||||
(searchItem) => searchItem.type === "favorite"
|
||||
);
|
||||
const registryMenus = [];
|
||||
for (const item of favoriteMenuRegistry.getAll()) {
|
||||
if ("isDisplayed" in item ? item.isDisplayed(this.env) : true) {
|
||||
registryMenus.push({
|
||||
Component: item.Component,
|
||||
groupNumber: item.groupNumber,
|
||||
key: item.Component.name,
|
||||
});
|
||||
}
|
||||
}
|
||||
return [...favorites, ...registryMenus];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} itemId
|
||||
*/
|
||||
onFavoriteSelected(itemId) {
|
||||
this.env.searchModel.toggleSearchItem(itemId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} itemId
|
||||
*/
|
||||
openConfirmationDialog(itemId) {
|
||||
const { userId } = this.favoriteItems.find((item) => item.id === itemId);
|
||||
const dialogProps = {
|
||||
title: this.env._t("Warning"),
|
||||
body: userId
|
||||
? this.env._t("Are you sure that you want to remove this filter?")
|
||||
: this.env._t(
|
||||
"This filter is global and will be removed for everybody if you continue."
|
||||
),
|
||||
confirm: () => this.env.searchModel.deleteFavorite(itemId),
|
||||
cancel: () => {},
|
||||
};
|
||||
this.dialogService.add(ConfirmationDialog, dialogProps);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// SearchBar Menu
|
||||
// ============================================================================
|
||||
.o_search_bar_menu {
|
||||
.o_dropdown_container {
|
||||
border-color: $dropdown-divider-bg !important;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="web.SearchBarMenu" owl="1">
|
||||
<Dropdown togglerClass="'o_searchview_dropdown_toggler d-print-none btn btn-outline-secondary o-no-caret rounded-start-0 h-100'"
|
||||
menuClass="'o_search_bar_menu d-flex flex-wrap flex-lg-nowrap mt-2 py-3'"
|
||||
position="'bottom-end'"
|
||||
t-if="this.env.searchModel.searchMenuTypes.size">
|
||||
<t t-set-slot="toggler" t-slot-scope="scope">
|
||||
<i class="fa fa-caret-down" aria-hidden="true"/>
|
||||
</t>
|
||||
<!-- Filter -->
|
||||
<t t-if="this.env.searchModel.searchMenuTypes.has('filter')">
|
||||
<div class="o_dropdown_container o_filter_menu w-100 w-lg-auto px-3 mb-4 mb-lg-0 border-end">
|
||||
<div class="px-3 fs-5 mb-2">
|
||||
<i class="me-2 text-primary" t-att-class="facet_icons.filter"/>
|
||||
<h5 class="o_dropdown_title d-inline">Filters</h5>
|
||||
</div>
|
||||
<t t-set="currentGroup" t-value="null"/>
|
||||
<t t-foreach="filterItems" t-as="item" t-key="item.id">
|
||||
<t t-if="currentGroup !== null and currentGroup !== item.groupNumber">
|
||||
<div class="dropdown-divider" role="separator"/>
|
||||
</t>
|
||||
<t t-if="item.options">
|
||||
<Dropdown togglerClass="'o_menu_item' + (item.isActive ? ' selected' : '')">
|
||||
<t t-set-slot="toggler">
|
||||
<t t-esc="item.description"/>
|
||||
</t>
|
||||
<t t-set="subGroup" t-value="null"/>
|
||||
<t t-foreach="item.options" t-as="option" t-key="option.id">
|
||||
<t t-if="subGroup !== null and subGroup !== option.groupNumber">
|
||||
<div class="dropdown-divider" role="separator"/>
|
||||
</t>
|
||||
<SearchDropdownItem class="{ o_item_option: true, selected: option.isActive }"
|
||||
t-esc="option.description"
|
||||
checked="option.isActive"
|
||||
parentClosingMode="'none'"
|
||||
onSelected="() => this.onFilterSelected({ itemId: item.id, optionId: option.id })"
|
||||
/>
|
||||
<t t-set="subGroup" t-value="option.groupNumber"/>
|
||||
</t>
|
||||
</Dropdown>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<SearchDropdownItem class="{ o_menu_item: true, selected: item.isActive }"
|
||||
checked="item.isActive"
|
||||
parentClosingMode="'none'"
|
||||
t-esc="item.description"
|
||||
onSelected="() => this.onFilterSelected({ itemId: item.id })"
|
||||
/>
|
||||
</t>
|
||||
<t t-set="currentGroup" t-value="item.groupNumber"/>
|
||||
</t>
|
||||
<t t-if="filterItems.length">
|
||||
<div role="separator" class="dropdown-divider"/>
|
||||
</t>
|
||||
<DropdownItem onSelected.bind="onAdvancedSearchClick">Advanced Search</DropdownItem>
|
||||
</div>
|
||||
</t>
|
||||
<!-- GroupBy -->
|
||||
<t t-if="this.env.searchModel.searchMenuTypes.has('groupBy')">
|
||||
<div class="o_dropdown_container o_group_by_menu w-100 w-lg-auto px-3 mb-4 mb-lg-0 border-end">
|
||||
<div class="px-3 fs-5 mb-2">
|
||||
<i class="me-2 text-action" t-att-class="facet_icons.groupBy"/>
|
||||
<h5 class="o_dropdown_title d-inline">Group By</h5>
|
||||
</div>
|
||||
<t t-set="currentGroup" t-value="null"/>
|
||||
<t t-foreach="groupByItems" t-as="item" t-key="item.id">
|
||||
<t t-if="currentGroup !== null and currentGroup !== item.groupNumber">
|
||||
<div class="dropdown-divider" role="separator"/>
|
||||
</t>
|
||||
<t t-if="item.options">
|
||||
<Dropdown togglerClass="'o_menu_item' + (item.isActive ? ' selected' : '')">
|
||||
<t t-set-slot="toggler">
|
||||
<t t-esc="item.description"/>
|
||||
</t>
|
||||
<t t-set="subGroup" t-value="null"/>
|
||||
<t t-foreach="item.options" t-as="option" t-key="option.id">
|
||||
<t t-if="subGroup !== null and subGroup !== option.groupNumber">
|
||||
<div class="dropdown-divider" role="separator"/>
|
||||
</t>
|
||||
<SearchDropdownItem class="{ o_item_option: true, selected: option.isActive }"
|
||||
checked="option.isActive ? true : false"
|
||||
parentClosingMode="'none'"
|
||||
t-esc="option.description"
|
||||
onSelected="() => this.onGroupBySelected({ itemId: item.id, optionId: option.id})"
|
||||
/>
|
||||
<t t-set="subGroup" t-value="option.groupNumber"/>
|
||||
</t>
|
||||
</Dropdown>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<SearchDropdownItem class="{ o_menu_item: true, selected: item.isActive }"
|
||||
checked="item.isActive"
|
||||
parentClosingMode="'none'"
|
||||
t-esc="item.description"
|
||||
onSelected="() => this.onGroupBySelected({ itemId: item.id })"
|
||||
/>
|
||||
</t>
|
||||
<t t-set="currentGroup" t-value="item.groupNumber"/>
|
||||
</t>
|
||||
<t t-if="!hideCustomGroupBy and fields.length">
|
||||
<div t-if="groupByItems.length" role="separator" class="dropdown-divider"/>
|
||||
<CustomGroupByItem fields="fields" onAddCustomGroup.bind="onAddCustomGroup"/>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
<!-- Comparison -->
|
||||
<t t-if="showComparisonMenu">
|
||||
<div class="o_dropdown_container o_comparison_menu w-100 w-lg-auto px-3 border-end">
|
||||
<div class="px-3 fs-5 mb-2">
|
||||
<i class="me-2 text-danger" t-att-class="facet_icons.comparison"/>
|
||||
<h5 class="o_dropdown_title d-inline">Comparison</h5>
|
||||
</div>
|
||||
<t t-foreach="comparisonItems" t-as="item" t-key="item.id">
|
||||
<SearchDropdownItem class="{ o_menu_item: true, selected: item.isActive }"
|
||||
checked="item.isActive"
|
||||
parentClosingMode="'none'"
|
||||
t-esc="item.description"
|
||||
onSelected="() => this.onComparisonSelected(item.id)"
|
||||
/>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
<!-- Favorite -->
|
||||
<t t-if="this.env.searchModel.searchMenuTypes.has('favorite')">
|
||||
<div class="o_dropdown_container o_favorite_menu w-100 w-lg-auto px-3">
|
||||
<div class="px-3 fs-5 mb-2">
|
||||
<i class="me-2 text-favourite" t-att-class="facet_icons.favorite"/>
|
||||
<h5 class="o_dropdown_title d-inline">Favorites</h5>
|
||||
</div>
|
||||
<t t-set="currentGroup" t-value="null"/>
|
||||
<t t-foreach="favoriteItems" t-as="item" t-key="item.id or item.key">
|
||||
<t t-if="currentGroup !== null and currentGroup !== item.groupNumber">
|
||||
<div role="separator" class="dropdown-divider"/>
|
||||
</t>
|
||||
<t t-if="item.type ==='favorite'">
|
||||
<SearchDropdownItem class="{ o_menu_item: true, selected: item.isActive }"
|
||||
checked="item.isActive"
|
||||
parentClosingMode="'none'"
|
||||
onSelected="() => this.onFavoriteSelected(item.id)"
|
||||
>
|
||||
<span class="d-flex p-0 align-items-center justify-content-between">
|
||||
<t t-esc="item.description"/>
|
||||
<i class="ms-1 o_icon_right fa fa-trash-o"
|
||||
title="Delete item"
|
||||
t-on-click.stop="() => this.openConfirmationDialog(item.id)"
|
||||
/>
|
||||
</span>
|
||||
</SearchDropdownItem>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<t t-component="item.Component"/>
|
||||
</t>
|
||||
<t t-set="currentGroup" t-value="item.groupNumber"/>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
</Dropdown>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -16,7 +16,8 @@ import {
|
||||
rankInterval,
|
||||
yearSelected,
|
||||
} from "./utils/dates";
|
||||
import { FACET_ICONS } from "./utils/misc";
|
||||
import { FACET_ICONS, FACET_COLORS } from "./utils/misc";
|
||||
|
||||
import { EventBus, toRaw } from "@odoo/owl";
|
||||
import { toDomain, toTree } from "@web/core/domain_tree";
|
||||
import { extractPathsFromDomain } from "@web/core/domain_selector/utils";
|
||||
@@ -1752,6 +1753,7 @@ export class SearchModel extends EventBus {
|
||||
facet.title = title;
|
||||
} else {
|
||||
facet.icon = FACET_ICONS[type];
|
||||
facet.color = FACET_COLORS[type];
|
||||
}
|
||||
if (groupActiveItemDomains.length) {
|
||||
facet.domain = Domain.or(groupActiveItemDomains).toString();
|
||||
|
||||
@@ -1,80 +1,28 @@
|
||||
.o_searchview {
|
||||
align-items: flex-end;
|
||||
padding: 0 20px 1px 0;
|
||||
position: relative;
|
||||
background-color: $input-focus-bg;
|
||||
border: $input-border-width solid $border-color;
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
border-bottom: 1px solid $border-color;
|
||||
padding-bottom: 3px;
|
||||
&:focus-within {
|
||||
background-color: $input-focus-bg;
|
||||
border-color: $input-focus-border-color;
|
||||
color: $input-focus-color;
|
||||
|
||||
&:focus-within {
|
||||
border-bottom-color: $primary;
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(md) {
|
||||
margin-right: 5px; // o_switch_view_button_icon alignment
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.o_searchview_input_container {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.o_searchview_facet {
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
margin: 1px 3px 0 0;
|
||||
max-width: 100%;
|
||||
position: relative;
|
||||
$o-searchview-facet-remove-width: 18px;
|
||||
|
||||
@include media-breakpoint-down(md) {
|
||||
flex-flow: row wrap;
|
||||
}
|
||||
|
||||
.o_searchview_facet_label {
|
||||
align-items: center;
|
||||
flex: 0 0 auto;
|
||||
padding: 0 3px;
|
||||
@include o-text-overflow($display: flex);
|
||||
}
|
||||
|
||||
.o_facet_values {
|
||||
direction: ltr#{"/*rtl:ignore*/"};
|
||||
padding: 0 $o-searchview-facet-remove-width 0 5px;
|
||||
|
||||
.o_facet_values_sep {
|
||||
font-style: italic;
|
||||
margin: 0 0.3rem;
|
||||
}
|
||||
}
|
||||
|
||||
.o_facet_remove {
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
justify-content: center;
|
||||
width: $o-searchview-facet-remove-width;
|
||||
@include o-position-absolute(0, 0, 0);
|
||||
@include o-hover-text-color($text-muted, $o-brand-odoo);
|
||||
+ .o-dropdown .o_searchview_dropdown_toggler {
|
||||
border-top-color: $input-focus-border-color;
|
||||
border-right-color: $input-focus-border-color;
|
||||
border-bottom-color: $input-focus-border-color;
|
||||
}
|
||||
}
|
||||
|
||||
.o_searchview_input {
|
||||
flex: 1 0 auto;
|
||||
width: 75px;
|
||||
border: none;
|
||||
outline: none;
|
||||
background-color: var(--o-input-background-color, #{$input-bg});
|
||||
appearance: none;
|
||||
color: inherit;
|
||||
background: inherit;
|
||||
}
|
||||
|
||||
.o_searchview_autocomplete {
|
||||
@include o-position-absolute(calc(100% + #{$border-width * 3}), 0);
|
||||
width: 100%;
|
||||
@include o-position-absolute(100%, $left: auto);
|
||||
|
||||
.o_menu_item {
|
||||
align-items: center;
|
||||
@@ -97,10 +45,6 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.o_searchview_icon {
|
||||
@include o-position-absolute($top: 4px, $right: 0);
|
||||
}
|
||||
}
|
||||
|
||||
.o_search_options {
|
||||
|
||||
@@ -7,6 +7,13 @@ export const FACET_ICONS = {
|
||||
comparison: "fa fa-adjust",
|
||||
};
|
||||
|
||||
export const FACET_COLORS = {
|
||||
filter: "primary",
|
||||
groupBy: "action",
|
||||
favorite: "warning",
|
||||
comparison: "danger",
|
||||
};
|
||||
|
||||
export const GROUPABLE_TYPES = [
|
||||
"boolean",
|
||||
"char",
|
||||
|
||||
@@ -13,6 +13,7 @@ import { CalendarDatePicker } from "./date_picker/calendar_date_picker";
|
||||
import { CalendarFilterPanel } from "./filter_panel/calendar_filter_panel";
|
||||
import { CalendarMobileFilterPanel } from "./mobile_filter_panel/calendar_mobile_filter_panel";
|
||||
import { CalendarQuickCreate } from "./quick_create/calendar_quick_create";
|
||||
import { SearchBar } from "@web/search/search_bar/search_bar";
|
||||
|
||||
import { Component, useState } from "@odoo/owl";
|
||||
|
||||
@@ -221,5 +222,6 @@ CalendarController.components = {
|
||||
QuickCreate: CalendarQuickCreate,
|
||||
Layout,
|
||||
ViewScaleSelector,
|
||||
SearchBar,
|
||||
};
|
||||
CalendarController.template = "web.CalendarController";
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
<t t-set-slot="layout-buttons">
|
||||
<t t-call="{{ props.buttonTemplate }}"/>
|
||||
</t>
|
||||
|
||||
<t t-set-slot="layout-actions">
|
||||
<SearchBar/>
|
||||
</t>
|
||||
<div class="o_calendar_container">
|
||||
<MobileFilterPanel t-if="env.isSmall" t-props="mobileFilterPanelProps" />
|
||||
<div t-if="showSideBar" class="o_calendar_sidebar_container d-md-block">
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
isDebugMode="!!env.debug"
|
||||
className="props.readonly ? 'o_read_mode' : 'o_edit_mode'"
|
||||
/>
|
||||
<div class="o_field_domain_panel" style="height:22px;">
|
||||
<div class="o_field_domain_panel d-flex align-items-center gap-2 mt-1">
|
||||
<i class="fa fa-arrow-right" role="img" aria-label="Domain" title="Domain" />
|
||||
<t t-if="state.isValid === null and state.recordCount === null">
|
||||
<i class="fa fa-circle-o-notch fa-spin ms-2" role="img" aria-label="Loading" title="Loading" />
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { ControlPanel } from "@web/search/control_panel/control_panel";
|
||||
|
||||
export class FormControlPanel extends ControlPanel {}
|
||||
FormControlPanel.template = "web.FormControlPanel";
|
||||
@@ -1,34 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="web.FormControlPanel" owl="1">
|
||||
<div class="o_control_panel" t-ref="root">
|
||||
<div t-if="display['top']" class="o_cp_top" t-att-class="{ 'flex-wrap': env.isSmall }">
|
||||
<div class="o_cp_top_left d-flex flex-grow-1 align-items-center" t-att-class="{ 'w-100': env.isSmall }">
|
||||
<t t-if="display['top-left']">
|
||||
<t t-slot="control-panel-breadcrumb">
|
||||
<t t-if="env.isSmall">
|
||||
<t t-call="web.Breadcrumbs.Small" t-if="!env.config.noBreadcrumbs"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<t t-call="web.Breadcrumbs" t-if="!env.config.noBreadcrumbs"/>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
</div>
|
||||
<div class="o_cp_bottom_right w-auto flex-shrink-0 justify-content-between align-items-center"
|
||||
t-att-class="{ 'flex-grow-1' : env.isSmall }">
|
||||
<t t-if="env.isSmall">
|
||||
<t t-slot="control-panel-status-indicator" />
|
||||
</t>
|
||||
<t t-slot="control-panel-action-menu" t-if="display['bottom-left']"/>
|
||||
<div t-if="pagerProps and pagerProps.total > 0" class="o_cp_pager" role="search">
|
||||
<Pager t-props="pagerProps"/>
|
||||
</div>
|
||||
<t t-slot="control-panel-create-button" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -9,7 +9,7 @@ import { SIZES } from "@web/core/ui/ui_service";
|
||||
import { useBus, useService } from "@web/core/utils/hooks";
|
||||
import { omit } from "@web/core/utils/objects";
|
||||
import { createElement } from "@web/core/utils/xml";
|
||||
import { ActionMenus } from "@web/search/action_menus/action_menus";
|
||||
import { CombinedActionMenus } from "@web/search/combined_action_menus/combined_action_menus";
|
||||
import { Layout } from "@web/search/layout";
|
||||
import { usePager } from "@web/search/pager_hook";
|
||||
import { useModel } from "@web/views/model";
|
||||
@@ -496,7 +496,7 @@ export class FormController extends Component {
|
||||
|
||||
FormController.template = `web.FormView`;
|
||||
FormController.components = {
|
||||
ActionMenus,
|
||||
CombinedActionMenus,
|
||||
FormStatusIndicator,
|
||||
Layout,
|
||||
ButtonBox,
|
||||
|
||||
@@ -5,17 +5,32 @@
|
||||
<div t-att-class="className" t-ref="root">
|
||||
<div class="o_form_view_container">
|
||||
<Layout className="model.useSampleModel ? 'o_view_sample_data' : ''" display="display">
|
||||
<t t-set-slot="layout-buttons">
|
||||
<t t-if="footerArchInfo and env.inDialog">
|
||||
<t t-component="props.Renderer" record="model.root" Compiler="props.Compiler" archInfo="footerArchInfo" enableViewButtons.bind="enableButtons" disableViewButtons.bind="disableButtons"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<t t-call="{{ props.buttonTemplate }}"/>
|
||||
<t t-set-slot="control-panel-create-button">
|
||||
<t t-if="canCreate">
|
||||
<button type="button" class="btn btn-secondary o_form_button_create" data-hotkey="c" t-on-click.stop="create" t-att-disabled="state.isDisabled">New</button>
|
||||
</t>
|
||||
</t>
|
||||
<t t-set-slot="control-panel-action-menu">
|
||||
|
||||
<t t-set-slot="layout-buttons">
|
||||
<t t-if="env.inDialog">
|
||||
<t t-if="footerArchInfo">
|
||||
<t t-component="props.Renderer" record="model.root" Compiler="props.Compiler" archInfo="footerArchInfo" enableViewButtons.bind="enableButtons" disableViewButtons.bind="disableButtons"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<t t-call="{{ props.buttonTemplate }}"/>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<t t-set-slot="layout-actions">
|
||||
<t t-if="buttonBoxTemplate">
|
||||
<t t-call="{{ buttonBoxTemplate }}" t-call-context="{ __comp__: Object.assign(Object.create(this), { this, props: { ...this.props, record: this.model.root } }) }"/>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<t t-set-slot="control-panel-additional-actions">
|
||||
<t t-if="props.info.actionMenus">
|
||||
<ActionMenus
|
||||
<CombinedActionMenus
|
||||
getActiveIds="() => model.root.isNew ? [] : [model.root.resId]"
|
||||
context="props.context"
|
||||
items="actionMenuItems"
|
||||
@@ -27,6 +42,7 @@
|
||||
/>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<t t-set-slot="control-panel-status-indicator">
|
||||
<t t-if="canEdit">
|
||||
<FormStatusIndicator model="model" discard.bind="discard" save.bind="saveButtonClicked" isDisabled="state.isDisabled" />
|
||||
@@ -37,7 +53,7 @@
|
||||
<button type="button" class="btn btn-outline-primary o_form_button_create" data-hotkey="c" t-on-click.stop="create" t-att-disabled="state.isDisabled">New</button>
|
||||
</t>
|
||||
</t>
|
||||
<t t-component="props.Renderer" record="model.root" Compiler="props.Compiler" archInfo="archInfo" enableViewButtons.bind="enableButtons" disableViewButtons.bind="disableButtons" onNotebookPageChange.bind="onNotebookPageChange" activeNotebookPages="props.state and props.state.activeNotebookPages"/>
|
||||
<t t-component="props.Renderer" record="model.root" Compiler="props.Compiler" archInfo="archInfo" translateAlert="translateAlert" enableViewButtons.bind="enableButtons" disableViewButtons.bind="disableButtons" onNotebookPageChange.bind="onNotebookPageChange" activeNotebookPages="props.state and props.state.activeNotebookPages"/>
|
||||
</Layout>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="web.FormStatusIndicator" owl="1">
|
||||
<div class="o_form_status_indicator d-md-flex align-items-center" t-att-class="{ o_form_status_indicator_new_record: props.model.root.isNew }">
|
||||
<div class="o_form_status_indicator d-md-flex align-items-center align-self-md-end me-auto" t-att-class="{ o_form_status_indicator_new_record: props.model.root.isNew }">
|
||||
<div class="o_form_status_indicator_buttons d-flex" t-att-class="{ invisible: !(props.model.root.isNew or displayButtons) }">
|
||||
<button
|
||||
type="button"
|
||||
class="o_form_button_save btn btn-light px-1 lh-sm"
|
||||
class="o_form_button_save btn btn-light px-1 py-0 lh-sm"
|
||||
t-att-disabled="props.isDisabled || !props.model.root.isNew and indicatorMode === 'invalid'"
|
||||
data-hotkey="s"
|
||||
t-on-click.stop="save"
|
||||
@@ -16,7 +16,7 @@
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="o_form_button_cancel btn btn-light px-1 lh-sm"
|
||||
class="o_form_button_cancel btn btn-light px-1 py-0 lh-sm"
|
||||
t-att-disabled="props.isDisabled"
|
||||
data-hotkey="j"
|
||||
t-on-click.stop="discard"
|
||||
|
||||
@@ -6,14 +6,12 @@ import { RelationalModel } from "../basic_relational_model";
|
||||
import { FormArchParser } from "./form_arch_parser";
|
||||
import { FormController } from "./form_controller";
|
||||
import { FormCompiler } from "./form_compiler";
|
||||
import { FormControlPanel } from "./control_panel/form_control_panel";
|
||||
|
||||
export const formView = {
|
||||
type: "form",
|
||||
display_name: "Form",
|
||||
multiRecord: false,
|
||||
searchMenuTypes: [],
|
||||
ControlPanel: FormControlPanel,
|
||||
Controller: FormController,
|
||||
Renderer: FormRenderer,
|
||||
ArchParser: FormArchParser,
|
||||
|
||||
@@ -4,9 +4,11 @@ import { Dropdown } from "@web/core/dropdown/dropdown";
|
||||
import { DropdownItem } from "@web/core/dropdown/dropdown_item";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { Layout } from "@web/search/layout";
|
||||
import { GroupByMenu } from "@web/search/group_by_menu/group_by_menu";
|
||||
import { useModel } from "@web/views/model";
|
||||
import { standardViewProps } from "@web/views/standard_view_props";
|
||||
import { useSetupView } from "@web/views/view_hook";
|
||||
import { SearchBar } from "@web/search/search_bar/search_bar";
|
||||
|
||||
import { Component, useRef } from "@odoo/owl";
|
||||
|
||||
@@ -127,7 +129,7 @@ export class GraphController extends Component {
|
||||
}
|
||||
|
||||
GraphController.template = "web.GraphView";
|
||||
GraphController.components = { Dropdown, DropdownItem, Layout };
|
||||
GraphController.components = { Dropdown, DropdownItem, Layout, GroupByMenu, SearchBar };
|
||||
|
||||
GraphController.props = {
|
||||
...standardViewProps,
|
||||
|
||||
@@ -9,41 +9,41 @@
|
||||
</t>
|
||||
</div>
|
||||
<div class="btn-group" role="toolbar" aria-label="Change graph">
|
||||
<button class="btn btn-light fa fa-bar-chart o_graph_button" data-tooltip="Bar Chart" aria-label="Bar Chart" data-mode="bar"
|
||||
<button class="btn btn-secondary fa fa-bar-chart o_graph_button" data-tooltip="Bar Chart" aria-label="Bar Chart" data-mode="bar"
|
||||
t-on-click="() => this.onModeSelected('bar')"
|
||||
t-att-class="{ active: model.metaData.mode === 'bar' }"
|
||||
/>
|
||||
<button class="btn btn-light fa fa-line-chart o_graph_button" data-tooltip="Line Chart" aria-label="Line Chart" data-mode="line"
|
||||
<button class="btn btn-secondary fa fa-line-chart o_graph_button" data-tooltip="Line Chart" aria-label="Line Chart" data-mode="line"
|
||||
t-on-click="() => this.onModeSelected('line')"
|
||||
t-att-class="{ active: model.metaData.mode === 'line' }"
|
||||
/>
|
||||
<button class="btn btn-light fa fa-pie-chart o_graph_button" data-tooltip="Pie Chart" aria-label="Pie Chart" data-mode="pie"
|
||||
<button class="btn btn-secondary fa fa-pie-chart o_graph_button" data-tooltip="Pie Chart" aria-label="Pie Chart" data-mode="pie"
|
||||
t-on-click="() => this.onModeSelected('pie')"
|
||||
t-att-class="{ active: model.metaData.mode === 'pie' }"
|
||||
/>
|
||||
</div>
|
||||
<div t-if="model.metaData.mode === 'bar'" class="btn-group" role="toolbar" aria-label="Change graph">
|
||||
<button class="btn btn-light fa fa-database o_graph_button" data-tooltip="Stacked" aria-label="Stacked"
|
||||
<button class="btn btn-secondary fa fa-database o_graph_button" data-tooltip="Stacked" aria-label="Stacked"
|
||||
t-on-click="toggleStacked"
|
||||
t-att-class="{ active: model.metaData.stacked }"
|
||||
/>
|
||||
</div>
|
||||
<div t-if="model.metaData.mode === 'line'" class="btn-group" role="toolbar" aria-label="Change graph">
|
||||
<button class="btn btn-light fa fa-database o_graph_button" data-tooltip="Stacked" aria-label="Stacked"
|
||||
<button class="btn btn-secondary fa fa-database o_graph_button" data-tooltip="Stacked" aria-label="Stacked"
|
||||
t-on-click="toggleStacked"
|
||||
t-att-class="{ active: model.metaData.stacked }"
|
||||
/>
|
||||
<button class="btn btn-light fa fa-signal o_graph_button" data-tooltip="Cumulative" aria-label="Cumulative"
|
||||
<button class="btn btn-secondary fa fa-signal o_graph_button" data-tooltip="Cumulative" aria-label="Cumulative"
|
||||
t-on-click="toggleCumulated"
|
||||
t-att-class="{ active: model.metaData.cumulated }"
|
||||
/>
|
||||
</div>
|
||||
<div t-if="model.metaData.mode !== 'pie' and model.metaData.domains.length === 1" class="btn-group" role="toolbar" aria-label="Sort graph" name="toggleOrderToolbar">
|
||||
<button class="btn btn-light fa fa-sort-amount-desc o_graph_button" data-tooltip="Descending" aria-label="Descending"
|
||||
<button class="btn btn-secondary fa fa-sort-amount-desc o_graph_button" data-tooltip="Descending" aria-label="Descending"
|
||||
t-on-click="() => this.toggleOrder('DESC')"
|
||||
t-att-class="{ active: model.metaData.order === 'DESC' }"
|
||||
/>
|
||||
<button class="btn btn-light fa fa-sort-amount-asc o_graph_button" data-tooltip="Ascending" aria-label="Ascending"
|
||||
<button class="btn btn-secondary fa fa-sort-amount-asc o_graph_button" data-tooltip="Ascending" aria-label="Ascending"
|
||||
t-on-click="() => this.toggleOrder('ASC')"
|
||||
t-att-class="{ active: model.metaData.order === 'ASC' }"
|
||||
/>
|
||||
@@ -56,6 +56,9 @@
|
||||
<t t-set-slot="layout-buttons">
|
||||
<t t-call="{{ props.buttonTemplate }}"/>
|
||||
</t>
|
||||
<t t-set-slot="layout-actions">
|
||||
<SearchBar/>
|
||||
</t>
|
||||
<t t-if="model.data">
|
||||
<t t-if="model.useSampleModel and props.info.noContentHelp" t-call="web.ActionHelper">
|
||||
<t t-set="noContentHelp" t-value="props.info.noContentHelp"/>
|
||||
|
||||
@@ -9,6 +9,7 @@ import { MultiRecordViewButton } from "@web/views/view_button/multi_record_view_
|
||||
import { useViewButtons } from "@web/views/view_button/view_button_hook";
|
||||
import { useSetupView } from "@web/views/view_hook";
|
||||
import { KanbanRenderer } from "./kanban_renderer";
|
||||
import { SearchBar } from "@web/search/search_bar/search_bar";
|
||||
|
||||
import { Component, reactive, useRef } from "@odoo/owl";
|
||||
|
||||
@@ -174,7 +175,7 @@ export class KanbanController extends Component {
|
||||
}
|
||||
|
||||
KanbanController.template = `web.KanbanView`;
|
||||
KanbanController.components = { Layout, KanbanRenderer, MultiRecordViewButton };
|
||||
KanbanController.components = { Layout, KanbanRenderer, MultiRecordViewButton, SearchBar };
|
||||
KanbanController.props = {
|
||||
...standardViewProps,
|
||||
defaultGroupBy: { validate: (dgb) => !dgb || typeof dgb === "string", optional: true },
|
||||
|
||||
@@ -4,9 +4,34 @@
|
||||
<t t-name="web.KanbanView" owl="1">
|
||||
<div t-att-class="className" t-ref="root">
|
||||
<Layout className="model.useSampleModel ? 'o_view_sample_data' : ''" display="props.display">
|
||||
<t t-set-slot="control-panel-create-button">
|
||||
<t t-if="canCreate">
|
||||
<button type="button" class="btn btn-primary o-kanban-button-new" accesskey="c" t-on-click="() => this.createRecord()" data-bounce-button="">
|
||||
New
|
||||
</button>
|
||||
</t>
|
||||
</t>
|
||||
<t t-set-slot="control-panel-always-buttons">
|
||||
<t t-foreach="headerButtons" t-as="button" t-key="button.id">
|
||||
<MultiRecordViewButton
|
||||
t-if="button.display === 'always'"
|
||||
list="model.root"
|
||||
className="button.className"
|
||||
clickParams="button.clickParams"
|
||||
defaultRank="'btn-secondary'"
|
||||
domain="props.domain"
|
||||
icon="button.icon"
|
||||
string="button.string"
|
||||
title="button.title"
|
||||
/>
|
||||
</t>
|
||||
</t>
|
||||
<t t-set-slot="layout-buttons">
|
||||
<t t-call="{{ props.buttonTemplate }}"/>
|
||||
</t>
|
||||
<t t-set-slot="layout-actions">
|
||||
<SearchBar/>
|
||||
</t>
|
||||
<t t-component="props.Renderer"
|
||||
list="model.root"
|
||||
archInfo="props.archInfo"
|
||||
@@ -19,32 +44,12 @@
|
||||
canQuickCreate="canQuickCreate"
|
||||
quickCreateState="quickCreateState"
|
||||
/>
|
||||
|
||||
</Layout>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="web.KanbanView.Buttons" owl="1">
|
||||
<div t-if="props.showButtons" class="o_cp_buttons d-flex align-items-baseline" role="toolbar" aria-label="Main actions">
|
||||
<t t-if="canCreate">
|
||||
<button type="button" class="btn btn-primary o-kanban-button-new" accesskey="c" t-on-click="() => this.createRecord()" data-bounce-button="">
|
||||
New
|
||||
</button>
|
||||
</t>
|
||||
<t t-foreach="headerButtons" t-as="button" t-key="button.id">
|
||||
<MultiRecordViewButton
|
||||
t-if="button.display === 'always'"
|
||||
list="model.root"
|
||||
className="button.className"
|
||||
clickParams="button.clickParams"
|
||||
defaultRank="'btn-secondary'"
|
||||
domain="props.domain"
|
||||
icon="button.icon"
|
||||
string="button.string"
|
||||
title="button.title"
|
||||
/>
|
||||
</t>
|
||||
</div>
|
||||
<div t-if="props.showButtons" class="o_cp_buttons d-flex align-items-baseline" role="toolbar" aria-label="Main actions"/>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<div class="o_kanban_header_title position-relative d-flex lh-lg" t-on-mouseenter="onTitleMouseEnter" t-on-mouseleave="onTitleMouseLeave">
|
||||
<div t-if="!env.isSmall and group.isFolded" class="o_column_title d-flex align-items-center pt-1 fs-4 lh-1 text-nowrap opacity-50 opacity-100-hover">
|
||||
<t t-esc="groupName"></t>
|
||||
<span class="badge text-bg-500 rounded-pill ms-2" t-esc="group.count"></span>
|
||||
<span class="badge text-bg-500 rounded-pill lh-1 ms-2" t-esc="group.count"></span>
|
||||
</div>
|
||||
<span t-if="!env.isSmall and !group.isFolded"
|
||||
t-esc="groupName"
|
||||
|
||||
@@ -20,6 +20,9 @@ import { useViewButtons } from "@web/views/view_button/view_button_hook";
|
||||
import { ExportDataDialog } from "@web/views/view_dialogs/export_data_dialog";
|
||||
import { useSetupView } from "@web/views/view_hook";
|
||||
import { ListConfirmationDialog } from "./list_confirmation_dialog";
|
||||
import { SearchBar } from "@web/search/search_bar/search_bar";
|
||||
import { Dropdown } from "@web/core/dropdown/dropdown";
|
||||
import { DropdownItem } from "@web/core/dropdown/dropdown_item";
|
||||
|
||||
import { Component, onMounted, onWillStart, useEffect, useRef, useSubEnv } from "@odoo/owl";
|
||||
|
||||
@@ -371,15 +374,12 @@ export class ListController extends Component {
|
||||
}
|
||||
|
||||
get display() {
|
||||
if (!this.env.isSmall) {
|
||||
return this.props.display;
|
||||
}
|
||||
const { controlPanel } = this.props.display;
|
||||
return {
|
||||
...this.props.display,
|
||||
controlPanel: {
|
||||
...controlPanel,
|
||||
"bottom-right": !this.nbSelected,
|
||||
layoutActions: !this.nbSelected,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -589,7 +589,7 @@ export class ListController extends Component {
|
||||
}
|
||||
|
||||
ListController.template = `web.ListView`;
|
||||
ListController.components = { ActionMenus, Layout, ViewButton, MultiRecordViewButton };
|
||||
ListController.components = { ActionMenus, Layout, ViewButton, MultiRecordViewButton, SearchBar, Dropdown, DropdownItem };
|
||||
ListController.props = {
|
||||
...standardViewProps,
|
||||
allowSelectors: { type: Boolean, optional: true },
|
||||
|
||||
@@ -4,26 +4,76 @@
|
||||
<t t-name="web.ListView" owl="1">
|
||||
<div t-att-class="className" t-ref="root">
|
||||
<Layout className="model.useSampleModel ? 'o_view_sample_data' : ''" display="display">
|
||||
<t t-set-slot="layout-buttons">
|
||||
<t t-if="env.isSmall and nbSelected">
|
||||
<t t-call="web.ListView.Selection" />
|
||||
</t>
|
||||
<t t-else="">
|
||||
<div class="o_cp_buttons" role="toolbar" aria-label="Control panel buttons" t-ref="buttons">
|
||||
<t t-call="{{ props.buttonTemplate }}"/>
|
||||
</div>
|
||||
<t t-set-slot="control-panel-create-button">
|
||||
<t t-if="!model.root.editedRecord and activeActions.create and (!model.root.isGrouped or !editable) and !env.inDialog">
|
||||
<button type="button" class="btn btn-primary o_list_button_add" data-hotkey="c" t-on-click="onClickCreate" data-bounce-button="">
|
||||
New
|
||||
</button>
|
||||
</t>
|
||||
</t>
|
||||
<t t-set-slot="control-panel-bottom-left">
|
||||
<t t-if="props.info.actionMenus and model.root.selection.length">
|
||||
<ActionMenus
|
||||
getActiveIds="() => model.root.selection.map((r) => r.resId)"
|
||||
context="props.context"
|
||||
|
||||
<t t-set-slot="control-panel-always-buttons">
|
||||
<t t-foreach="archInfo.headerButtons" t-as="button" t-key="button.id">
|
||||
<MultiRecordViewButton
|
||||
t-if="button.display === 'always'"
|
||||
list="model.root"
|
||||
className="button.className"
|
||||
clickParams="button.clickParams"
|
||||
defaultRank="'btn-secondary'"
|
||||
domain="props.domain"
|
||||
items="actionMenuItems"
|
||||
isDomainSelected="model.root.isDomainSelected"
|
||||
resModel="model.root.resModel"
|
||||
onActionExecuted="() => model.load()"/>
|
||||
icon="button.icon"
|
||||
string="button.string"
|
||||
title="button.title"
|
||||
/>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<t t-set-slot="layout-buttons">
|
||||
<div class="o_cp_buttons" role="toolbar" aria-label="Control panel buttons" t-ref="buttons">
|
||||
<t t-call="{{ props.buttonTemplate }}"/>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-set-slot="layout-actions">
|
||||
<SearchBar/>
|
||||
</t>
|
||||
|
||||
<t t-set-slot="control-panel-additional-actions">
|
||||
<div t-if="nbSelected" class="d-flex ms-3">
|
||||
<t t-call="web.ListView.Selection"/>
|
||||
<t t-foreach="archInfo.headerButtons" t-as="button" t-key="button.id">
|
||||
<MultiRecordViewButton
|
||||
t-if="button.display !== 'always'"
|
||||
list="model.root"
|
||||
className="button.className"
|
||||
clickParams="button.clickParams"
|
||||
defaultRank="'btn-secondary'"
|
||||
domain="props.domain"
|
||||
icon="button.icon"
|
||||
string="button.string"
|
||||
title="button.title"
|
||||
/>
|
||||
</t>
|
||||
<t t-if="props.info.actionMenus">
|
||||
<ActionMenus
|
||||
getActiveIds="() => model.root.selection.map((r) => r.resId)"
|
||||
context="props.context"
|
||||
domain="props.domain"
|
||||
items="actionMenuItems"
|
||||
isDomainSelected="model.root.isDomainSelected"
|
||||
resModel="model.root.resModel"
|
||||
onActionExecuted="() => model.load()"/>
|
||||
</t>
|
||||
</div>
|
||||
<t t-else="">
|
||||
<Dropdown togglerClass="'d-print-none btn btn-text ms-1 lh-sm'">
|
||||
<t t-set-slot="toggler">
|
||||
<i class="fa fa-cog"/>
|
||||
</t>
|
||||
<DropdownItem t-if="nbTotal and !nbSelected and activeActions.exportXlsx and isExportEnable" onSelected.bind="onDirectExportData">
|
||||
Export All
|
||||
</DropdownItem>
|
||||
</Dropdown>
|
||||
</t>
|
||||
</t>
|
||||
<t t-component="props.Renderer" list="model.root" activeActions="activeActions" archInfo="archInfo" allowSelectors="props.allowSelectors" editable="editable" openRecord.bind="openRecord" noContentHelp="props.info.noContentHelp" onAdd.bind="createRecord"/>
|
||||
@@ -32,56 +82,32 @@
|
||||
</t>
|
||||
|
||||
<t t-name="web.ListView.Buttons" owl="1">
|
||||
<div class="o_list_buttons d-flex flex-wrap" role="toolbar" aria-label="Main actions">
|
||||
<t t-if="props.showButtons">
|
||||
<t t-if="model.root.editedRecord">
|
||||
<button type="button" class="btn btn-primary o_list_button_save" data-hotkey="s" t-on-click.stop="onClickSave">
|
||||
Save
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary o_list_button_discard" data-hotkey="j" t-on-click="onClickDiscard" t-on-mousedown="onMouseDownDiscard">
|
||||
Discard
|
||||
</button>
|
||||
</t>
|
||||
<t t-elif="activeActions.create and (!model.root.isGrouped or !editable)">
|
||||
<button type="button" class="btn btn-primary o_list_button_add" data-hotkey="c" t-on-click="onClickCreate" data-bounce-button="">
|
||||
New
|
||||
</button>
|
||||
</t>
|
||||
<t t-foreach="archInfo.headerButtons" t-as="button" t-key="button.id">
|
||||
<MultiRecordViewButton
|
||||
t-if="nbSelected || (button.display === 'always')"
|
||||
list="model.root"
|
||||
className="button.className"
|
||||
clickParams="button.clickParams"
|
||||
defaultRank="'btn-secondary'"
|
||||
domain="props.domain"
|
||||
icon="button.icon"
|
||||
string="button.string"
|
||||
title="button.title"
|
||||
/>
|
||||
</t>
|
||||
<t t-if="nbTotal and !nbSelected and activeActions.exportXlsx and isExportEnable and !env.isSmall">
|
||||
<button type="button" class="btn btn-secondary fa fa-download o_list_export_xlsx" data-tooltip="Export All" aria-label="Export All" t-on-click="onDirectExportData"/>
|
||||
</t>
|
||||
</t>
|
||||
<t t-if="!env.isSmall and nbSelected">
|
||||
<t t-call="web.ListView.Selection"/>
|
||||
<div t-if="props.showButtons" class="o_list_buttons d-flex d-empty-none align-items-baseline" role="toolbar" aria-label="Main actions">
|
||||
<t t-if="model.root.editedRecord">
|
||||
<button type="button" class="btn btn-primary o_list_button_save" data-hotkey="s" t-on-click.stop="onClickSave">
|
||||
Save
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary o_list_button_discard" data-hotkey="j" t-on-click="onClickDiscard" t-on-mousedown="onMouseDownDiscard">
|
||||
Discard
|
||||
</button>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="web.ListView.Selection" owl="1">
|
||||
<div class="o_list_selection_box alert alert-info d-inline-flex align-items-center p-1" role="alert">
|
||||
<span t-if="isDomainSelected" class="p-1">All <t t-esc="nbTotal"/> selected</span>
|
||||
<t t-else="">
|
||||
<span class="me-1" t-esc="nbSelected"/> selected
|
||||
<a t-if="isPageSelected and nbTotal > nbSelected" href="#" class="o_list_select_domain ms-2 btn btn-sm btn-info p-1 border-0 fw-normal" t-on-click="onSelectDomain">
|
||||
<i class="fa fa-fw fa-arrow-right"/> Select all <span t-esc="nbTotal"/>
|
||||
<div class="o_list_selection_box list-group flex-row me-2" role="alert">
|
||||
<span class="list-group-item active d-flex align-items-center alisgn-self-stretch pe-0 py-0 rounded-1 lh-1">
|
||||
<span t-if="isDomainSelected">All <b t-esc="nbTotal"/> selected</span>
|
||||
<t t-else="">
|
||||
<b class="me-1" t-esc="nbSelected"/> selected
|
||||
<a t-if="isPageSelected and nbTotal > nbSelected" href="#" class="o_list_select_domain ms-3 btn btn-sm btn-info p-1 me-n2 border-0 fw-normal" t-on-click="onSelectDomain">
|
||||
<i class="fa fa-fw fa-arrow-right"/> Select all <span t-esc="nbTotal"/>
|
||||
</a>
|
||||
</t>
|
||||
<a href="#" title="Unselect All" class="o_list_unselect_all btn btn-link py-0 ms-2" t-on-click="onUnselectAll">
|
||||
<i class="oi oi-close oi-large"/>
|
||||
</a>
|
||||
</t>
|
||||
<a t-if="nbSelected > 1" href="#" class="o_list_unselect_all ms-2 btn btn-sm btn-info p-1 border-0 fw-normal" t-att-class="{'btn-link': env.isSmall}" t-on-click="onUnselectAll">
|
||||
<i class="fa fa-fw fa-times"/><t t-if="!env.isSmall"> Unselect all</t>
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import { Layout } from "@web/search/layout";
|
||||
import { useModel } from "@web/views/model";
|
||||
import { standardViewProps } from "@web/views/standard_view_props";
|
||||
import { useSetupView } from "@web/views/view_hook";
|
||||
import { SearchBar } from "@web/search/search_bar/search_bar";
|
||||
|
||||
import { Component, useRef } from "@odoo/owl";
|
||||
|
||||
@@ -131,7 +132,7 @@ export class PivotController extends Component {
|
||||
}
|
||||
|
||||
PivotController.template = "web.PivotView";
|
||||
PivotController.components = { Dropdown, DropdownItem, Layout };
|
||||
PivotController.components = { Dropdown, DropdownItem, Layout, SearchBar };
|
||||
|
||||
PivotController.props = {
|
||||
...standardViewProps,
|
||||
|
||||
@@ -22,6 +22,9 @@
|
||||
<t t-set-slot="layout-buttons">
|
||||
<t t-call="{{ props.buttonTemplate }}"/>
|
||||
</t>
|
||||
<t t-set-slot="layout-actions">
|
||||
<SearchBar/>
|
||||
</t>
|
||||
<t t-set="displayNoContent" t-value="
|
||||
props.info.noContentHelp !== false and (
|
||||
!(model.hasData() and model.metaData.activeMeasures.length) or
|
||||
|
||||
@@ -109,7 +109,7 @@
|
||||
<div class="o_right_field_panel h-100 px-2 overflow-auto border">
|
||||
<ul class="o_fields_list list-unstyled" t-ref="draggable">
|
||||
<t t-foreach="state.exportList" t-as="field" t-key="field.id">
|
||||
<li t-attf-class="o_export_field {{ state.isSmall ? '' : 'o_export_field_sortable' }}" t-att-data-field_id="field.id">
|
||||
<li t-attf-class="o_export_field d-inline-block w-100 {{ state.isSmall ? '' : 'o_export_field_sortable' }}" t-att-data-field_id="field.id">
|
||||
<span t-if="!state.isSmall" class="fa fa-sort o_sort_field mx-1" t-attf-style="opacity:{{ state.exportList.length === 1 ? 0 : 1 }}" />
|
||||
<span t-esc="isDebug and field.id ? `${field.string} (${field.id})` : field.string" />
|
||||
<span class="fa fa-trash m-1 pe-2 float-end o_remove_field cursor-pointer" t-att-title="removeFieldText" t-on-click.stop="() => this.onRemoveItemExportList(field.id)" />
|
||||
|
||||
@@ -15,6 +15,7 @@ export class SettingsFormController extends formView.Controller {
|
||||
this.state = useState({ displayNoContent: false });
|
||||
this.searchState = useState({ value: "" });
|
||||
this.rootRef = useRef("root");
|
||||
this.canCreate = false;
|
||||
useSubEnv({ searchState: this.searchState });
|
||||
useEffect(
|
||||
() => {
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
<xpath expr="./div[@t-ref='root']" position="attributes">
|
||||
<attribute name="class">o-settings-form-view o_field_highlight</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//Layout" position="inside">
|
||||
<t t-set-slot="control-panel-top-right">
|
||||
<xpath expr="//Layout/t[@t-set-slot='layout-actions']" position="replace">
|
||||
<t t-set-slot="layout-actions">
|
||||
<div class="o_cp_searchview d-flex flex-grow-1" role="search">
|
||||
<div class="o_searchview pb-1 align-self-center border-bottom flex-grow-1" role="search" aria-autocomplete="list">
|
||||
<i class="o_searchview_icon oi oi-search" role="img" aria-label="Search..." title="Search..." />
|
||||
@@ -28,6 +28,11 @@
|
||||
</t>
|
||||
</t>
|
||||
</xpath>
|
||||
<xpath expr="//Layout/t[@t-set-slot='layout-buttons']" position="replace">
|
||||
<t t-set-slot="layout-buttons">
|
||||
<t t-call="{{ props.buttonTemplate }}"/>
|
||||
</t>
|
||||
</xpath>
|
||||
</t>
|
||||
|
||||
<t t-name="web.SettingsFormView.Buttons" t-inherit="web.FormView.Buttons" t-inherit-mode="primary" owl="1">
|
||||
|
||||
@@ -23,16 +23,16 @@
|
||||
</xpath>
|
||||
</t>
|
||||
|
||||
<t t-name="website.ControlPanel.Regular" t-inherit="web.ControlPanel.Regular" t-inherit-mode="extension" owl="1">
|
||||
<!-- <t t-name="website.ControlPanel.Regular" t-inherit="web.ControlPanel.Regular" t-inherit-mode="extension" owl="1">
|
||||
<xpath expr="//div[hasclass('o_search_options')]/*[last()]" position="after">
|
||||
<t t-slot="control-panel-website-extra-actions"/>
|
||||
</xpath>
|
||||
</t>
|
||||
</t> -->
|
||||
|
||||
<t t-name="website.ControlPanel" t-inherit="web.ControlPanel" t-inherit-mode="primary" owl="1">
|
||||
<xpath expr="//t[@t-call='web.ControlPanel.Regular']" position="replace">
|
||||
<!-- <xpath expr="//t[@t-call='web.ControlPanel.Regular']" position="replace">
|
||||
<t t-call="website.ControlPanel.Regular"/>
|
||||
</xpath>
|
||||
</xpath> -->
|
||||
</t>
|
||||
|
||||
<t t-name="website.RecordFilter" owl="1">
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-name="website.ThemePreviewForm.ControlPanel.Regular" t-inherit="web.ControlPanel.Regular" t-inherit-mode="primary" owl="1">
|
||||
<!-- <t t-name="website.ThemePreviewForm.ControlPanel.Regular" t-inherit="web.ControlPanel.Regular" t-inherit-mode="primary" owl="1">
|
||||
<xpath expr="//div[hasclass('o_cp_bottom_right')]" position="replace">
|
||||
<div class="o_cp_bottom_right" role="group">
|
||||
<input class="btn-check" id="themeViewerDesktop" type="radio" name="viewer" data-mode='desktop' autocomplete="off" checked='checked'/>
|
||||
@@ -25,15 +25,15 @@
|
||||
<label class="btn btn-secondary" for="themeViewerMobile" t-on-click="this.onMobileClick">Mobile</label>
|
||||
</div>
|
||||
</xpath>
|
||||
</t>
|
||||
</t> -->
|
||||
|
||||
<t t-name="website.ThemePreviewForm.ControlPanel" t-inherit="web.ControlPanel" t-inherit-mode="primary" owl="1">
|
||||
<xpath expr="//t[@t-call='web.ControlPanel.Regular']" position="replace">
|
||||
<!-- <xpath expr="//t[@t-call='web.ControlPanel.Regular']" position="replace">
|
||||
<t t-call="website.ThemePreviewForm.ControlPanel.Regular"/>
|
||||
</xpath>
|
||||
</xpath> -->
|
||||
</t>
|
||||
|
||||
<t t-name="website.ThemePreviewKanban.ControlPanel.Regular" t-inherit="web.ControlPanel.Regular" t-inherit-mode="primary" owl="1">
|
||||
<!-- <t t-name="website.ThemePreviewKanban.ControlPanel.Regular" t-inherit="web.ControlPanel.Regular" t-inherit-mode="primary" owl="1">
|
||||
<xpath expr="//div[hasclass('o_cp_top_left')]" position="replace">
|
||||
<div class="o_cp_top_left">
|
||||
<ol class="breadcrumb" role="navigation">
|
||||
@@ -51,12 +51,12 @@
|
||||
</a>
|
||||
</div>
|
||||
</xpath>
|
||||
</t>
|
||||
</t> -->
|
||||
|
||||
<t t-name="website.ThemePreviewKanban.ControlPanel" t-inherit="web.ControlPanel" t-inherit-mode="primary" owl="1">
|
||||
<xpath expr="//t[@t-call='web.ControlPanel.Regular']" position="replace">
|
||||
<!-- <xpath expr="//t[@t-call='web.ControlPanel.Regular']" position="replace">
|
||||
<t t-call="website.ThemePreviewKanban.ControlPanel.Regular"/>
|
||||
</xpath>
|
||||
</xpath> -->
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
||||
@@ -93,10 +93,7 @@ ThemePreviewFormControlPanel.template = 'website.ThemePreviewForm.ControlPanel';
|
||||
const ThemePreviewFormView = {
|
||||
...formView,
|
||||
display: {
|
||||
controlPanel: {
|
||||
'top-right': false,
|
||||
'bottom-right': true,
|
||||
}
|
||||
controlPanel: {}
|
||||
},
|
||||
buttonTemplate: 'website.ThemePreview.Buttons',
|
||||
Controller: ThemePreviewFormController,
|
||||
|
||||
@@ -33,10 +33,7 @@ const ThemePreviewKanbanView = {
|
||||
Controller: ThemePreviewKanbanController,
|
||||
ControlPanel: ThemePreviewControlPanel,
|
||||
display: {
|
||||
controlPanel: {
|
||||
'bottom-left': false,
|
||||
'bottom-right': false,
|
||||
},
|
||||
controlPanel: {},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="website.EditInBackendSystray" owl="1">
|
||||
<div class="o_website_edit_in_backend">
|
||||
<a href="#" accesskey="e" t-on-click="editInBackend">
|
||||
<div class="o_website_edit_in_backend d-flex">
|
||||
<a href="#" class="d-flex align-items-center px-2" accesskey="e" t-on-click="editInBackend">
|
||||
<span class="fa fa-cog"/>
|
||||
<span class= "d-none d-md-block ms-2" t-esc="state.mainObjectName"/>
|
||||
</a>
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
<button class="btn btn-secondary js_date_range" data-date="month">Last Month</button>
|
||||
<button class="btn btn-secondary js_date_range" data-date="year">Last Year</button>
|
||||
</div>
|
||||
<div class="btn-group d-none d-md-inline-block float-end" style="margin-right: 20px;">
|
||||
<div class="btn-group d-none d-md-inline-block float-end">
|
||||
<t t-foreach="widget.websites" t-as="website">
|
||||
<button t-attf-class="btn btn-secondary js_website #{website.selected ? 'active' : ''}"
|
||||
t-att-data-website-id="website.id">
|
||||
|
||||
Reference in New Issue
Block a user