Initial commit: Odoo 18.0-20251222 extra-addons
This commit is contained in:
29
web_refresher/static/src/js/control_panel.esm.js
Executable file
29
web_refresher/static/src/js/control_panel.esm.js
Executable file
@@ -0,0 +1,29 @@
|
||||
/* 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";
|
||||
|
||||
patch(ControlPanel, {
|
||||
components: {...ControlPanel.components, Refresher},
|
||||
});
|
||||
|
||||
patch(ControlPanel.prototype, {
|
||||
/**
|
||||
* @returns {{searchModel: Object<*>, pagerProps: Object<*>}|null}
|
||||
*/
|
||||
get refresherProps() {
|
||||
const {config, searchModel} = this.env;
|
||||
const forbiddenSubType = ["base_settings"];
|
||||
if (forbiddenSubType.includes(config.viewSubType)) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
searchModel: searchModel,
|
||||
pagerProps: this.pagerProps,
|
||||
};
|
||||
},
|
||||
});
|
||||
122
web_refresher/static/src/js/refresher.esm.js
Executable file
122
web_refresher/static/src/js/refresher.esm.js
Executable file
@@ -0,0 +1,122 @@
|
||||
/* 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;
|
||||
|
||||
/**
|
||||
* @returns {DOMTokenList|null}
|
||||
*/
|
||||
function contentClassList() {
|
||||
const content = document.querySelector(".o_content");
|
||||
return content ? content.classList : null;
|
||||
}
|
||||
|
||||
function clearAnimationTimeout() {
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
timeoutId = null;
|
||||
}
|
||||
|
||||
function animate() {
|
||||
clearAnimationTimeout();
|
||||
contentClassList().add(refreshClass);
|
||||
timeoutId = setTimeout(() => {
|
||||
contentClassList().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}
|
||||
* @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() {
|
||||
const {searchModel, pagerProps} = this.props;
|
||||
if (!searchModel && !pagerProps) {
|
||||
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},
|
||||
},
|
||||
});
|
||||
24
web_refresher/static/src/scss/refresher.scss
Executable file
24
web_refresher/static/src/scss/refresher.scss
Executable file
@@ -0,0 +1,24 @@
|
||||
.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);
|
||||
margin-bottom: 10px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
17
web_refresher/static/src/xml/control_panel.xml
Executable file
17
web_refresher/static/src/xml/control_panel.xml
Executable file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!-- Copyright 2022 Tecnativa - Alexandre Díaz
|
||||
Copyright 2023 Taras Shabaranskyi
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
|
||||
<template>
|
||||
<t
|
||||
t-name="web_refresher.ControlPanel"
|
||||
t-inherit="web.ControlPanel"
|
||||
t-inherit-mode="extension"
|
||||
>
|
||||
<xpath expr="//div[hasclass('o_cp_pager')]" position="before">
|
||||
<div class="oe_cp_refresher" role="search" t-ref="refresher">
|
||||
<Refresher t-props="refresherProps" />
|
||||
</div>
|
||||
</xpath>
|
||||
</t>
|
||||
</template>
|
||||
17
web_refresher/static/src/xml/refresher.xml
Executable file
17
web_refresher/static/src/xml/refresher.xml
Executable file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!-- Copyright 2022 Tecnativa - Alexandre Díaz
|
||||
Copyright 2023 Taras Shabaranskyi
|
||||
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->
|
||||
<template>
|
||||
<t t-name="web_refresher.Button">
|
||||
<nav class="oe_refresher" aria-label="Refresher" aria-atomic="true">
|
||||
<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>
|
||||
Reference in New Issue
Block a user