Initial commit: Core packages

This commit is contained in:
Ernad Husremovic 2025-08-29 15:20:45 +02:00
commit 12c29a983b
9512 changed files with 8379910 additions and 0 deletions

View file

@ -0,0 +1,26 @@
/** @odoo-module **/
import { useRefToModel } from '@mail/component_hooks/use_ref_to_model';
import { registerMessagingComponent } from '@mail/utils/messaging_component';
const { Component } = owl;
export class ActivityButtonView extends Component {
setup() {
super.setup();
useRefToModel({ fieldName: 'buttonRef', refName: 'button' });
}
get activityButtonView() {
return this.props.record;
}
}
Object.assign(ActivityButtonView, {
props: { record: Object },
template: 'mail.ActivityButtonView',
});
registerMessagingComponent(ActivityButtonView);

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="mail.ActivityButtonView" owl="1">
<a class="o_ActivityButtonView" role="button" t-on-click.prevent="activityButtonView.onClick" t-ref="button">
<i class="o_ActivityButtonView_icon fa fa-fw fa-lg" t-att-class="activityButtonView.buttonClass" role="img"/>
</a>
</t>
</templates>

View file

@ -0,0 +1,28 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
import { standardFieldProps } from "@web/views/fields/standard_field_props";
const { Component } = owl;
class ActivityException extends Component {
get textClass() {
if (this.props.value) {
return 'text-' + this.props.value + ' fa ' + this.props.record.data.activity_exception_icon;
}
return undefined;
}
}
Object.assign(ActivityException, {
props: standardFieldProps,
template: 'mail.ActivityException',
fieldDependencies: {
activity_exception_icon: { type: 'char' },
},
noLabel: true,
});
registry.category('fields').add('activity_exception', ActivityException);

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="mail.ActivityException" owl="1">
<div
t-if="props.value"
class="o_ActivityException float-end mt-1"
t-att-class="textClass"
title="This record has an exception activity."
></div>
</t>
</templates>

View file

@ -0,0 +1,20 @@
/** @odoo-module **/
import { registerMessagingComponent } from '@mail/utils/messaging_component';
const { Component } = owl;
export class ActivityListView extends Component {
get activityListView() {
return this.props.record;
}
}
Object.assign(ActivityListView, {
props: { record: Object },
template: 'mail.ActivityListView',
});
registerMessagingComponent(ActivityListView);

View file

@ -0,0 +1,8 @@
.o_ActivityListView {
width: #{"min(95vw, 300px)"};
max-height: #{"min(95vh, 350px)"};
}
.o_ActivityListView_activityList {
overflow-y: auto;
}

View file

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="mail.ActivityListView" owl="1">
<div class="o_ActivityListView d-flex flex-column" t-ref="root">
<div class="o_ActivityListView_activityList d-flex flex-column flex-grow-1">
<t t-if="activityListView.activityListViewItems.length === 0">
<span class="p-3 text-center fst-italic text-500 border-bottom">Schedule activities to help you get things done.</span>
</t>
<t t-if="activityListView.overdueActivityListViewItems.length > 0">
<div class="d-flex bg-100 py-2 border-bottom">
<span class="text-danger fw-bold mx-3">Overdue</span>
<span class="flex-grow-1"/>
<span class="badge rounded-pill text-bg-danger mx-3 align-self-center" t-esc="activityListView.overdueActivityListViewItems.length"/>
</div>
<t t-foreach="activityListView.overdueActivityListViewItems" t-as="activityListViewItem" t-key="activityListViewItem">
<ActivityListViewItem record="activityListViewItem"/>
</t>
</t>
<t t-if="activityListView.todayActivityListViewItems.length > 0">
<div class="d-flex bg-100 py-2 border-bottom">
<span class="text-warning fw-bold mx-3">Today</span>
<span class="flex-grow-1"/>
<span class="badge rounded-pill text-bg-warning mx-3 align-self-center" t-esc="activityListView.todayActivityListViewItems.length"/>
</div>
<t t-foreach="activityListView.todayActivityListViewItems" t-as="activityListViewItem" t-key="activityListViewItem">
<ActivityListViewItem record="activityListViewItem"/>
</t>
</t>
<t t-if="activityListView.plannedActivityListViewItems.length > 0">
<div class="d-flex bg-100 py-2 border-bottom">
<span class="text-success fw-bold mx-3">Planned</span>
<span class="flex-grow-1"/>
<span class="badge rounded-pill text-bg-success mx-3 align-self-center" t-esc="activityListView.plannedActivityListViewItems.length"/>
</div>
<t t-foreach="activityListView.plannedActivityListViewItems" t-as="activityListViewItem" t-key="activityListViewItem">
<ActivityListViewItem record="activityListViewItem"/>
</t>
</t>
</div>
<button class="o_ActivityListView_addActivityButton btn btn-secondary p-3 text-center" t-on-click="activityListView.onClickAddActivityButton">
<i class="fa fa-plus fa-fw"></i><strong>Schedule an activity</strong>
</button>
</div>
</t>
</templates>

