[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:
@@ -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 < widget.pending_count">
|
||||
<t t-if="state.invite.pending_users.length < 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>
|
||||
|
||||
@@ -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/**/*
|
||||
|
||||
@@ -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/**/*
|
||||
|
||||
Reference in New Issue
Block a user