[REF] web,mail: formView design
- ButtonBox is moved to the ControlPanel's actions section (center). - StatusBarField, ButtonBox and PercentPieField design. - Chatter is moved below the ControlPanel (aka "in the view") task-2818586 Part-of: odoo/odoo#116641 Co-authored-by: Pierre Paridans <app@odoo.com> Co-authored-by: Pierre Rousseau <pro@odoo.com> Co-authored-by: luvi <luvi@odoo.com>
This commit is contained in:
committed by
Pierre Paridans
co-authored by
Pierre Paridans
Pierre Rousseau
luvi
parent
c964329add
commit
824024f8aa
@@ -3,7 +3,7 @@
|
||||
|
||||
<t t-name="mail.Thread" owl="1">
|
||||
<t t-if="env.inChatter" t-call="mail.Thread.jumpPresent"/> <!-- chatter has its own scrollable, this ensures proper sticky showing -->
|
||||
<div class="o-mail-Thread flex-grow-1 bg-view" t-att-class="{ 'overflow-auto': props.hasScrollAdjust, 'pb-4': !state.showJumpPresent }" t-ref="messages">
|
||||
<div class="o-mail-Thread flex-grow-1" t-att-class="{ 'overflow-auto': props.hasScrollAdjust, 'pb-4': !state.showJumpPresent }" t-ref="messages">
|
||||
<t t-if="!props.thread.isEmpty or props.thread.loadOlder or props.thread.hasLoadingFailed" name="content">
|
||||
<div class="d-flex flex-column position-relative" t-att-class="{'justify-content-end': !env.inChatter and props.thread.type !== 'mailbox'}" style="min-height:100%">
|
||||
<span class="position-absolute w-100 invisible" t-att-class="props.order === 'asc' ? 'bottom-0' : 'top-0'" t-ref="present-treshold" t-att-style="`height: Min(${PRESENT_THRESHOLD}px, 100%)`"/>
|
||||
|
||||
@@ -5,7 +5,6 @@ import { registry } from "@web/core/registry";
|
||||
import { SIZES } from "@web/core/ui/ui_service";
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
import { append, createElement, setAttributes } from "@web/core/utils/xml";
|
||||
import { ViewCompiler, getModifier } from "@web/views/view_compiler";
|
||||
import { FormCompiler } from "@web/views/form/form_compiler";
|
||||
|
||||
function compileChatter(node, params) {
|
||||
@@ -37,8 +36,9 @@ function compileChatter(node, params) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
const chatterContainerXml = createElement("Chatter");
|
||||
const chatterContainerXml = createElement("t");
|
||||
setAttributes(chatterContainerXml, {
|
||||
"t-component": "__comp__.mailComponents.Chatter",
|
||||
hasActivities,
|
||||
hasFollowers,
|
||||
hasMessageList,
|
||||
@@ -46,9 +46,9 @@ function compileChatter(node, params) {
|
||||
hasParentReloadOnFollowersUpdate,
|
||||
hasParentReloadOnMessagePosted,
|
||||
isAttachmentBoxVisibleInitially,
|
||||
threadId: params.threadId,
|
||||
threadModel: params.threadModel,
|
||||
webRecord: params.webRecord,
|
||||
threadId: "__comp__.props.record.model.root.resId or undefined",
|
||||
threadModel: "__comp__.props.record.model.root.resModel",
|
||||
webRecord: "__comp__.props.record.model.root",
|
||||
saveRecord: "() => __comp__.saveButtonClicked and __comp__.saveButtonClicked()",
|
||||
});
|
||||
const chatterContainerHookXml = createElement("div");
|
||||
@@ -60,92 +60,24 @@ function compileChatter(node, params) {
|
||||
function compileAttachmentPreview(node, params) {
|
||||
const webClientViewAttachmentViewContainerHookXml = createElement("div");
|
||||
webClientViewAttachmentViewContainerHookXml.classList.add("o_attachment_preview");
|
||||
const webClientViewAttachmentViewContainerXml = createElement("AttachmentView");
|
||||
const webClientViewAttachmentViewContainerXml = createElement("t");
|
||||
setAttributes(webClientViewAttachmentViewContainerXml, {
|
||||
threadId: params.threadId,
|
||||
threadModel: params.threadModel,
|
||||
"t-component": "__comp__.mailComponents.AttachmentView",
|
||||
threadId: "__comp__.props.record.model.root.resId or undefined",
|
||||
threadModel: "__comp__.props.record.model.root.resModel",
|
||||
});
|
||||
append(webClientViewAttachmentViewContainerHookXml, webClientViewAttachmentViewContainerXml);
|
||||
return webClientViewAttachmentViewContainerHookXml;
|
||||
}
|
||||
|
||||
export class MailFormCompiler extends ViewCompiler {
|
||||
setup() {
|
||||
this.compilers.push({ selector: "t", fn: this.compileT });
|
||||
this.compilers.push({ selector: "div.oe_chatter", fn: this.compileChatter });
|
||||
this.compilers.push({
|
||||
selector: "div.o_attachment_preview",
|
||||
fn: this.compileAttachmentPreview,
|
||||
});
|
||||
}
|
||||
|
||||
compile(node, params) {
|
||||
const res = super.compile(node, params).children[0];
|
||||
const chatterContainerHookXml = res.querySelector(".o-mail-Form-chatter");
|
||||
if (chatterContainerHookXml) {
|
||||
setAttributes(chatterContainerHookXml, {
|
||||
"t-if": `!__comp__.hasAttachmentViewer() and __comp__.uiService.size >= ${SIZES.XXL}`,
|
||||
"t-attf-class": "o-aside",
|
||||
});
|
||||
const chatterContainerXml = chatterContainerHookXml.querySelector("Chatter");
|
||||
setAttributes(chatterContainerXml, {
|
||||
hasMessageListScrollAdjust: "true",
|
||||
isInFormSheetBg: "false",
|
||||
});
|
||||
}
|
||||
const attachmentViewHookXml = res.querySelector(".o_attachment_preview");
|
||||
if (attachmentViewHookXml) {
|
||||
setAttributes(attachmentViewHookXml, {
|
||||
"t-if": `__comp__.hasAttachmentViewer()`,
|
||||
});
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
compileT(node, params) {
|
||||
const compiledRoot = createElement("t");
|
||||
for (const child of node.childNodes) {
|
||||
const invisible = getModifier(child, "invisible");
|
||||
let compiledChild = this.compileNode(child, params, false);
|
||||
compiledChild = this.applyInvisible(invisible, compiledChild, {
|
||||
...params,
|
||||
recordExpr: "__comp__.model.root",
|
||||
});
|
||||
append(compiledRoot, compiledChild);
|
||||
}
|
||||
return compiledRoot;
|
||||
}
|
||||
|
||||
compileChatter(node) {
|
||||
return compileChatter(node, {
|
||||
chatter: "__comp__.chatter",
|
||||
threadId: "__comp__.model.root.resId or undefined",
|
||||
threadModel: "__comp__.model.root.resModel",
|
||||
webRecord: "__comp__.model.root",
|
||||
});
|
||||
}
|
||||
|
||||
compileAttachmentPreview(node) {
|
||||
return compileAttachmentPreview(node, {
|
||||
threadId: "__comp__.model.root.resId or undefined",
|
||||
threadModel: "__comp__.model.root.resModel",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
registry.category("form_compilers").add("chatter_compiler", {
|
||||
selector: "div.oe_chatter",
|
||||
fn: (node) =>
|
||||
compileChatter(node, {
|
||||
threadId: "__comp__.props.record.resId or undefined",
|
||||
threadModel: "__comp__.props.record.resModel",
|
||||
webRecord: "__comp__.props.record",
|
||||
}),
|
||||
fn: compileChatter,
|
||||
});
|
||||
|
||||
registry.category("form_compilers").add("attachment_preview_compiler", {
|
||||
selector: "div.o_attachment_preview",
|
||||
fn: () => createElement("t"),
|
||||
fn: compileAttachmentPreview,
|
||||
});
|
||||
|
||||
patch(FormCompiler.prototype, "mail", {
|
||||
@@ -156,7 +88,9 @@ patch(FormCompiler.prototype, "mail", {
|
||||
if (!chatterContainerHookXml) {
|
||||
return res; // no chatter, keep the result as it is
|
||||
}
|
||||
const chatterContainerXml = chatterContainerHookXml.querySelector("Chatter");
|
||||
const chatterContainerXml = chatterContainerHookXml.querySelector(
|
||||
"t[t-component='__comp__.mailComponents.Chatter']"
|
||||
);
|
||||
setAttributes(chatterContainerXml, {
|
||||
hasMessageListScrollAdjust: "false",
|
||||
isInFormSheetBg: "false",
|
||||
@@ -170,24 +104,46 @@ patch(FormCompiler.prototype, "mail", {
|
||||
if (!parentXml) {
|
||||
return res; // miss-config: a sheet-bg is required for the rest
|
||||
}
|
||||
if (params.hasAttachmentViewerInArch) {
|
||||
|
||||
const webClientViewAttachmentViewHookXml = res.querySelector(".o_attachment_preview");
|
||||
if (webClientViewAttachmentViewHookXml) {
|
||||
// in sheet bg (attachment viewer present)
|
||||
setAttributes(webClientViewAttachmentViewHookXml, {
|
||||
"t-if": `__comp__.hasAttachmentViewer() and __comp__.uiService.size >= ${SIZES.XXL}`,
|
||||
});
|
||||
const sheetBgChatterContainerHookXml = chatterContainerHookXml.cloneNode(true);
|
||||
sheetBgChatterContainerHookXml.classList.add("o-isInFormSheetBg");
|
||||
setAttributes(sheetBgChatterContainerHookXml, {
|
||||
"t-if": `__comp__.props.hasAttachmentViewer`,
|
||||
"t-if": `__comp__.hasAttachmentViewer() and __comp__.uiService.size >= ${SIZES.XXL}`,
|
||||
});
|
||||
append(formSheetBgXml, sheetBgChatterContainerHookXml);
|
||||
const sheetBgChatterContainerXml =
|
||||
sheetBgChatterContainerHookXml.querySelector("Chatter");
|
||||
const sheetBgChatterContainerXml = sheetBgChatterContainerHookXml.querySelector(
|
||||
"t[t-component='__comp__.mailComponents.Chatter']"
|
||||
);
|
||||
setAttributes(sheetBgChatterContainerXml, {
|
||||
isInFormSheetBg: "true",
|
||||
hasMessageListScrollAdjust: "false",
|
||||
});
|
||||
}
|
||||
// after sheet bg (standard position, either aside or below)
|
||||
if (webClientViewAttachmentViewHookXml) {
|
||||
setAttributes(chatterContainerHookXml, {
|
||||
"t-if": `!(__comp__.hasAttachmentViewer() and __comp__.uiService.size >= ${SIZES.XXL})`,
|
||||
"t-attf-class": `{{ __comp__.uiService.size >= ${SIZES.XXL} and !(__comp__.hasAttachmentViewer() and __comp__.uiService.size >= ${SIZES.XXL}) ? "o-aside" : "" }}`,
|
||||
});
|
||||
setAttributes(chatterContainerXml, {
|
||||
isInFormSheetBg: "__comp__.hasAttachmentViewer()",
|
||||
hasMessageListScrollAdjust: `__comp__.uiService.size >= ${SIZES.XXL} and !(__comp__.hasAttachmentViewer() and __comp__.uiService.size >= ${SIZES.XXL})`,
|
||||
});
|
||||
} else {
|
||||
setAttributes(chatterContainerXml, {
|
||||
isInFormSheetBg: "false",
|
||||
hasMessageListScrollAdjust: `__comp__.uiService.size >= ${SIZES.XXL}`,
|
||||
});
|
||||
setAttributes(chatterContainerHookXml, {
|
||||
"t-attf-class": `{{ __comp__.uiService.size >= ${SIZES.XXL} ? "o-aside" : "mt-4 mt-md-0" }}`,
|
||||
});
|
||||
}
|
||||
// after sheet bg (standard position, below form)
|
||||
setAttributes(chatterContainerHookXml, {
|
||||
"t-if": `!__comp__.props.hasAttachmentViewer and __comp__.uiService.size < ${SIZES.XXL}`,
|
||||
});
|
||||
append(parentXml, chatterContainerHookXml);
|
||||
return res;
|
||||
},
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { AttachmentView } from "@mail/attachments/attachment_view";
|
||||
import { Chatter } from "@mail/web/chatter";
|
||||
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { createElement } from "@web/core/utils/xml";
|
||||
import { SIZES } from "@web/core/ui/ui_service";
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
import { useDebounced } from "@web/core/utils/timing";
|
||||
import { FormController } from "@web/views/form/form_controller";
|
||||
import { useViewCompiler } from "@web/views/view_compiler";
|
||||
import { evalDomain } from "@web/views/utils";
|
||||
|
||||
import { MailFormCompiler } from "./form_compiler";
|
||||
|
||||
import { onMounted, onWillUnmount, useState } from "@odoo/owl";
|
||||
|
||||
patch(FormController.prototype, "mail", {
|
||||
setup() {
|
||||
this._super();
|
||||
this.messagingState = useState({
|
||||
/** @type {import("@mail/core/thread_model").Thread} */
|
||||
thread: undefined,
|
||||
});
|
||||
if (this.env.services["mail.thread"]) {
|
||||
this.threadService = useService("mail.thread");
|
||||
}
|
||||
this.uiService = useService("ui");
|
||||
this.hasAttachmentViewerInArch = false;
|
||||
|
||||
const { archInfo } = this.props;
|
||||
|
||||
const template = createElement("t");
|
||||
const xmlDocAttachmentPreview = archInfo.xmlDoc.querySelector("div.o_attachment_preview");
|
||||
if (xmlDocAttachmentPreview && xmlDocAttachmentPreview.parentNode.nodeName === "form") {
|
||||
// TODO hasAttachmentViewer should also depend on the groups= and/or invisible modifier on o_attachment_preview (see invoice form)
|
||||
template.appendChild(xmlDocAttachmentPreview);
|
||||
this.hasAttachmentViewerInArch = true;
|
||||
archInfo.arch = archInfo.xmlDoc.outerHTML;
|
||||
}
|
||||
|
||||
const xmlDocChatter = archInfo.xmlDoc.querySelector("div.oe_chatter");
|
||||
if (xmlDocChatter && xmlDocChatter.parentNode.nodeName === "form") {
|
||||
template.appendChild(xmlDocChatter.cloneNode(true));
|
||||
}
|
||||
|
||||
const mailTemplates = useViewCompiler(
|
||||
MailFormCompiler,
|
||||
archInfo.arch,
|
||||
{ Mail: template },
|
||||
{}
|
||||
);
|
||||
this.mailTemplate = mailTemplates.Mail;
|
||||
|
||||
this.onResize = useDebounced(this.render, 200);
|
||||
onMounted(() => browser.addEventListener("resize", this.onResize));
|
||||
onWillUnmount(() => browser.removeEventListener("resize", this.onResize));
|
||||
},
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasAttachmentViewer() {
|
||||
if (
|
||||
!this.threadService ||
|
||||
this.uiService.size < SIZES.XXL ||
|
||||
!this.hasAttachmentViewerInArch ||
|
||||
!this.model.root.resId
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
this.messagingState.thread = this.threadService.insert({
|
||||
id: this.model.root.resId,
|
||||
model: this.model.root.resModel,
|
||||
type: "chatter",
|
||||
});
|
||||
return this.messagingState.thread.attachmentsInWebClientView.length > 0;
|
||||
},
|
||||
evalDomainFromRecord(record, expr) {
|
||||
return evalDomain(expr, record.evalContext);
|
||||
},
|
||||
});
|
||||
|
||||
Object.assign(FormController.components, {
|
||||
AttachmentView,
|
||||
Chatter,
|
||||
});
|
||||
@@ -2,14 +2,7 @@
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-inherit="web.FormView" t-inherit-mode="extension">
|
||||
<xpath expr="//div[hasclass('o_form_view_container')]" position="after">
|
||||
<t t-if="mailTemplate">
|
||||
<t t-call="{{ mailTemplate }}" t-call-context="{ __comp__: Object.assign(Object.create(this), { this: this }) }"/>
|
||||
</t>
|
||||
</xpath>
|
||||
<xpath expr="//Layout/t[@t-component='props.Renderer']" position="attributes">
|
||||
<attribute name="hasAttachmentViewerInArch">hasAttachmentViewerInArch</attribute>
|
||||
<attribute name="hasAttachmentViewer">hasAttachmentViewer()</attribute>
|
||||
<attribute name="saveButtonClicked">() => this.saveButtonClicked()</attribute>
|
||||
</xpath>
|
||||
</t>
|
||||
|
||||
@@ -2,29 +2,51 @@
|
||||
|
||||
import { Chatter } from "@mail/web/chatter";
|
||||
import { AttachmentView } from "@mail/attachments/attachment_view";
|
||||
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
import { FormRenderer } from "@web/views/form/form_renderer";
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { SIZES } from "@web/core/ui/ui_service";
|
||||
import { useDebounced } from "@web/core/utils/timing";
|
||||
import { onMounted, onWillUnmount, useState } from "@odoo/owl";
|
||||
|
||||
patch(FormRenderer.prototype, "mail", {
|
||||
get compileParams() {
|
||||
return {
|
||||
...this._super(),
|
||||
hasAttachmentViewerInArch: this.props.hasAttachmentViewerInArch,
|
||||
saveButtonClicked: this.props.saveButtonClicked,
|
||||
setup() {
|
||||
this._super();
|
||||
this.mailComponents = {
|
||||
AttachmentView,
|
||||
Chatter,
|
||||
};
|
||||
this.messagingState = useState({
|
||||
/** @type {import("@mail/core/thread_model").Thread} */
|
||||
thread: undefined,
|
||||
});
|
||||
if (this.env.services["mail.thread"]) {
|
||||
this.threadService = useService("mail.thread");
|
||||
}
|
||||
this.uiService = useService("ui");
|
||||
|
||||
this.onResize = useDebounced(this.render, 200);
|
||||
onMounted(() => browser.addEventListener("resize", this.onResize));
|
||||
onWillUnmount(() => browser.removeEventListener("resize", this.onResize));
|
||||
},
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasAttachmentViewer() {
|
||||
if (!this.threadService || this.uiService.size < SIZES.XXL || !this.props.record.resId) {
|
||||
return false;
|
||||
}
|
||||
this.messagingState.thread = this.threadService.insert({
|
||||
id: this.props.record.resId,
|
||||
model: this.props.record.resModel,
|
||||
type: "chatter",
|
||||
});
|
||||
return this.messagingState.thread.attachmentsInWebClientView.length > 0;
|
||||
},
|
||||
});
|
||||
|
||||
patch(FormRenderer.props, "mail", {
|
||||
hasAttachmentViewerInArch: { type: Boolean, optional: true },
|
||||
// Template props : added by the FormCompiler
|
||||
hasAttachmentViewer: { type: Boolean, optional: true },
|
||||
chatter: { type: Object, optional: true },
|
||||
saveButtonClicked: { type: Function, optional: true },
|
||||
});
|
||||
|
||||
Object.assign(FormRenderer.components, {
|
||||
AttachmentView,
|
||||
Chatter,
|
||||
});
|
||||
|
||||
@@ -4,20 +4,14 @@
|
||||
|
||||
.o-mail-Form-chatter {
|
||||
--ChatterTopbar-padding-v: #{map-get($spacers, 2)};
|
||||
|
||||
@include media-breakpoint-down(md) {
|
||||
--ChatterTopbar-padding-left: #{$o-horizontal-padding};
|
||||
--ChatterTopbar-padding-right: #{$o-horizontal-padding};
|
||||
}
|
||||
--ChatterTopbar-padding-left: #{$o-horizontal-padding};
|
||||
--ChatterTopbar-padding-right: #{$o-horizontal-padding};
|
||||
|
||||
&.o-aside {
|
||||
--Chatter-max-width: #{$o-form-view-sheet-max-width};
|
||||
--ChatterTopbar-padding-left: #{$o-horizontal-padding};
|
||||
--ChatterTopbar-padding-right: #{$o-horizontal-padding};
|
||||
|
||||
flex-shrink: 0;
|
||||
flex-grow: 1;
|
||||
border-left: $border-width solid;
|
||||
padding: map-get($spacers, 0);
|
||||
max-width: var(--Chatter-max-width);
|
||||
width: $o-mail-Chatter-minWidth;
|
||||
@@ -33,13 +27,10 @@
|
||||
|
||||
&.o-isInFormSheetBg {
|
||||
background-color: $white;
|
||||
width: auto;
|
||||
border-top: 1px solid $border-color;
|
||||
}
|
||||
|
||||
&:not(.o-aside):not(.o-full-width) .o-mail-Chatter {
|
||||
@include make-container-max-widths();
|
||||
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
@@ -49,11 +40,6 @@
|
||||
// Style
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
.o-mail-Form-chatter.o-aside {
|
||||
border-left-color: $border-color;
|
||||
}
|
||||
|
||||
|
||||
.o-mail-Form-chatter {
|
||||
display: flex;
|
||||
margin: 0 auto;
|
||||
|
||||
@@ -2,14 +2,6 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
.o-mail-Chatter-topbar {
|
||||
padding-inline-start: 0;
|
||||
}
|
||||
|
||||
.o-mail-Form-chatter.o-aside .o-mail-Chatter-topbar {
|
||||
padding-inline-start: $o-horizontal-padding;
|
||||
}
|
||||
|
||||
.o-chatter-disabled .o-mail-Message-actions {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -2,27 +2,27 @@
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="mail.Chatter" owl="1">
|
||||
<div class="o-mail-Chatter bg-view h-100 flex-grow-1 d-flex flex-column" t-att-class="{ 'overflow-auto': props.hasMessageListScrollAdjust, 'o-chatter-disabled': props.threadId === false }" t-ref="root">
|
||||
<div class="o-mail-Chatter-topbar d-flex flex-shrink-0 flex-grow-0">
|
||||
<button t-if="props.hasMessageList" class="o-mail-Chatter-sendMessage btn text-nowrap me-2" t-att-class="{ 'btn-odoo': state.thread.composer.type !== 'note', 'btn-light': state.thread.composer.type === 'note', 'o-active': state.thread.composer.type === 'message', 'my-2': !props.compactHeight }" t-att-disabled="!state.thread.hasWriteAccess and !(state.thread.hasReadAccess and state.thread.canPostOnReadonly) and props.threadId" data-hotkey="m" t-on-click="() => this.toggleComposer('message')">
|
||||
<div class="o-mail-Chatter w-100 h-100 flex-grow-1 d-flex flex-column" t-att-class="{ 'overflow-auto': props.hasMessageListScrollAdjust, 'o-chatter-disabled': props.threadId === false }" t-ref="root">
|
||||
<div class="o-mail-Chatter-topbar d-flex flex-shrink-0 flex-grow-0 px-3 overflow-x-auto">
|
||||
<button t-if="props.hasMessageList" class="o-mail-Chatter-sendMessage btn text-nowrap me-1" t-att-class="{ 'btn-primary': state.thread.composer.type !== 'note', 'btn-secondary': state.thread.composer.type === 'note', 'o-active': state.thread.composer.type === 'message', 'my-2': !props.compactHeight }" t-att-disabled="!state.thread.hasWriteAccess and !(state.thread.hasReadAccess and state.thread.canPostOnReadonly) and props.threadId" data-hotkey="m" t-on-click="() => this.toggleComposer('message')">
|
||||
Send message
|
||||
</button>
|
||||
<button t-if="props.hasMessageList" class="o-mail-Chatter-logNote btn text-nowrap me-2" t-att-class="{ 'btn-odoo o-active': state.thread.composer.type === 'note', 'btn-light': state.thread.composer.type !== 'note', 'my-2': !props.compactHeight }" data-hotkey="shift+m" t-on-click="() => this.toggleComposer('note')">
|
||||
<button t-if="props.hasMessageList" class="o-mail-Chatter-logNote btn text-nowrap me-2" t-att-class="{ 'btn-primary o-active': state.thread.composer.type === 'note', 'btn-secondary': state.thread.composer.type !== 'note', 'my-2': !props.compactHeight }" data-hotkey="shift+m" t-on-click="() => this.toggleComposer('note')">
|
||||
Log note
|
||||
</button>
|
||||
<div class="border-bottom flex-grow-1 d-flex" t-att-class="{ 'border-start ps-2 pe-4': props.hasActivities }">
|
||||
<button t-if="props.hasActivities" class="btn btn-light text-nowrap" t-att-class="{ 'my-2': !props.compactHeight }" data-hotkey="shift+a" t-on-click="scheduleActivity">
|
||||
<div class="flex-grow-1 d-flex">
|
||||
<button t-if="props.hasActivities" class="btn btn-secondary text-nowrap" t-att-class="{ 'my-2': !props.compactHeight }" data-hotkey="shift+a" t-on-click="scheduleActivity">
|
||||
<i class="fa fa-clock-o me-1"/>
|
||||
<span>Activities</span>
|
||||
</button>
|
||||
<span class="o-mail-Chatter-topbarGrow flex-grow-1 border-start pe-2" t-att-class="{ 'ms-2': props.hasActivities }"/>
|
||||
<span class="o-mail-Chatter-topbarGrow flex-grow-1 pe-2"/>
|
||||
<FileUploader t-if="attachments.length === 0" fileUploadClass="'o-mail-Chatter-fileUploader'" multiUpload="true" onUploaded.bind="onUploaded" onClick="(ev) => this.onClickAttachFile(ev)">
|
||||
<t t-set-slot="toggler">
|
||||
<t t-call="mail.Chatter.attachFiles"/>
|
||||
</t>
|
||||
</FileUploader>
|
||||
<t t-else="" t-call="mail.Chatter.attachFiles"/>
|
||||
<Dropdown t-if="props.hasFollowers" position="'bottom-end'" disabled="isDisabled" class="'o-mail-Followers d-flex'" menuClass="'o-mail-Followers-dropdown flex-column p-0'" menuDisplay="'d-flex'" title="followerButtonLabel" togglerClass="'o-mail-Followers-button btn btn-light px-2 ' + (props.compactHeight ? '' : 'my-2')">
|
||||
<Dropdown t-if="props.hasFollowers" position="'bottom-end'" disabled="isDisabled" class="'o-mail-Followers d-flex me-1'" menuClass="'o-mail-Followers-dropdown flex-column p-0'" menuDisplay="'d-flex'" title="followerButtonLabel" togglerClass="'o-mail-Followers-button btn btn-link text-action px-1 ' + (props.compactHeight ? '' : 'my-2')">
|
||||
<t t-set-slot="toggler">
|
||||
<i class="fa fa-user-o" role="img"/>
|
||||
<span class="o-mail-Followers-counter ps-1" t-esc="state.thread.followers.length"/>
|
||||
@@ -58,7 +58,7 @@
|
||||
<button t-if="props.hasFollowers and state.thread.followerOfSelf" class="btn px-0" t-att-class="{ 'text-success': !unfollowHover.isHover, 'text-warning': unfollowHover.isHover, 'my-2': !props.compactHeight }" t-att-disabled="!props.threadId" t-on-click="onClickUnfollow" t-ref="unfollow">
|
||||
<div class="position-relative">
|
||||
<!-- Hidden element used to set the button maximum size -->
|
||||
<span class="invisible">
|
||||
<span class="d-flex invisible text-nowrap">
|
||||
<i class="me-1 fa fa-fw fa-eye-slash"/><t t-out="followingText.length > unfollowText.length ? followingText : unfollowText"/>
|
||||
</span>
|
||||
<span class="position-absolute end-0 top-0" t-att-class="{ 'text-warning': unfollowHover.isHover }">
|
||||
@@ -66,7 +66,16 @@
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
<button t-if="!state.thread.followerOfSelf" class="o-mail-Chatter-follow btn btn-link text-600" t-on-click="onClickFollow">Follow</button>
|
||||
<button t-if="!state.thread.followerOfSelf" class="o-mail-Chatter-follow btn btn-link px-0 text-600" t-on-click="onClickFollow">
|
||||
<div class="position-relative">
|
||||
<span class="d-flex invisible text-nowrap">
|
||||
<i class="me-1 fa fa-fw fa-eye-slash"/><t t-out="followingText.length > unfollowText.length ? followingText : unfollowText"/>
|
||||
</span>
|
||||
<span class="position-absolute end-0 top-0">
|
||||
Follow
|
||||
</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<button t-if="props.close" class="o-mail-Chatter-close btn btn-dark flex-shrink-0 rounded-bottom-3" aria-label="Close" t-on-click="props.close">
|
||||
<i class="oi oi-large oi-close"/>
|
||||
@@ -136,7 +145,7 @@
|
||||
</t>
|
||||
|
||||
<t t-name="mail.Chatter.attachFiles" owl="1">
|
||||
<button class="btn btn-light" aria-label="Attach files" t-att-class="{ 'my-2': !props.compactHeight }" t-on-click="onClickAddAttachments">
|
||||
<button class="btn btn-link text-action px-1" aria-label="Attach files" t-att-class="{ 'my-2': !props.compactHeight }" t-on-click="onClickAddAttachments">
|
||||
<i class="fa fa-paperclip fa-lg me-1"/>
|
||||
<span t-if="attachments.length > 0" t-esc="attachments.length"/>
|
||||
<i t-if="!state.thread.areAttachmentsLoaded and state.thread.isLoadingAttachments" class="fa fa-circle-o-notch fa-spin" aria-label="Attachment counter loading..."/>
|
||||
|
||||
@@ -12,11 +12,7 @@
|
||||
margin: 0;
|
||||
}
|
||||
.o_form_sheet {
|
||||
margin: 0;
|
||||
border: none;
|
||||
flex: 1;
|
||||
box-shadow: none;
|
||||
padding: 0;
|
||||
max-width: 100%;
|
||||
.oe_pad {
|
||||
flex: 1;
|
||||
|
||||
@@ -402,46 +402,6 @@
|
||||
// Percent pie
|
||||
&.o_field_percent_pie {
|
||||
align-items: center;
|
||||
|
||||
> .o_pie {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-right: 10px;
|
||||
border-radius: 100%;
|
||||
overflow: hidden;
|
||||
background-color: $o-brand-primary;
|
||||
|
||||
&:after { // Outside pie border to go over border-radius irregularities
|
||||
content: " ";
|
||||
@include o-position-absolute(0, 0);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
.o_mask {
|
||||
@include o-position-absolute(0, 0);
|
||||
transform-origin: 0;
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
background-color: $o-view-background-color;
|
||||
&.o_full {
|
||||
background-color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.o_pie_value {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
border-radius: 100%;
|
||||
|
||||
font-weight: 700;
|
||||
color: $o-brand-odoo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ace editor
|
||||
|
||||
@@ -26,7 +26,6 @@ $o-colors-complete: join(
|
||||
$o-sheet-cancel-hpadding: $o-horizontal-padding !default;
|
||||
$o-sheet-cancel-bpadding: $o-horizontal-padding !default;
|
||||
|
||||
$o-statusbar-arrow-width: $o-statusbar-height / 3 !default;
|
||||
$o-statusbar-disabled-bg: lighten($o-brand-lightsecondary, 7%) !default;
|
||||
|
||||
$o-datepicker-week-bg-color: lighten($o-datepicker-week-color, 30%) !default;
|
||||
|
||||
@@ -217,7 +217,6 @@
|
||||
.o_cp_buttons {
|
||||
display: flex;
|
||||
margin-right: auto;
|
||||
margin-left: $o-kanban-record-margin;
|
||||
}
|
||||
.o_cp_pager {
|
||||
display: flex;
|
||||
|
||||
@@ -12,15 +12,6 @@ export class PercentPieField extends Component {
|
||||
...standardFieldProps,
|
||||
string: { type: String, optional: true },
|
||||
};
|
||||
|
||||
get transform() {
|
||||
const rotateDeg = (360 * this.props.record.data[this.props.name]) / 100;
|
||||
return {
|
||||
left: rotateDeg < 180 ? 180 : rotateDeg,
|
||||
right: rotateDeg < 180 ? rotateDeg : 0,
|
||||
value: rotateDeg,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const percentPieField = {
|
||||
|
||||
@@ -1,19 +1,10 @@
|
||||
.o_field_percent_pie .o_pie {
|
||||
$pie-dimension: $o-statbutton-height - 2 * $o-statbutton-vpadding;
|
||||
$pie-ring-width: 4px;
|
||||
.o_field_percent_pie {
|
||||
.o_pie {
|
||||
--PercentPieField-color-active: #{$o-action};
|
||||
--PercentPieField-color-static: #{rgba(#000, .15)};
|
||||
|
||||
width: $pie-dimension;
|
||||
height: $pie-dimension;
|
||||
|
||||
&:after { // Outside pie border to go over border-radius irregularities
|
||||
border: 1px solid $o-brand-secondary;
|
||||
}
|
||||
|
||||
.o_pie_value {
|
||||
@include o-position-absolute($pie-ring-width, $pie-ring-width);
|
||||
width: $pie-dimension - 2 * $pie-ring-width;
|
||||
height: $pie-dimension - 2 * $pie-ring-width;
|
||||
border: 1px solid $o-brand-secondary;
|
||||
background-color: $o-brand-lightsecondary;
|
||||
height: var(--PercentPieField-size, #{$font-size-lg});
|
||||
aspect-ratio: 1;
|
||||
margin-right: var(--PercentPieField-gap, #{map-get($spacers, 2) });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,17 @@
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="web.PercentPieField" owl="1">
|
||||
<div class="o_pie">
|
||||
<div class="o_mask" t-attf-class="{{transform.value >= 360 ? 'o_full' : ''}}" t-attf-style="transform: rotate({{transform.left}}deg)"/>
|
||||
<div class="o_mask" t-attf-class="{{transform.value >= 180 ? 'o_full' : ''}}" t-attf-style="transform: rotate({{transform.right}}deg)"/>
|
||||
<div class="o_pie_value">
|
||||
<span t-esc="props.record.data[props.name] + '%'"/>
|
||||
</div>
|
||||
<figure class="o_pie mb-0 rounded-circle"
|
||||
t-attf-style="background: conic-gradient(
|
||||
var(--PercentPieField-color-active) 0% {{props.record.data[props.name]}}%,
|
||||
var(--PercentPieField-color-static) 0% 100%
|
||||
)"
|
||||
/>
|
||||
|
||||
<div class="o_pie_info">
|
||||
<span class="o_pie_value" t-esc="props.record.data[props.name] + '%'"/>
|
||||
<span class="o_pie_text" t-esc="props.string"/>
|
||||
</div>
|
||||
<span t-esc="props.string"/>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
|
||||
@@ -130,11 +130,10 @@ export class StatusBarField extends Component {
|
||||
}
|
||||
|
||||
getDropdownItemClassNames(item) {
|
||||
const classNames = [
|
||||
"btn",
|
||||
item.isSelected ? "btn-primary" : "btn-secondary",
|
||||
"o_arrow_button",
|
||||
];
|
||||
const classNames = [];
|
||||
if (item.isSelected) {
|
||||
classNames.push("active");
|
||||
}
|
||||
if (item.isSelected || this.props.isDisabled) {
|
||||
classNames.push("disabled");
|
||||
}
|
||||
|
||||
@@ -10,37 +10,42 @@
|
||||
align-items: stretch;
|
||||
|
||||
> .o_arrow_button {
|
||||
min-height: $o-statusbar-height;
|
||||
position: relative;
|
||||
padding-left: $o-statusbar-arrow-width*2;
|
||||
border-width: 0;
|
||||
padding-left: $o-statusbar-arrow-width * 1.5;
|
||||
border-radius: 0;
|
||||
font-size: $font-size-sm;
|
||||
|
||||
&:first-child {
|
||||
@include border-end-radius($border-radius);
|
||||
padding-right: $o-horizontal-padding; // Compensate container padding
|
||||
overflow-x: hidden; // to prevent horizontal scroll due to last arrow
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding-left: $o-horizontal-padding - 1;
|
||||
border-left: $border-width solid $border-color;
|
||||
@include border-start-radius($border-radius);
|
||||
padding-left: $o-horizontal-padding;
|
||||
}
|
||||
|
||||
&:not(:first-child):before, &:not(:first-child):after {
|
||||
content: " ";
|
||||
display: block;
|
||||
@include o-position-absolute(0, -$o-statusbar-arrow-width + 1);
|
||||
&:not(:first-child) {
|
||||
&:before, &:after {
|
||||
@include o-position-absolute(-$border-width, -$o-statusbar-arrow-width);
|
||||
display: block;
|
||||
|
||||
border-top: floor($o-statusbar-height/2) solid transparent;
|
||||
border-bottom: ceil($o-statusbar-height/2) solid transparent;
|
||||
border-right: none;
|
||||
border-left: $o-statusbar-arrow-width solid $o-view-background-color;
|
||||
-moz-transform: scale(0.9999); // Smooth the triangle on firefox
|
||||
}
|
||||
border-top: floor($o-statusbar-height/2) solid transparent;
|
||||
border-bottom: ceil($o-statusbar-height/2) solid transparent;
|
||||
border-right: none;
|
||||
border-left: $o-statusbar-arrow-width solid map-get($-btn-secondary-design, background);
|
||||
-moz-transform: scale(0.9999); // Smooth the triangle on firefox
|
||||
content: " ";
|
||||
}
|
||||
|
||||
&:not(:first-child):before {
|
||||
right: -$o-statusbar-arrow-width;
|
||||
border-left-color: $border-color!important;
|
||||
&:before {
|
||||
right: calc(-#{$o-statusbar-arrow-width} - #{$border-width});
|
||||
border-left-color: $o-view-background-color;
|
||||
}
|
||||
|
||||
&:hover:after {
|
||||
border-left-color: map-get($-btn-secondary-design, hover-background);
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
@@ -54,12 +59,19 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.o_arrow_button_current.disabled {
|
||||
background-color: $o-brand-odoo;
|
||||
color: color-contrast($o-brand-odoo);
|
||||
&.o_arrow_button_current.disabled, &:active:not(:first-child) {
|
||||
background-color: map-get($-btn-secondary-design, active-background);
|
||||
border-color: map-get($-btn-secondary-design, active-border);
|
||||
color: map-get($-btn-secondary-design, active-color);
|
||||
|
||||
&:after, &:before {
|
||||
border-left-color: $o-brand-odoo;
|
||||
&:after {
|
||||
border-left-color: map-get($-btn-secondary-design, active-background);
|
||||
}
|
||||
|
||||
&, & + .btn {
|
||||
&:before {
|
||||
border-left-color: map-get($-btn-secondary-design, active-border);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
$o-statusbar-arrow-width: 1em;
|
||||
@@ -6,7 +6,7 @@
|
||||
<t t-set="items" t-value="computeItems()" />
|
||||
|
||||
<t t-if="items.folded.length">
|
||||
<button type="button" class="btn dropdown-toggle text-uppercase" t-att-class="{ 'o_arrow_button': !env.isSmall, 'btn-primary': env.isSmall }">
|
||||
<button type="button" class="btn btn-secondary dropdown-toggle" t-att-class="{ 'o_arrow_button': !env.isSmall, 'btn-primary': env.isSmall }">
|
||||
<Dropdown toggler="'parent'">
|
||||
<t t-foreach="items.folded" t-as="item" t-key="item.id">
|
||||
<DropdownItem class="getDropdownItemClassNames(item)" onSelected="() => this.selectItem(item)">
|
||||
@@ -25,7 +25,7 @@
|
||||
<t t-if="item.isSelected">
|
||||
<button
|
||||
type="button"
|
||||
class="btn o_arrow_button_current o_arrow_button disabled text-uppercase"
|
||||
class="btn btn-secondary o_arrow_button_current o_arrow_button disabled"
|
||||
disabled="disabled"
|
||||
role="radio"
|
||||
aria-label="Current state"
|
||||
@@ -39,7 +39,7 @@
|
||||
<t t-elif="props.isDisabled">
|
||||
<button
|
||||
type="button"
|
||||
class="btn o_arrow_button disabled text-uppercase"
|
||||
class="btn btn-secondary o_arrow_button disabled"
|
||||
disabled="disabled"
|
||||
role="radio"
|
||||
aria-label="Not active state"
|
||||
@@ -52,7 +52,7 @@
|
||||
<t t-else="">
|
||||
<button
|
||||
type="button"
|
||||
class="btn o_arrow_button text-uppercase"
|
||||
class="btn btn-secondary o_arrow_button"
|
||||
role="radio"
|
||||
aria-label="Not active state, click to change it"
|
||||
aria-checked="false"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
<t t-name="web.X2ManyField" owl="1">
|
||||
<div t-att-class="className">
|
||||
<div class="o_x2m_control_panel">
|
||||
<div class="o_x2m_control_panel d-empty-none mb-2">
|
||||
<t t-if="displayControlPanelButtons">
|
||||
<div class="o_cp_buttons" role="toolbar" aria-label="Control panel buttons" t-ref="buttons">
|
||||
<t t-foreach="creates" t-as="create" t-key="create_index">
|
||||
@@ -28,8 +28,8 @@
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
<div class="o_cp_pager" role="search">
|
||||
<Pager t-if="props.record.data[props.name].count > props.record.data[props.name].limit" t-props="pagerProps"/>
|
||||
<div t-if="props.record.data[props.name].count > props.record.data[props.name].limit" class="o_cp_pager" role="search">
|
||||
<Pager t-props="pagerProps"/>
|
||||
</div>
|
||||
</div>
|
||||
<ListRenderer t-if="props.viewMode === 'list'" t-props="rendererProps" />
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Component } from "@odoo/owl";
|
||||
export class ButtonBox extends Component {
|
||||
setup() {
|
||||
const ui = useService("ui");
|
||||
this.getMaxButtons = () => [2, 2, 2, 4][ui.size] || 7;
|
||||
this.getMaxButtons = () => [3, 3, 3, 7, 3, 4, 7][ui.size] || 7;
|
||||
}
|
||||
|
||||
getButtons() {
|
||||
|
||||
@@ -1,77 +1,74 @@
|
||||
.o-form-buttonbox {
|
||||
--o-input-border-color: #{map-get($grays, '600')};
|
||||
margin-bottom: $o-sheet-vpadding;
|
||||
margin-top: -$o-sheet-vpadding;
|
||||
@include o-form-sheet-negative-margin;
|
||||
// Use box-shadow instead of border-bottom because some button boxes are
|
||||
// empty in some cases and we do not want to see a floating border in
|
||||
// that cases.
|
||||
box-shadow: inset 0 -1px 0 $border-color;
|
||||
// Compute the expected oe_stat_button's height according to the default btn size.
|
||||
$-stat-button-inner-size: ($btn-padding-y * 2) + ($btn-font-size * $btn-line-height);
|
||||
$-stat-button-height: $-stat-button-inner-size + o-to-rem($btn-border-width * 2);
|
||||
|
||||
.btn.oe_stat_button, > .o_field_widget {
|
||||
flex: 0 0 auto;
|
||||
width: percentage(1/3); // Adapt the number of visible buttons for each screen width
|
||||
@include media-breakpoint-up(md) {
|
||||
width: percentage(1/5);
|
||||
}
|
||||
@include media-breakpoint-up(lg) {
|
||||
width: percentage(1/7);
|
||||
}
|
||||
@include media-breakpoint-up(xl) {
|
||||
width: percentage(1/8);
|
||||
}
|
||||
$-stat-button-icon-size: 1.5em;
|
||||
$-stat-button-icon-margin-end: $btn-padding-x * 0.5;
|
||||
|
||||
%-stat-button-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 100%;
|
||||
text-align: left;
|
||||
font-weight: $font-weight-normal;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
&.o_full > *:first-child {
|
||||
border-left: none !important;
|
||||
box-shadow: -1px 0 0 $border-color;
|
||||
%-stat-button-text {
|
||||
@include o-text-overflow(block);
|
||||
order: 1;
|
||||
font-size: $o-font-size-base-smaller;
|
||||
line-height: $line-height-sm;
|
||||
}
|
||||
|
||||
.btn.oe_stat_button {
|
||||
height: $o-statbutton-height;
|
||||
|
||||
> .o_button_icon {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
line-height: $o-statbutton-height;
|
||||
width: 30%;
|
||||
%-stat-button-value {
|
||||
order: 2;
|
||||
font-weight: $font-weight-bold;
|
||||
color: $o-brand-primary;
|
||||
font-size: $o-font-size-base-smaller;
|
||||
}
|
||||
|
||||
%-stat-button-figure-offset {
|
||||
margin-left: $btn-padding-x * -0.33;
|
||||
}
|
||||
|
||||
.oe_stat_button {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
flex-grow: 1;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
height: $-stat-button-height;
|
||||
|
||||
.o_button_icon {
|
||||
font-size: $-stat-button-icon-size;
|
||||
color: $primary;
|
||||
|
||||
&:before {
|
||||
font-size: 22px;
|
||||
vertical-align: middle;
|
||||
margin-right: $-stat-button-icon-margin-end;
|
||||
}
|
||||
}
|
||||
|
||||
> .o_field_percent_pie {
|
||||
margin-left: $o-statbutton-spacing; // To create the button padding left (firefox bug)
|
||||
}
|
||||
|
||||
// Some buttons only display text without using StatInfo template
|
||||
> span {
|
||||
@include o-text-overflow(block);
|
||||
white-space: normal; // text on several lines if needed
|
||||
}
|
||||
|
||||
> .o_stat_info, > span, > .o_field_statinfo { // contains the value and text
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
font-weight: $font-weight-normal;
|
||||
.o_stat_info, > span, .o_field_statinfo {
|
||||
@extend %-stat-button-container;
|
||||
|
||||
max-width: 70%;
|
||||
padding-right: $o-statbutton-spacing;
|
||||
line-height: 1.3;
|
||||
|
||||
> .o_stat_value, > .o_stat_text {
|
||||
@include o-text-overflow(block);
|
||||
line-height: 1.2;
|
||||
.o_stat_text {
|
||||
@extend %-stat-button-text;
|
||||
}
|
||||
|
||||
.o_stat_value {
|
||||
font-weight: $font-weight-bold;
|
||||
color: map-get($o-theme-text-colors, 'odoo');
|
||||
@extend %-stat-button-value;
|
||||
}
|
||||
|
||||
.o_stat_text .o_field_empty {
|
||||
.o_field_empty {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -82,6 +79,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
&:not(:first-child) .o_button_icon {
|
||||
@extend %-stat-button-figure-offset;
|
||||
}
|
||||
|
||||
&:not(:hover) .o_stat_info > .o_hover {
|
||||
display: none !important;
|
||||
}
|
||||
@@ -89,11 +90,21 @@
|
||||
display: none !important
|
||||
}
|
||||
}
|
||||
.oe_stat_button {
|
||||
border-left: 1px solid $border-color;
|
||||
|
||||
> * + * {
|
||||
margin-left: -$border-width;
|
||||
}
|
||||
&.o_full > .oe_stat_button:first-child {
|
||||
border-left: none;
|
||||
|
||||
.oe_stat_button, > div > button {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.oe_stat_button:first-child, div:first-child > button {
|
||||
@include border-start-radius($border-radius);
|
||||
}
|
||||
|
||||
.oe_stat_button:last-child, div:last-child > button {
|
||||
@include border-end-radius($border-radius);
|
||||
}
|
||||
|
||||
// "More" button and dropdown
|
||||
@@ -101,25 +112,43 @@
|
||||
> .o_button_more {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
&:after {
|
||||
margin-left: 5px;
|
||||
@include o-caret-down;
|
||||
}
|
||||
}
|
||||
&.show > .o_button_more {
|
||||
&:after {
|
||||
@include o-caret-up;
|
||||
}
|
||||
}
|
||||
|
||||
.o_dropdown_more {
|
||||
width: 150px;
|
||||
> .dropdown-item {
|
||||
.oe_stat_button {
|
||||
border: none;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.oe_stat_button {
|
||||
height: auto;
|
||||
width: 100%;
|
||||
border: none;
|
||||
padding-top: map-get($spacers, 2);
|
||||
padding-bottom: map-get($spacers, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Piechart
|
||||
--PercentPieField-size: #{$-stat-button-icon-size};
|
||||
--PercentPieField-gap: #{$-stat-button-icon-margin-end};
|
||||
|
||||
.oe_stat_button:not(:first-child) .o_pie {
|
||||
@extend %-stat-button-figure-offset;
|
||||
}
|
||||
|
||||
.o_pie_info {
|
||||
@extend %-stat-button-container;
|
||||
}
|
||||
|
||||
.o_pie_text {
|
||||
@extend %-stat-button-text;
|
||||
}
|
||||
|
||||
.o_pie_value {
|
||||
@extend %-stat-button-value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
<t t-set="allButtons" t-value="getButtons()" />
|
||||
<t t-set="visibleButtons" t-value="allButtons['visible']" />
|
||||
<t t-set="additionalButtons" t-value="allButtons['additional']" />
|
||||
<div class="o-form-buttonbox oe_button_box position-relative text-end" t-attf-class="{{ visibleButtons.length >= getMaxButtons() ? 'o_full' : 'o_not_full'}} {{this.props.class}}">
|
||||
<div class="o-form-buttonbox position-relative d-flex w-md-auto" t-attf-class="{{ visibleButtons.length >= getMaxButtons() ? 'o_full w-100' : 'o_not_full'}} {{this.props.class}}">
|
||||
<t t-slot="{{ button_value }}" t-foreach="visibleButtons" t-as="button" t-key="button_value"/>
|
||||
<t t-if="additionalButtons.length" >
|
||||
<Dropdown togglerClass="'btn btn-light o_button_more border-0 border-start border-bottom'" menuClass="'o_dropdown_more position-absolute start-0 top-100 w-100 p-0 border-0'" class="'oe_stat_button btn position-relative p-0 border-0'">
|
||||
<Dropdown position="'bottom-end'" togglerClass="'o_button_more btn btn-outline-secondary'" menuClass="'o_dropdown_more position-absolute top-100 w-100 p-0 border-0'" class="'oe_stat_button btn position-relative p-0 border-0'">
|
||||
<t t-set-slot="toggler"><span>More</span></t>
|
||||
<DropdownItem t-foreach="additionalButtons" t-as="button" t-key="button_value" class="'d-flex flex-column p-0'">
|
||||
<t t-slot="{{ button_value }}" />
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
$o-form-view-sheet-max-width: 1140px !default;
|
||||
$o-form-view-sheet-max-width: 1534px !default;
|
||||
$o-form-picture-size: 90px !default;
|
||||
|
||||
@@ -63,9 +63,11 @@ export class FormCompiler extends ViewCompiler {
|
||||
);
|
||||
}
|
||||
|
||||
compile() {
|
||||
compile(key, params = {}) {
|
||||
const compiled = super.compile(...arguments);
|
||||
compiled.children[0].setAttribute("t-ref", "compiled_view_root");
|
||||
if (!params.isSubView) {
|
||||
compiled.children[0].setAttribute("t-ref", "compiled_view_root");
|
||||
}
|
||||
return compiled;
|
||||
}
|
||||
|
||||
@@ -151,17 +153,10 @@ export class FormCompiler extends ViewCompiler {
|
||||
if (child.tagName === "button" || child.children.tagName === "button") {
|
||||
child.classList.add(
|
||||
"oe_stat_button",
|
||||
"btn-light",
|
||||
"flex-shrink-0",
|
||||
"mb-0",
|
||||
"py-0",
|
||||
"border-0",
|
||||
"border-start",
|
||||
"border-bottom",
|
||||
"rounded-0",
|
||||
"text-start",
|
||||
"text-nowrap",
|
||||
"text-capitalize"
|
||||
"btn",
|
||||
"btn-outline-secondary",
|
||||
"flex-grow-1",
|
||||
"flex-lg-grow-0"
|
||||
);
|
||||
}
|
||||
if (child.tagName === "field") {
|
||||
@@ -230,7 +225,11 @@ export class FormCompiler extends ViewCompiler {
|
||||
});
|
||||
if (!sheetNode) {
|
||||
for (const child of el.childNodes) {
|
||||
append(form, this.compileNode(child, params));
|
||||
// ButtonBox are already compiled for the control panel and should not
|
||||
// be recompiled for the renderer of the view
|
||||
if (child.attributes?.name?.value !== "button_box") {
|
||||
append(form, this.compileNode(child, params));
|
||||
}
|
||||
}
|
||||
form.classList.add("o_form_nosheet");
|
||||
} else {
|
||||
@@ -407,7 +406,7 @@ export class FormCompiler extends ViewCompiler {
|
||||
compileHeader(el, params) {
|
||||
const statusBar = createElement("div");
|
||||
statusBar.className =
|
||||
"o_form_statusbar position-relative d-flex justify-content-between border-bottom";
|
||||
"o_form_statusbar position-relative d-flex justify-content-between mb-2 px-3 pb-2 pb-md-0 px-md-0";
|
||||
const buttons = [];
|
||||
const others = [];
|
||||
for (const child of el.childNodes) {
|
||||
@@ -644,12 +643,12 @@ export class FormCompiler extends ViewCompiler {
|
||||
sheetBG.className = "o_form_sheet_bg";
|
||||
|
||||
const sheetFG = createElement("div");
|
||||
sheetFG.className = "o_form_sheet position-relative clearfix";
|
||||
sheetFG.className = "o_form_sheet position-relative";
|
||||
|
||||
append(sheetBG, sheetFG);
|
||||
for (const child of el.childNodes) {
|
||||
const compiled = this.compileNode(child, params);
|
||||
if (!compiled) {
|
||||
if (!compiled || compiled.nodeName === "ButtonBox") {
|
||||
continue;
|
||||
}
|
||||
if (getTag(child, true) === "field") {
|
||||
|
||||
@@ -18,8 +18,14 @@ import { isX2Many } from "@web/views/utils";
|
||||
import { useViewButtons } from "@web/views/view_button/view_button_hook";
|
||||
import { useSetupView } from "@web/views/view_hook";
|
||||
import { FormStatusIndicator } from "./form_status_indicator/form_status_indicator";
|
||||
import { ButtonBox } from "./button_box/button_box";
|
||||
import { ViewButton } from "@web/views/view_button/view_button";
|
||||
import { Field } from "@web/views/fields/field";
|
||||
|
||||
import { Component, onRendered, useEffect, useRef, useState } from "@odoo/owl";
|
||||
import { useViewCompiler } from "../view_compiler";
|
||||
import { FormCompiler } from "./form_compiler";
|
||||
import { evalDomain } from "../utils";
|
||||
|
||||
const viewRegistry = registry.category("views");
|
||||
|
||||
@@ -180,6 +186,17 @@ export class FormController extends Component {
|
||||
this.archInfo.arch = this.archInfo.xmlDoc.outerHTML;
|
||||
}
|
||||
|
||||
const xmlDocButtonBox = this.archInfo.xmlDoc.querySelector("div[name='button_box']");
|
||||
if (xmlDocButtonBox) {
|
||||
const buttonBoxTemplates = useViewCompiler(
|
||||
this.props.Compiler || FormCompiler,
|
||||
xmlDocButtonBox.outerHTML,
|
||||
{ ButtonBox: xmlDocButtonBox },
|
||||
{ isSubView: true }
|
||||
);
|
||||
this.buttonBoxTemplate = buttonBoxTemplates.ButtonBox;
|
||||
}
|
||||
|
||||
const rootRef = useRef("root");
|
||||
useViewButtons(this.model, rootRef, {
|
||||
beforeExecuteAction: this.beforeExecuteActionButton.bind(this),
|
||||
@@ -471,10 +488,21 @@ export class FormController extends Component {
|
||||
result["o_field_highlight"] = size < SIZES.SM || hasTouch();
|
||||
return result;
|
||||
}
|
||||
|
||||
evalDomainFromRecord(record, expr) {
|
||||
return evalDomain(expr, record.evalContext);
|
||||
}
|
||||
}
|
||||
|
||||
FormController.template = `web.FormView`;
|
||||
FormController.components = { ActionMenus, FormStatusIndicator, Layout };
|
||||
FormController.components = {
|
||||
ActionMenus,
|
||||
FormStatusIndicator,
|
||||
Layout,
|
||||
ButtonBox,
|
||||
ViewButton,
|
||||
Field,
|
||||
};
|
||||
FormController.props = {
|
||||
...standardViewProps,
|
||||
discardRecord: { type: Function, optional: true },
|
||||
|
||||
@@ -16,16 +16,12 @@
|
||||
}
|
||||
// Compensate margins
|
||||
@mixin o-form-sheet-negative-margin {
|
||||
margin-left: -$o-horizontal-padding;
|
||||
margin-right: -$o-horizontal-padding;
|
||||
@include media-breakpoint-up(lg, $o-extra-grid-breakpoints) {
|
||||
margin-left: -$o-horizontal-padding*2;
|
||||
margin-right: -$o-horizontal-padding*2;
|
||||
}
|
||||
margin-left: calc(var(--formView-sheet-padding-x) * -1);
|
||||
margin-right: calc(var(--formView-sheet-padding-x) * -1);
|
||||
}
|
||||
@mixin o-form-nosheet-negative-margin {
|
||||
margin-left: -$o-horizontal-padding;
|
||||
margin-right: -$o-horizontal-padding;
|
||||
margin-left: calc(var(--formView-sheet-padding-x) * -1);
|
||||
margin-right: calc(var(--formView-sheet-padding-x) * -1);
|
||||
}
|
||||
|
||||
// XXS form view specific rules
|
||||
@@ -66,7 +62,18 @@
|
||||
}
|
||||
|
||||
.o_form_view {
|
||||
background-color: $o-view-background-color;
|
||||
// Component custom properties
|
||||
--formView-sheetBg-padding-top: #{map-get($spacers, 2)};
|
||||
--formView-sheetBg-padding-x: #{map-get($spacers, 3)};
|
||||
|
||||
--formView-sheet-padding-y: #{map-get($spacers, 3)};
|
||||
--formView-sheet-padding-x: #{map-get($spacers, 3)};
|
||||
--formView-sheet-padding-y-xxl: #{map-get($spacers, 4)};
|
||||
--formView-sheet-padding-x-xxl: #{map-get($spacers, 4)};
|
||||
--formView-sheet-border-color: #{$border-color};
|
||||
--formView-sheet-border-width: #{$border-width};
|
||||
--formView-sheet-border-radius: #{$border-radius};
|
||||
--formView-sheet-background: #{$o-view-background-color};
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
display: flex;
|
||||
@@ -242,9 +249,6 @@
|
||||
}
|
||||
|
||||
.o_notebook {
|
||||
--notebook-margin-x: -#{$o-horizontal-padding};
|
||||
--notebook-padding-x: #{$o-horizontal-padding};
|
||||
|
||||
> .tab-content > .tab-pane > :first-child:not(.o_group) {
|
||||
// These elements will appear attached to the tabs
|
||||
&.o_field_x2many.o_field_x2many_list {
|
||||
@@ -254,45 +258,49 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Sheet
|
||||
// Sheet BG
|
||||
.o_form_sheet_bg {
|
||||
position: relative;
|
||||
flex: 1 0 auto;
|
||||
background-color: $o-webclient-background-color;
|
||||
border-bottom: 1px solid $border-color;
|
||||
padding-top: var(--formView-sheetBg-padding-top);
|
||||
padding-left: var(--formView-sheetBg-padding-left, var(--formView-sheetBg-padding-x));
|
||||
padding-right: var(--formView-sheetBg-padding-right, var(--formView-sheetBg-padding-x));
|
||||
width: 100%;
|
||||
max-width: $o-form-view-sheet-max-width;
|
||||
margin-right: auto; // Always align to le left
|
||||
|
||||
> .o_form_sheet {
|
||||
@include make-container();
|
||||
@include make-container-max-widths();
|
||||
@include media-breakpoint-down(md) {
|
||||
--formView-sheetBg-padding-x: var(--formView-sheetBg-padding-x-sm, 0);
|
||||
}
|
||||
}
|
||||
|
||||
background-color: $o-view-background-color;
|
||||
box-shadow: 0 5px 20px -15px rgba(#000, 0.4);
|
||||
margin: $o-sheet-vpadding*0.2 auto;
|
||||
@include media-breakpoint-up(md) {
|
||||
margin: $o-sheet-vpadding*0.5 auto;
|
||||
}
|
||||
padding: $o-sheet-vpadding;
|
||||
@include o-form-sheet-inner-right-padding;
|
||||
@include o-form-sheet-inner-left-padding;
|
||||
// Sheet
|
||||
.o_form_sheet {
|
||||
padding: var(--formView-sheet-padding-y) var(--formView-sheet-padding-x);
|
||||
border-style: solid;
|
||||
border-color: var(--formView-sheet-border-color);
|
||||
border-width: var(--formView-sheet-border-width);
|
||||
border-radius: var(--formView-sheet-border-radius);
|
||||
background-color: var(--formView-sheet-background);
|
||||
|
||||
@include media-breakpoint-up(sm) {
|
||||
border: 1px solid $border-color;
|
||||
}
|
||||
@include media-breakpoint-down(md) {
|
||||
--formView-sheet-padding-y: #{map-get($spacers, 4)};
|
||||
--formView-sheet-padding-x: #{map-get($spacers, 3)};
|
||||
--formView-sheet-border-width: 0 0 1px 0;
|
||||
--formView-sheet-border-radius: 0;
|
||||
}
|
||||
|
||||
// Selection
|
||||
> .o_selection {
|
||||
float: right;
|
||||
}
|
||||
@include media-breakpoint-up(xxl, $o-extra-grid-breakpoints) {
|
||||
--formView-sheet-padding-y: var(--formView-sheet-padding-y-xxl);
|
||||
--formView-sheet-padding-x: var(--formView-sheet-padding-x-xxl);
|
||||
}
|
||||
|
||||
// Selection
|
||||
> .o_selection {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
|
||||
// Statusbar
|
||||
.o_form_statusbar {
|
||||
@include media-breakpoint-up(md) {
|
||||
padding-left: $o-horizontal-padding;
|
||||
}
|
||||
background-color: $o-view-background-color;
|
||||
|
||||
> .o_statusbar_status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -313,46 +321,8 @@
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
> .o_statusbar_buttons {
|
||||
> .btn, .o_widget > .btn {
|
||||
$o-statusbar-buttons-vmargin: 4px;
|
||||
min-height: $o-statusbar-height - 2 * $o-statusbar-buttons-vmargin;
|
||||
margin: $o-statusbar-buttons-vmargin 3px $o-statusbar-buttons-vmargin 0;
|
||||
padding-top: 2px;
|
||||
padding-bottom: 2px;
|
||||
|
||||
> .o_button_icon {
|
||||
margin-right: $o-statbutton-spacing;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Touch device mode
|
||||
> .o_statusbar_buttons > .btn-group, > .o_statusbar_status {
|
||||
> .dropdown-toggle {
|
||||
&:after {
|
||||
@include o-caret-down;
|
||||
margin-left: $o-form-spacing-unit;
|
||||
}
|
||||
}
|
||||
|
||||
> .dropdown-menu {
|
||||
min-width: 0;
|
||||
|
||||
.dropdown-item.btn {
|
||||
min-width: 100%;
|
||||
margin-bottom: 3px;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Match the status bar button styles with rest of the buttons in touch device
|
||||
@include media-breakpoint-down(md) {
|
||||
> .o_statusbar_buttons > .btn {
|
||||
margin: 0;
|
||||
padding: $o-touch-btn-padding;
|
||||
}
|
||||
border-bottom: $border-width solid $border-color;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -412,6 +382,8 @@
|
||||
|
||||
// Groups
|
||||
.o_group {
|
||||
justify-content: space-between;
|
||||
|
||||
.o_form_label {
|
||||
font-weight: $font-weight-normal;
|
||||
}
|
||||
@@ -454,6 +426,7 @@
|
||||
--columns: 1;
|
||||
gap: map-get($spacers, 2) $o-horizontal-padding;
|
||||
margin-bottom: map-get($spacers, 2);
|
||||
max-width: 700px;
|
||||
|
||||
span, .o_field_boolean, .oe_avatar, .o_form_uri {
|
||||
&.o_field_widget {
|
||||
@@ -507,19 +480,22 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Contextual adaptation
|
||||
.o_kanban_renderer {
|
||||
--Kanban-background: transparent;
|
||||
--Kanban-gap: #{map-get($spacers, 2)};
|
||||
--KanbanRecord-margin-v: 0;
|
||||
--KanbanRecord-margin-h: 0;
|
||||
}
|
||||
|
||||
// Notebooks
|
||||
.o_notebook {
|
||||
--notebook-margin-x: calc(var(--formView-sheet-padding-x) * -1);
|
||||
--notebook-padding-x: var(--formView-sheet-padding-x);
|
||||
|
||||
clear: both; // For the notebook not to have alongside floating elements
|
||||
margin-top: $o-form-spacing-unit * 2;
|
||||
|
||||
--notebook-margin-x: -#{$o-horizontal-padding};
|
||||
--notebook-padding-x: #{$o-horizontal-padding};
|
||||
|
||||
@include media-breakpoint-up(lg, $o-extra-grid-breakpoints) {
|
||||
--notebook-margin-x: -#{$o-horizontal-padding * 2};
|
||||
--notebook-padding-x: #{$o-horizontal-padding * 2};
|
||||
}
|
||||
|
||||
> .tab-content {
|
||||
border-bottom: 1px solid $border-color;
|
||||
|
||||
@@ -955,16 +931,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.o_form_sheet {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.o_form_sheet_bg > .o_form_sheet {
|
||||
border-top: 0;
|
||||
border-bottom: 0;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.o_form_label:not(.o_invisible_modifier) {
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
@@ -1049,7 +1015,15 @@
|
||||
}
|
||||
|
||||
.o_form_sheet_bg {
|
||||
padding: 0;
|
||||
/**
|
||||
* Reset all paddings and margins to 0.
|
||||
* Make the view fill all available room.
|
||||
*/
|
||||
--formView-sheetBg-padding-top: 0;
|
||||
--formView-sheetBg-padding-x: 0;
|
||||
--formView-sheet-padding: 0;
|
||||
--formView-sheet-border-radius: 0;
|
||||
|
||||
width: 100%;
|
||||
|
||||
> .o_form_statusbar, > .alert {
|
||||
@@ -1122,24 +1096,14 @@
|
||||
flex-flow: row nowrap !important;
|
||||
|
||||
.o_form_view_container {
|
||||
width: $o-form-sheet-min-width + 2 * $o-horizontal-padding;
|
||||
width: 1px; // List view needs a width value to recompute the size correctly
|
||||
}
|
||||
|
||||
.o_form_sheet_bg {
|
||||
flex: 1 1 auto; // Side chatter is disabled if this has to shrink but this was added for safety
|
||||
padding: 0 $o-horizontal-padding;
|
||||
overflow: auto;
|
||||
border-bottom: none;
|
||||
|
||||
> .o_form_statusbar, > .alert {
|
||||
margin-left: -$o-horizontal-padding;
|
||||
margin-right: -$o-horizontal-padding;
|
||||
}
|
||||
|
||||
> .o_form_sheet {
|
||||
width: 100%;
|
||||
max-width: map-get($container-max-widths, xxl);
|
||||
}
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1 1 $o-form-sheet-min-width; // Side chatter is disabled if this has to shrink but this was added for safety
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,8 +44,8 @@
|
||||
</t>
|
||||
|
||||
<t t-name="web.FormView.Buttons" owl="1">
|
||||
<div class="o_cp_buttons" role="toolbar" aria-label="Main actions" t-ref="cpButtons">
|
||||
<div t-if="model.root.isInEdition" class="o_form_buttons_edit">
|
||||
<div class="o_cp_buttons d-flex align-items-baseline" role="toolbar" aria-label="Main actions" t-ref="cpButtons">
|
||||
<div t-if="model.root.isInEdition" class="o_form_buttons_edit d-flex gap-1">
|
||||
<button type="button" class="btn btn-primary o_form_button_save" data-hotkey="s" t-on-click.stop="() => this.saveButtonClicked({closable: true})">
|
||||
Save
|
||||
</button>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<t t-name="web.StatusBarButtons" owl="1">
|
||||
<div class="o_statusbar_buttons d-flex align-items-center align-content-around flex-wrap">
|
||||
<div class="o_statusbar_buttons d-flex align-items-center align-content-around flex-wrap gap-1">
|
||||
<t t-set="slots" t-value="visibleSlotNames" />
|
||||
<t t-if="env.isSmall and slots.length > 1">
|
||||
<Dropdown togglerClass="'btn btn-primary'" showCaret="true">
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
// Default KanbanView values
|
||||
|
||||
--Kanban-background: #{$o-kanban-background};
|
||||
--Kanban-gap: unset;
|
||||
--Kanban-padding: 0;
|
||||
|
||||
--KanbanGroup-background: var(--Kanban-background);
|
||||
@@ -40,6 +41,7 @@
|
||||
border-top: $border-width solid var(--Kanban-background);
|
||||
background: var(--Kanban-background);
|
||||
padding: var(--Kanban-padding);
|
||||
gap: var(--Kanban-gap);
|
||||
|
||||
@include media-breakpoint-down(md) {
|
||||
--KanbanRecord-padding-h: #{$o-kanban-inside-hgutter * 2};
|
||||
|
||||
@@ -25,7 +25,7 @@ function iconFromString(iconString) {
|
||||
const icon = {};
|
||||
if (iconString.startsWith("fa-")) {
|
||||
icon.tag = "i";
|
||||
icon.class = `fa fa-fw o_button_icon ${iconString}`;
|
||||
icon.class = `o_button_icon fa ${iconString}`;
|
||||
} else {
|
||||
icon.tag = "img";
|
||||
icon.src = iconString;
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
t-att-tabindex="props.tabindex"
|
||||
t-on-click.stop="onClick"
|
||||
>
|
||||
<t t-if="icon" t-tag="icon.tag" t-att-class="icon.class" t-att-src="icon.src"/>
|
||||
<t t-if="icon" t-tag="icon.tag" t-att-class="icon.class + (props.string ? ' me-1' : '')" t-att-src="icon.src"/>
|
||||
<t t-slot="contents"><span t-if="props.string" t-esc="props.string"/></t>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
@@ -184,7 +184,7 @@ QUnit.module("Form Compiler", (hooks) => {
|
||||
const expected = /*xml*/ `
|
||||
<t>
|
||||
<div class="o_form_renderer o_form_nosheet" t-att-class="__comp__.props.class" t-attf-class="{{__comp__.props.record.isInEdition ? 'o_form_editable' : 'o_form_readonly'}} d-block {{ __comp__.props.record.isDirty ? 'o_form_dirty' : !__comp__.props.record.isNew ? 'o_form_saved' : '' }}" t-ref="compiled_view_root">
|
||||
<div class="o_form_statusbar position-relative d-flex justify-content-between border-bottom"><StatusBarButtons/></div>
|
||||
<div class="o_form_statusbar position-relative d-flex justify-content-between pb-3 px-3"><StatusBarButtons/></div>
|
||||
<div>someDiv</div>
|
||||
</div>
|
||||
</t>`;
|
||||
@@ -206,10 +206,10 @@ QUnit.module("Form Compiler", (hooks) => {
|
||||
const expected = /*xml*/ `
|
||||
<t>
|
||||
<div class="o_form_renderer" t-att-class="__comp__.props.class" t-attf-class="{{__comp__.props.record.isInEdition ? 'o_form_editable' : 'o_form_readonly'}} d-flex {{ __comp__.uiService.size < 6 ? "flex-column" : "flex-nowrap h-100" }} {{ __comp__.props.record.isDirty ? 'o_form_dirty' : !__comp__.props.record.isNew ? 'o_form_saved' : '' }}" t-ref="compiled_view_root">
|
||||
<div class="o_form_sheet_bg">
|
||||
<div class="o_form_statusbar position-relative d-flex justify-content-between border-bottom"><StatusBarButtons/></div>
|
||||
<div class="o_form_sheet_bg pt-2">
|
||||
<div class="o_form_statusbar position-relative d-flex justify-content-between pb-3 px-3"><StatusBarButtons/></div>
|
||||
<div>someDiv</div>
|
||||
<div class="o_form_sheet position-relative clearfix">
|
||||
<div class="o_form_sheet position-relative clearfix px-3">
|
||||
<div>inside sheet</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -267,7 +267,7 @@ QUnit.module("Form Compiler", (hooks) => {
|
||||
</form>`;
|
||||
|
||||
const expected = /*xml*/ `
|
||||
<div class="o_form_statusbar position-relative d-flex justify-content-between border-bottom">
|
||||
<div class="o_form_statusbar position-relative d-flex justify-content-between pb-3 px-3">
|
||||
<StatusBarButtons>
|
||||
<t t-set-slot="button_0" isVisible="true">
|
||||
<div>someDiv</div>
|
||||
@@ -285,7 +285,7 @@ QUnit.module("Form Compiler", (hooks) => {
|
||||
</form>`;
|
||||
|
||||
const expected = /*xml*/ `
|
||||
<div class="o_form_statusbar position-relative d-flex justify-content-between border-bottom">
|
||||
<div class="o_form_statusbar position-relative d-flex justify-content-between pb-3 px-3">
|
||||
<StatusBarButtons/>
|
||||
</div>`;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user