mirror of
https://github.com/bringout/oca-web.git
synced 2026-04-19 12:52:00 +02:00
Add oca-web submodule with 68 web modules
🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
af56672c08
commit
53fddf87c8
2469 changed files with 101716 additions and 0 deletions
|
|
@ -0,0 +1,31 @@
|
|||
/** @odoo-module **/
|
||||
/* Copyright 2022 Tecnativa - Alexandre D. Díaz
|
||||
* Copyright 2022 Tecnativa - Carlos Roca
|
||||
* Copyright 2023 Taras Shabaranskyi
|
||||
* License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). */
|
||||
|
||||
import {ControlPanel} from "@web/search/control_panel/control_panel";
|
||||
import {Refresher} from "./refresher.esm";
|
||||
import {patch} from "@web/core/utils/patch";
|
||||
|
||||
ControlPanel.components = Object.assign({}, ControlPanel.components, {
|
||||
Refresher,
|
||||
});
|
||||
|
||||
/**
|
||||
* @property {String[]} forbiddenSubTypes
|
||||
* @property {Object<String, *>} refresherProps
|
||||
*/
|
||||
patch(ControlPanel.prototype, "web_refresher.ControlPanel", {
|
||||
setup() {
|
||||
this._super(...arguments);
|
||||
this.forbiddenSubTypes = ["base_settings"];
|
||||
this.refresherProps = {
|
||||
searchModel: this.env.searchModel,
|
||||
pagerProps: this.pagerProps,
|
||||
};
|
||||
this.displayRefresher = !this.forbiddenSubTypes.includes(
|
||||
this.env.config.viewSubType
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
/** @odoo-module **/
|
||||
/* Copyright 2022 Tecnativa - Alexandre D. Díaz
|
||||
* Copyright 2022 Tecnativa - Carlos Roca
|
||||
* Copyright 2023 Taras Shabaranskyi
|
||||
* License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). */
|
||||
|
||||
import {Component} from "@odoo/owl";
|
||||
import {useDebounced} from "@web/core/utils/timing";
|
||||
import {useService} from "@web/core/utils/hooks";
|
||||
|
||||
export function useRefreshAnimation(timeout) {
|
||||
const refreshClass = "o_content__refresh";
|
||||
let timeoutId = null;
|
||||
|
||||
function clearAnimationTimeout() {
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
timeoutId = null;
|
||||
}
|
||||
|
||||
function animate() {
|
||||
clearAnimationTimeout();
|
||||
const content = document.querySelector(".o_content");
|
||||
if (content) {
|
||||
content.classList.add(refreshClass);
|
||||
timeoutId = setTimeout(() => {
|
||||
// Check if element still exists in DOM after timeout
|
||||
if (
|
||||
document.contains(content) &&
|
||||
content.classList.contains(refreshClass)
|
||||
) {
|
||||
content.classList.remove(refreshClass);
|
||||
}
|
||||
clearAnimationTimeout();
|
||||
}, timeout);
|
||||
}
|
||||
}
|
||||
|
||||
return animate;
|
||||
}
|
||||
|
||||
export class Refresher extends Component {
|
||||
setup() {
|
||||
super.setup();
|
||||
this.action = useService("action");
|
||||
this.refreshAnimation = useRefreshAnimation(1000);
|
||||
this.onClickRefresh = useDebounced(this.onClickRefresh, 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
get displayButton() {
|
||||
const {searchModel, pagerProps, refresherReport} = this.props;
|
||||
const hasSearchModel = searchModel && searchModel.search;
|
||||
return Boolean(
|
||||
refresherReport || hasSearchModel || (pagerProps && pagerProps.onUpdate)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Boolean}
|
||||
* @private
|
||||
*/
|
||||
_searchModelRefresh() {
|
||||
const {searchModel} = this.props;
|
||||
if (searchModel && typeof searchModel.search === "function") {
|
||||
searchModel.search();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise<Boolean>}
|
||||
* @private
|
||||
*/
|
||||
async _pagerRefresh() {
|
||||
const pagerProps = this.props.pagerProps;
|
||||
if (pagerProps && typeof pagerProps.onUpdate === "function") {
|
||||
const {limit, offset} = pagerProps;
|
||||
await pagerProps.onUpdate({offset, limit});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise<Boolean>}
|
||||
*/
|
||||
async refresh() {
|
||||
let updated = await this._pagerRefresh();
|
||||
if (!updated) {
|
||||
updated = this._searchModelRefresh();
|
||||
}
|
||||
return updated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to refresh the views that has not the props
|
||||
* required by the refresher, like ir.actions.report or
|
||||
* ir.actions.client.
|
||||
*/
|
||||
async refreshReport() {
|
||||
const viewAction = this.action.currentController.action;
|
||||
const options = {};
|
||||
if (this.env.config.breadcrumbs.length > 1) {
|
||||
const breadcrumb = this.env.config.breadcrumbs.slice(-1);
|
||||
await this.action.restore(breadcrumb.jsId);
|
||||
} else {
|
||||
options.clearBreadcrumbs = true;
|
||||
}
|
||||
this.action.doAction(viewAction, options);
|
||||
}
|
||||
|
||||
async onClickRefresh() {
|
||||
if (this.props.refresherReport) {
|
||||
return this.refreshReport();
|
||||
}
|
||||
const updated = await this.refresh();
|
||||
if (updated) {
|
||||
this.refreshAnimation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(Refresher, {
|
||||
template: "web_refresher.Button",
|
||||
props: {
|
||||
searchModel: {type: Object, optional: true},
|
||||
pagerProps: {type: Object, optional: true},
|
||||
refresherReport: {type: Boolean, optional: true},
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
.oe_cp_refresher {
|
||||
margin: auto 0 auto 0;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.o_content {
|
||||
&__refresh {
|
||||
animation: web_refresh 0.6s ease;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes web_refresh {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
transform: translateY(10px);
|
||||
opacity: 0.6;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!-- Copyright 2024 Tecnativa - Carlos Roca
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
|
||||
<template>
|
||||
<t
|
||||
t-name="web_refresher.ControlPanel.Regular"
|
||||
t-inherit="web.ControlPanel.Regular"
|
||||
t-inherit-mode="extension"
|
||||
owl="1"
|
||||
>
|
||||
<xpath expr="//div[hasclass('o_cp_pager')]" position="before">
|
||||
<div class="oe_cp_refresher" role="search" t-ref="refresher">
|
||||
<Refresher t-if="displayRefresher" t-props="refresherProps" />
|
||||
</div>
|
||||
</xpath>
|
||||
<xpath expr="//div[hasclass('o_cp_bottom_right')]" position="before">
|
||||
<div
|
||||
t-if="!display['bottom-right']"
|
||||
class="o_cp_bottom_right d-flex flex-row-reverse"
|
||||
>
|
||||
<div class="oe_cp_refresher" role="search" t-ref="refresher">
|
||||
<Refresher t-if="displayRefresher" refresherReport="true" />
|
||||
</div>
|
||||
</div>
|
||||
</xpath>
|
||||
</t>
|
||||
<t
|
||||
t-name="web_refresher.ControlPanel.Small"
|
||||
t-inherit="web.ControlPanel.Small"
|
||||
t-inherit-mode="extension"
|
||||
owl="1"
|
||||
>
|
||||
<xpath expr="//div[hasclass('o_cp_pager')]" position="before">
|
||||
<div class="oe_cp_refresher" role="search" t-ref="refresher">
|
||||
<Refresher t-if="displayRefresher" t-props="refresherProps" />
|
||||
</div>
|
||||
</xpath>
|
||||
<xpath expr="//div[hasclass('o_cp_bottom_right')]" position="before">
|
||||
<div
|
||||
t-if="!display['bottom-right']"
|
||||
class="o_cp_bottom_right d-flex flex-row-reverse"
|
||||
>
|
||||
<div class="oe_cp_refresher" role="search" t-ref="refresher">
|
||||
<Refresher t-if="displayRefresher" refresherReport="true" />
|
||||
</div>
|
||||
</div>
|
||||
</xpath>
|
||||
</t>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!-- Copyright 2024 Tecnativa - Carlos Roca
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
|
||||
<template>
|
||||
<t
|
||||
t-name="web_refresher.FormControlPanel"
|
||||
t-inherit="web.FormControlPanel"
|
||||
t-inherit-mode="extension"
|
||||
owl="1"
|
||||
>
|
||||
<xpath expr="//div[hasclass('o_cp_pager')]" position="before">
|
||||
<div class="oe_cp_refresher" role="search" t-ref="refresher">
|
||||
<Refresher t-if="displayRefresher" t-props="refresherProps" />
|
||||
</div>
|
||||
</xpath>
|
||||
<xpath expr="//div[hasclass('o_cp_bottom_right')]" position="before">
|
||||
<div
|
||||
t-if="!display['bottom-right']"
|
||||
class="o_cp_bottom_right d-flex flex-row-reverse"
|
||||
>
|
||||
<div class="oe_cp_refresher" role="search" t-ref="refresher">
|
||||
<Refresher t-if="displayRefresher" refresherReport="true" />
|
||||
</div>
|
||||
</div>
|
||||
</xpath>
|
||||
</t>
|
||||
</template>
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!-- Copyright 2022 Tecnativa - Alexandre Díaz
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
|
||||
<template>
|
||||
<t t-name="web_refresher.Button" owl="1">
|
||||
<nav
|
||||
class="oe_refresher"
|
||||
aria-label="Refresher"
|
||||
aria-atomic="true"
|
||||
t-if="displayButton"
|
||||
>
|
||||
<button
|
||||
class="fa fa-refresh btn btn-icon oe_pager_refresh"
|
||||
aria-label="Refresh"
|
||||
t-on-click="onClickRefresh"
|
||||
title="Refresh"
|
||||
tabindex="-1"
|
||||
/>
|
||||
</nav>
|
||||
</t>
|
||||
</template>
|
||||
Loading…
Add table
Add a link
Reference in a new issue