View file

@ -0,0 +1,20 @@
/** @odoo-module **/
import { registerMessagingComponent } from '@mail/utils/messaging_component';
const { Component } = owl;
export class ActivityListViewItem extends Component {
get activityListViewItem() {
return this.props.record;
}
}
Object.assign(ActivityListViewItem, {
props: { record: Object },
template: 'mail.ActivityListViewItem',
});
registerMessagingComponent(ActivityListViewItem);

View file

@ -0,0 +1,16 @@
.o_ActivityListViewItem_actionLink {
@include o-hover-text-color($text-muted, map-get($theme-colors, 'success'));
@include o-hover-opacity(0.5, 1);
}
.o_ActivityListViewItem_editButton {
opacity: 0.5;
}
.o_ActivityListViewItem:hover .o_ActivityListViewItem_editButton {
opacity: 1;
}
.o_ActivityListViewItem_container {
min-width: 0;
}

View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="mail.ActivityListViewItem" owl="1">
<div class="o_ActivityListViewItem d-flex flex-column border-bottom py-2">
<div class="o_ActivityListViewItem_container d-flex align-items-baseline ms-3 me-1">
<i t-if="activityListViewItem.activity.icon" class="fa small me-2" t-attf-class="{{ activityListViewItem.activity.icon }}" role="img"/>
<t t-if="activityListViewItem.activity.summary">
<b class="text-900 me-2 text-truncate flex-grow-1 flex-basis-0" t-esc="activityListViewItem.activity.summary"/>
</t>
<t t-if="!activityListViewItem.activity.summary and activityListViewItem.activity.type">
<b class="text-900 me-2 text-truncate flex-grow-1" t-esc="activityListViewItem.activity.type.displayName"/>
</t>
<button t-if="activityListViewItem.hasEditButton" class="o_ActivityListViewItem_editButton btn btn-sm btn-link" t-on-click="activityListViewItem.onClickEditActivityButton">
<i class="fa fa-pencil"/>
</button>
<t t-if="activityListViewItem.activity.canWrite">
<button t-if="activityListViewItem.fileUploader" class="o_ActivityListViewItem_actionLink btn btn-link shadow-none fs-4 fa fa-upload" title="Upload file" aria-label="Upload File" t-on-click="activityListViewItem.onClickUploadDocument"/>
<button t-if="activityListViewItem.hasMarkDoneButton" class="o_ActivityListViewItem_actionLink o_ActivityListViewItem_markAsDone btn btn-link shadow-none fs-4 fa fa-check-circle" title="Mark as done" aria-label="Mark as done" t-on-click="activityListViewItem.onClickMarkAsDone" t-ref="markDoneButton"/>
</t>
</div>
<div t-if="activityListViewItem.activity.state !== 'today'" class="d-flex align-items-baseline flex-wrap mx-3">
<i class="fa fa-clock-o me-2 text-muted" role="img" aria-label="Deadline" title="Deadline"/>
<t t-if="!activityListViewItem.activity.isCurrentPartnerAssignee and activityListViewItem.activity.assignee">
<small class="text-truncate" t-esc="activityListViewItem.activity.assignee.displayName"/>
<small class="mx-1">-</small>
</t>
<small t-att-title="activityListViewItem.activity.dateDeadline" t-esc="activityListViewItem.delayLabel"/>
</div>
<ActivityMarkDonePopoverContent t-if="activityListViewItem.markDoneView" record="activityListViewItem.markDoneView"/>
<div t-if="activityListViewItem.mailTemplateViews.length > 0" class="mx-3 mt-2">
<MailTemplate
t-foreach="activityListViewItem.mailTemplateViews" t-as="mailTemplateView" t-key="mailTemplateView"
record="mailTemplateView"
/>
</div>
</div>
</t>
</templates>

