[REF] base_setup: convert module to new js framework

convert res_config_dev_tool widget
convert res_config_edition widget
convert res_config_invite_users widget

closes odoo/odoo#96615

Related: odoo/enterprise#29804
Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
This commit is contained in:
Simon Genin (ges)
2022-07-28 08:10:45 +02:00
parent cd624c891f
commit 6ab02de109
11 changed files with 221 additions and 243 deletions
+1 -3
View File
@@ -22,9 +22,7 @@ Shows you a list of applications features to install from.
'assets': {
'web.assets_backend': [
'base_setup/static/src/scss/settings.scss',
'base_setup/static/src/js/res_config_dev_tool.js',
'base_setup/static/src/js/res_config_edition.js',
'base_setup/static/src/js/res_config_invite_users.js',
'base_setup/static/src/js/**/*.js',
],
'web.assets_qweb': [
'base_setup/static/src/xml/**/*',
@@ -0,0 +1,20 @@
/** @odoo-modules */
import { registry } from "@web/core/registry";
export const demoDataService = {
dependencies: ["rpc"],
async start(env, { rpc }) {
let isDemoDataActiveProm;
return {
isDemoDataActive() {
if (!isDemoDataActiveProm) {
isDemoDataActiveProm = rpc("/base_setup/demo_active");
}
return isDemoDataActiveProm;
},
};
},
};
registry.category("services").add("demo_data", demoDataService);
@@ -1,49 +1,37 @@
odoo.define('base_setup.ResConfigDevTool', function (require) {
"use strict";
/** @odoo-module */
var config = require('web.config');
var Widget = require('web.Widget');
var widget_registry = require('web.widget_registry');
import { registry } from "@web/core/registry";
import { useService } from "@web/core/utils/hooks";
var ResConfigDevTool = Widget.extend({
template: 'res_config_dev_tool',
events: {
'click .o_web_settings_force_demo': '_onClickForceDemo',
},
const { Component, onWillStart } = owl;
init: function () {
this._super.apply(this, arguments);
this.isDebug = config.isDebug();
this.isAssets = config.isDebug("assets");
this.isTests = config.isDebug("tests");
},
/**
* Widget in the settings that handles the "Developer Tools" section.
* Can be used to enable/disable the debug modes.
* Can be used to load the demo data.
*/
class ResConfigDevTool extends Component {
setup() {
this.isDebug = Boolean(odoo.debug);
this.isAssets = odoo.debug.includes("assets");
this.isTests = odoo.debug.includes("tests");
willStart: function () {
var self = this;
return this._super.apply(this, arguments).then(function () {
return self._rpc({
route: '/base_setup/demo_active',
}).then(function (demo_active) {
self.demo_active = demo_active;
});
});
},
this.action = useService("action");
this.demo = useService("demo_data");
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
onWillStart(async () => {
this.isDemoDataActive = await this.demo.isDemoDataActive();
});
}
/**
* Forces demo data to be installed in a database without demo data installed.
*
* @private
* @param {MouseEvent} ev
*/
_onClickForceDemo: function (ev) {
ev.preventDefault();
this.do_action('base.demo_force_install_action');
},
});
/**
* Forces demo data to be installed in a database without demo data installed.
*/
onClickForceDemo() {
this.action.doAction("base.demo_force_install_action");
}
}
widget_registry.add('res_config_dev_tool', ResConfigDevTool);
});
ResConfigDevTool.template = "res_config_dev_tool";
registry.category("view_widgets").add("res_config_dev_tool", ResConfigDevTool);
@@ -1,26 +1,24 @@
odoo.define('base_setup.ResConfigEdition', function (require) {
"use strict";
/** @odoo-module */
var Widget = require('web.Widget');
var widget_registry = require('web.widget_registry');
var session = require ('web.session');
import { registry } from "@web/core/registry";
import { session } from "@web/session";
var ResConfigEdition = Widget.extend({
template: 'res_config_edition',
const { Component } = owl;
const { DateTime } = luxon;
/**
* @override
*/
init: function () {
this._super.apply(this, arguments);
this.server_version = session.server_version;
this.expiration_date = session.expiration_date
? moment(session.expiration_date)
: moment().add(30, 'd');
},
});
/**
* Widget in the settings that handles a part of the "About" section.
* Contains info about the odoo version, database expiration date and copyrights.
*/
class ResConfigEdition extends Component {
setup() {
this.serverVersion = session.server_version;
this.expirationDate = session.expiration_date
? DateTime.fromSQL(session.expirationDate).toLocaleString(DateTime.DATE_FULL)
: DateTime.now().plus({ days: 30 }).toLocaleString(DateTime.DATE_FULL);
}
}
widget_registry.add('res_config_edition', ResConfigEdition);
ResConfigEdition.template = "res_config_edition";
return ResConfigEdition;
});
registry.category("view_widgets").add("res_config_edition", ResConfigEdition);
@@ -1,174 +1,124 @@
odoo.define('base_setup.ResConfigInviteUsers', function (require) {
"use strict";
/** @odoo-module */
var Widget = require('web.Widget');
var widget_registry = require('web.widget_registry');
var core = require('web.core');
import { registry } from "@web/core/registry";
import { _t } from "@web/core/l10n/translation";
import { unique } from "@web/core/utils/arrays";
import { useService } from "@web/core/utils/hooks";
var _t = core._t;
const { Component, useState, onWillStart } = owl;
var ResConfigInviteUsers = Widget.extend({
template: 'res_config_invite_users',
class ResConfigInviteUsers extends Component {
setup() {
this.orm = useService("orm");
this.invite = useService("user_invite");
this.action = useService("action");
this.notification = useService("notification");
events: {
'click .o_web_settings_invite': '_onClickInvite',
'click .o_web_settings_user': '_onClickUser',
'click .o_web_settings_more': '_onClickMore',
'keydown .o_user_emails': '_onKeydownUserEmails',
},
this.state = useState({
status: "idle", // idle, inviting
emails: "",
invite: null,
});
/**
* @override
*/
init: function () {
this._super.apply(this, arguments);
this.emails = [];
},
onWillStart(async () => {
this.state.invite = await this.invite.fetchData();
});
}
willStart: function () {
var self = this;
/**
* @param {string} email
* @returns {boolean} true if the given email address is valid
*/
validateEmail(email) {
const re = /^([a-z0-9][-a-z0-9_\+\.]*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,63}(?:\.[a-z]{2})?)$/i;
return re.test(email);
}
return this._super.apply(this, arguments).then(function () {
return self.load();
});
},
get emails() {
return unique(
this.state.emails
.split(/[ ,;\n]+/)
.map((email) => email.trim())
.filter((email) => email.length)
);
}
load: function () {
var self = this;
return this._rpc({
route: '/base_setup/data',
}).then(function (data) {
self.active_users = data.active_users;
self.pending_users = data.pending_users;
self.action_pending_users = data.action_pending_users;
self.pending_count = data.pending_count;
self.resend_invitation = data.resend_invitation || false;
});
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* @private
* @param {string} email
* @returns {boolean} true if the given email address is valid
*/
_validateEmail: function (email) {
var re = /^([a-z0-9][-a-z0-9_\+\.]*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,63}(?:\.[a-z]{2})?)$/i;
return re.test(email);
},
/**
* Send invitation for valid and unique email addresses
*
* @private
*/
_invite: function () {
var self = this;
var $userEmails = this.$('.o_user_emails');
$userEmails.prop('disabled', true);
this.$('.o_web_settings_invite').prop('disabled', true);
var value = $userEmails.val().trim();
if (value) {
// filter out duplicates
var emails = _.uniq(value.split(/[ ,;\n]+/));
// filter out invalid email addresses
var invalidEmails = _.reject(emails, this._validateEmail);
if (invalidEmails.length) {
this.displayNotification({ message: _.str.sprintf(
_t('Invalid email addresses: %s.'),
invalidEmails.join(', ')
), type: 'danger' });
}
emails = _.difference(emails, invalidEmails);
if (!this.resend_invitation) {
// filter out already processed or pending addresses
var pendingEmails = _.map(this.pending_users, function (info) {
return info[1];
});
var existingEmails = _.intersection(emails, this.emails.concat(pendingEmails));
if (existingEmails.length) {
this.displayNotification({ message: _.str.sprintf(
_t('Email addresses already existing: %s.'),
existingEmails.join(', ')
), type: 'danger' });
}
emails = _.difference(emails, existingEmails);
}
if (emails.length) {
$userEmails.val('');
this._rpc({
model: 'res.users',
method: 'web_create_users',
args: [emails],
}).then(function () {
return self.load().then(function () {
self.renderElement();
self.$('.o_user_emails').focus();
});
});
} else {
$userEmails.prop('disabled', false);
this.$('.o_web_settings_invite').prop('disabled', false);
self.$('.o_user_emails').focus();
}
validate() {
if (!this.emails.length) {
throw new Error(_t("Empty email address"));
}
const invalidEmails = [];
for (const email of this.emails) {
if (!this.validateEmail(email)) {
invalidEmails.push(email);
}
},
}
if (invalidEmails.length) {
throw new Error(`${_t("Invalid email address")}${ invalidEmails.length > 1 ? "es" : "" }: ${invalidEmails.join(", ")}`);
}
}
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
get inviteButtonText() {
if (this.state.status === "inviting") {
return _t("Inviting...");
}
return _t("Invite");
}
/**
* @private
* @param {MouseEvent} ev
*/
_onClickInvite: function (ev) {
if (this.$('.o_user_emails').val().length) {
var $button = $(ev.target);
$button.button('loading');
return this._invite();
onClickMore(ev) {
this.action.doAction(this.state.invite.action_pending_users);
}
onClickUser(ev, user) {
const action = Object.assign({}, this.state.invite.action_pending_users, {
res_id: user[0],
});
this.action.doAction(action);
}
onKeydownUserEmails(ev) {
const keys = ["Enter", "Tab", ","];
if (keys.includes(ev.key)) {
if (ev.key === "Tab" && !this.emails.length) {
return;
}
},
/**
* @private
* @param {MouseEvent} ev
*/
_onClickMore: function (ev) {
ev.preventDefault();
this.do_action(this.action_pending_users);
},
/**
* @private
* @param {MouseEvent} ev
*/
_onClickUser: function (ev) {
ev.preventDefault();
var user_id = $(ev.currentTarget).data('user-id');
this.sendInvite();
}
}
var action = Object.assign({}, this.action_pending_users, {res_id: user_id});
this.do_action(action);
},
/**
* @private
* @param {KeyboardEvent} ev
*/
_onKeydownUserEmails: function (ev) {
var keyCodes = [$.ui.keyCode.TAB, $.ui.keyCode.COMMA, $.ui.keyCode.ENTER];
if (_.contains(keyCodes, ev.which)) {
ev.preventDefault();
this._invite();
/**
* Send invitation for valid and unique email addresses
*
* @private
*/
async sendInvite() {
try {
this.validate();
} catch (e) {
this.notification.add(e.message, { type: "danger" });
return;
}
this.state.status = "inviting";
const pendingUserEmails = this.state.invite.pending_users.map((user) => user[1]);
const emailsLeftToProcess = this.emails.filter(
(email) => !pendingUserEmails.includes(email)
);
try {
if (emailsLeftToProcess) {
await this.orm.call("res.users", "web_create_users", [emailsLeftToProcess]);
this.state.invite = await this.invite.fetchData(true);
}
},
});
} finally {
this.state.emails = "";
this.state.status = "idle";
}
}
}
widget_registry.add('res_config_invite_users', ResConfigInviteUsers);
ResConfigInviteUsers.template = "res_config_invite_users";
});
registry.category("view_widgets").add("res_config_invite_users", ResConfigInviteUsers);
@@ -0,0 +1,20 @@
/** @odoo-modules */
import { registry } from "@web/core/registry";
export const userInviteService = {
dependencies: ["rpc"],
async start(env, { rpc }) {
let dataProm;
return {
fetchData(reload = false) {
if (!dataProm || reload) {
dataProm = rpc("/base_setup/data");
}
return dataProm;
},
};
},
};
registry.category("services").add("user_invite", userInviteService);
@@ -1,16 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<template>
<div t-name='res_config_dev_tool'>
<div t-name='res_config_dev_tool' owl="1">
<div id="developer_tool">
<h2>Developer Tools</h2>
<div class="row mt16 o_settings_container">
<div class="col-12 col-lg-6 o_setting_box" id="devel_tool">
<div class="o_setting_right_pane">
<a t-if="!widget.isDebug" class="d-block" href="?debug=1">Activate the developer mode</a>
<a t-if="!widget.isAssets" class="d-block" href="?debug=assets">Activate the developer mode (with assets)</a>
<a t-if="!widget.isTests" class="d-block" href="?debug=assets,tests">Activate the developer mode (with tests assets)</a>
<a t-if="widget.isDebug" class="d-block" href="?debug=">Deactivate the developer mode</a>
<a t-if="widget.isDebug and !widget.demo_active" class="o_web_settings_force_demo" href="#">Load demo data</a>
<a t-if="!isDebug" class="d-block" href="?debug=1">Activate the developer mode</a>
<a t-if="!isAssets" class="d-block" href="?debug=assets">Activate the developer mode (with assets)</a>
<a t-if="!isTests" class="d-block" href="?debug=assets,tests">Activate the developer mode (with tests assets)</a>
<a t-if="isDebug" class="d-block" href="?debug=">Deactivate the developer mode</a>
<a t-if="isDebug and !isDemoDataActive" t-on-click.prevent="onClickForceDemo" class="o_web_settings_force_demo" href="#">Load demo data</a>
</div>
</div>
</div>
@@ -1,13 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- TODO See if this has to be editable in some way, see if it is easy for someone to change this -->
<!-- TODO Also see what is done to override the (Community Edition) -->
<template>
<div t-name='res_config_edition'>
<div t-name='res_config_edition' owl="1">
<div class="col-12 o_setting_box" id="edition">
<div class="o_setting_right_pane">
<div class="user-heading">
<h3 class="px-0">
Odoo <t t-esc="widget.server_version"/>
Odoo <t t-esc="serverVersion"/>
(Community Edition)
</h3>
</div>
@@ -1,20 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<templates>
<div t-name='res_config_invite_users'>
<div t-name='res_config_invite_users' owl="1">
<p class="o_form_label">Invite New Users</p>
<div class="d-flex">
<input class="o_user_emails o_input mt8" type="text" placeholder="Enter e-mail address"/>
<button class="btn btn-primary o_web_settings_invite" data-loading-text="Inviting..."><strong>Invite</strong></button>
</div>
<t t-if="widget.pending_users.length">
<div class="d-flex">
<input t-model="state.emails" t-att-disabled="state.status != 'idle'" class="o_user_emails o_input mt8" t-on-keydown="onKeydownUserEmails" type="text" placeholder="Enter e-mail address"/>
<button t-att-disabled="state.status != 'idle'" class="btn btn-primary o_web_settings_invite" t-on-click="sendInvite"><strong><t t-esc="inviteButtonText"/></strong></button>
</div>
<t t-if="state.invite.pending_users.length">
<p class="o_form_label pt-3">Pending Invitations:</p>
<span t-foreach="widget.pending_users" t-as="pending">
<a href="#" class="badge rounded-pill text-primary o_web_settings_user" t-att-data-user-id="pending[0]"> <t t-esc="pending[1]"/> </a>
<span t-foreach="state.invite.pending_users" t-as="pending" t-key="pending[0]">
<a href="#" class="badge rounded-pill text-primary o_web_settings_user" t-on-click.prevent="(ev) => this.onClickUser(ev, pending)"> <t t-esc="pending[1]"/> </a>
</span>
<t t-if="widget.pending_users.length &lt; widget.pending_count">
<t t-if="state.invite.pending_users.length &lt; state.invite.pending_count">
<br/>
<a href="#" class="o_web_settings_more"><t t-esc="widget.pending_count - widget.pending_users.length"/> more </a>
<a href="#" class="o_web_settings_more" t-on-click.prevent="onClickMore"><t t-esc="state.invite.pending_count - state.invite.pending_users.length"/> more </a>
</t>
</t>
</div>
+3
View File
@@ -66,3 +66,6 @@ web_cohort/static/tests/legacy/**/*
# blacklist legacy
web_map/static/src/legacy/**/*
web_map/static/tests/legacy/**/*
# whitelist base_setup
!base_setup/static/src/**/*
+3
View File
@@ -66,3 +66,6 @@ web_cohort/static/tests/legacy/**/*
# blacklist legacy
web_map/static/src/legacy/**/*
web_map/static/tests/legacy/**/*
# whitelist base_setup
!base_setup/static/src/**/*