View file

@ -0,0 +1,20 @@
/** @odoo-module **/
import { registerMessagingComponent } from '@mail/utils/messaging_component';
const { Component } = owl;
export class KanbanFieldActivityView extends Component {
get kanbanFieldActivityView() {
return this.props.record;
}
}
Object.assign(KanbanFieldActivityView, {
props: { record: Object },
template: 'mail.KanbanFieldActivityView',
});
registerMessagingComponent(KanbanFieldActivityView);

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="mail.KanbanFieldActivityView" owl="1">
<ActivityButtonView record="kanbanFieldActivityView.activityButtonView"/>
</t>
</templates>

View file

@ -0,0 +1,98 @@
/** @odoo-module **/
// ensure components are registered beforehand.
import '@mail/backend_components/kanban_field_activity_view/kanban_field_activity_view';
import { getMessagingComponent } from '@mail/utils/messaging_component';
import { registry } from "@web/core/registry";
import { standardFieldProps } from "@web/views/fields/standard_field_props";
const { Component, onWillDestroy, onWillUpdateProps } = owl;
const getNextId = (function () {
let tmpId = 0;
return () => {
tmpId += 1;
return tmpId;
};
})();
/**
* Container for messaging component KanbanFieldActivityView ensuring messaging
* records are ready before rendering KanbanFieldActivityView component.
*/
export class KanbanFieldActivityViewContainer extends Component {
/**
* @override
*/
setup() {
super.setup();
this.kanbanFieldActivityView = undefined;
this.kanbanFieldActivityViewId = getNextId();
this._insertFromProps(this.props);
onWillUpdateProps(nextProps => this._insertFromProps(nextProps));
onWillDestroy(() => this._deleteRecord());
}
/**
* @private
*/
_deleteRecord() {
if (this.kanbanFieldActivityView) {
if (this.kanbanFieldActivityView.exists()) {
this.kanbanFieldActivityView.delete();
}
this.kanbanFieldActivityView = undefined;
}
}
/**
* @private
*/
async _insertFromProps(props) {
const messaging = await this.env.services.messaging.get();
if (owl.status(this) === "destroyed") {
this._deleteRecord();
return;
}
const kanbanFieldActivityView = messaging.models['KanbanFieldActivityView'].insert({
id: this.kanbanFieldActivityViewId,
thread: {
activities: props.value.records.map(activityData => {
return {
id: activityData.resId,
};
}),
hasActivities: true,
id: props.record.resId,
model: props.record.resModel,
},
webRecord: props.record,
});
if (kanbanFieldActivityView !== this.kanbanFieldActivityView) {
this._deleteRecord();
this.kanbanFieldActivityView = kanbanFieldActivityView;
}
this.render();
}
}
Object.assign(KanbanFieldActivityViewContainer, {
components: { KanbanFieldActivityView: getMessagingComponent('KanbanFieldActivityView') },
fieldDependencies: {
activity_exception_decoration: { type: 'selection' },
activity_exception_icon: { type: 'char' },
activity_state: { type: 'selection' },
activity_summary: { type: 'char' },
activity_type_icon: { type: 'char' },
activity_type_id: { type: 'many2one', relation: 'mail.activity.type' },
},
props: {
...standardFieldProps,
},
template: 'mail.KanbanFieldActivityViewContainer',
});
registry.category('fields').add('kanban_activity', KanbanFieldActivityViewContainer);

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="mail.KanbanFieldActivityViewContainer" owl="1">
<KanbanFieldActivityView t-if="kanbanFieldActivityView" record="kanbanFieldActivityView"/>
</t>
</templates>

View file

@ -0,0 +1,20 @@
/** @odoo-module **/
import { registerMessagingComponent } from '@mail/utils/messaging_component';
const { Component } = owl;
export class ListFieldActivityView extends Component {
get listFieldActivityView() {
return this.props.record;
}
}
Object.assign(ListFieldActivityView, {
props: { record: Object },
template: 'mail.ListFieldActivityView',
});
registerMessagingComponent(ListFieldActivityView);

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="mail.ListFieldActivityView" owl="1">
<ActivityButtonView record="listFieldActivityView.activityButtonView"/>
<span class="o_ListFieldActivityView_summary" t-out="listFieldActivityView.summaryText"/>
</t>
</templates>

View file

@ -0,0 +1,98 @@
/** @odoo-module **/
// ensure components are registered beforehand.
import '@mail/backend_components/list_field_activity_view/list_field_activity_view';
import { getMessagingComponent } from '@mail/utils/messaging_component';
import { registry } from "@web/core/registry";
import { standardFieldProps } from "@web/views/fields/standard_field_props";
const { Component, onWillDestroy, onWillUpdateProps } = owl;
const getNextId = (function () {
let tmpId = 0;
return () => {
tmpId += 1;
return tmpId;
};
})();
/**
* Container for messaging component ListFieldActivityView ensuring messaging
* records are ready before rendering ListFieldActivityView component.
*/
export class ListFieldActivityViewContainer extends Component {
/**
* @override
*/
setup() {
super.setup();
this.listFieldActivityView = undefined;
this.listFieldActivityViewId = getNextId();
this._insertFromProps(this.props);
onWillUpdateProps(nextProps => this._insertFromProps(nextProps));
onWillDestroy(() => this._deleteRecord());
}
/**
* @private
*/
_deleteRecord() {
if (this.listFieldActivityView) {
if (this.listFieldActivityView.exists()) {
this.listFieldActivityView.delete();
}
this.listFieldActivityView = undefined;
}
}
/**
* @private
*/
async _insertFromProps(props) {
const messaging = await this.env.services.messaging.get();
if (owl.status(this) === "destroyed") {
this._deleteRecord();
return;
}
const listFieldActivityView = messaging.models['ListFieldActivityView'].insert({
id: this.listFieldActivityViewId,
thread: {
activities: props.value.records.map(activityData => {
return {
id: activityData.resId,
};
}),
hasActivities: true,
id: props.record.resId,
model: props.record.resModel,
},
webRecord: props.record,
});
if (listFieldActivityView !== this.listFieldActivityView) {
this._deleteRecord();
this.listFieldActivityView = listFieldActivityView;
}
this.render();
}
}
Object.assign(ListFieldActivityViewContainer, {
components: { ListFieldActivityView: getMessagingComponent('ListFieldActivityView') },
fieldDependencies: {
activity_exception_decoration: { type: 'selection' },
activity_exception_icon: { type: 'char' },
activity_state: { type: 'selection' },
activity_summary: { type: 'char' },
activity_type_icon: { type: 'char' },
activity_type_id: { type: 'many2one', relation: 'mail.activity.type' },
},
props: {
...standardFieldProps,
},
template: 'mail.ListFieldActivityViewContainer',
});
registry.category('fields').add('list_activity', ListFieldActivityViewContainer);

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="mail.ListFieldActivityViewContainer" owl="1">
<ListFieldActivityView t-if="listFieldActivityView" record="listFieldActivityView"/>
</t>
</templates>