[IMP] web,project: add and modify commands in the palette

\- Add 'Move to next stage'
Generic, we think it is a useful tool to have anywhere.

\- Change the 'Set priority...' command
to 'Set priority as Low/High' :
Only 1 command will be available with the 'not current' priority.
Specific, because it is used on selection fields,
which means there could be more than 2 options.

\- Transform the 'Set kanban state...' command into three commands
Generic. 2 commands will be available.
One for each 'not current' kanban state.

task-2857447

closes odoo/odoo#91821

Signed-off-by: Laurent Stukkens (ltu) <ltu@odoo.com>
This commit is contained in:
Audric Onockx (auon)
2022-11-14 17:17:47 +01:00
parent 1c5fe423f2
commit 76b513917b
5 changed files with 102 additions and 41 deletions
@@ -0,0 +1,32 @@
/** @odoo-module **/
import { sprintf } from "@web/core/utils/strings";
import { registry } from "@web/core/registry";
import { PriorityField } from "@web/views/fields/priority/priority_field";
import { useCommand } from "@web/core/commands/command_hook";
import { useState } from "@odoo/owl";
export class PrioritySwitchField extends PriorityField {
setup() {
this.state = useState({
index: -1,
});
if (this.props.record.activeFields[this.props.name].viewType !== "form") {
return;
}
for (const [id, name] of this.options) {
useCommand(
sprintf(this.env._t("Set priority as %s"), name),
() => this.props.update(id),
{
category: "smart_action",
hotkey: "alt+r",
isAvailable: () => this.props.value !== id,
}
);
}
}
}
registry.category("fields").add("priority_switch", PrioritySwitchField);
+2 -2
View File
@@ -1090,7 +1090,7 @@
<button name="action_assign_to_me" string="Assign to Me" type="object" attrs="{'invisible': &quot;['|', ('user_ids', 'in', uid), ('user_ids', '=', [])]&quot;}" data-hotkey="q"/>
<button name="action_assign_to_me" string="Assign to Me" type="object" class="oe_highlight" attrs="{'invisible' : &quot;['|', ('user_ids', 'in', uid), ('user_ids', '!=', [])]&quot;}" data-hotkey="q"/>
<field name="stage_id" widget="statusbar" options="{'clickable': '1', 'fold_field': 'fold'}" attrs="{'invisible': [('project_id', '=', False), ('stage_id', '=', False)]}"/>
<field name="personal_stage_type_id" widget="statusbar" options="{'clickable': '1', 'fold_field': 'fold'}" attrs="{'invisible': [('project_id', '!=', False)]}" domain="[('user_id', '=', uid)]"/>
<field name="personal_stage_type_id" widget="statusbar" options="{'clickable': '1', 'fold_field': 'fold'}" attrs="{'invisible': [('project_id', '!=', False)]}" domain="[('user_id', '=', uid)]" string="Personal Stage"/>
</header>
<div class="alert alert-info oe_edit_only mb-0" role="status" attrs="{'invisible': ['|', ('recurring_task', '=', False), ('recurrence_id', '=', False)]}" groups="project.group_project_recurring_tasks">
<p>Edit recurring task</p>
@@ -1151,7 +1151,7 @@
<div class="oe_title pe-0">
<h1 class="d-flex justify-content-between align-items-center">
<div class="d-flex w-100">
<field name="priority" widget="priority" class="me-3"/>
<field name="priority" widget="priority_switch" class="me-3"/>
<field name="name" class="o_task_name text-truncate" placeholder="Task Title..."/>
</div>
<field name="kanban_state" widget="state_selection" class=""/>
@@ -4,42 +4,34 @@ import { useCommand } from "@web/core/commands/command_hook";
import { Dropdown } from "@web/core/dropdown/dropdown";
import { DropdownItem } from "@web/core/dropdown/dropdown_item";
import { registry } from "@web/core/registry";
import { sprintf } from "@web/core/utils/strings";
import { _lt } from "@web/core/l10n/translation";
import { standardFieldProps } from "../standard_field_props";
import { formatSelection } from "../formatters";
import { Component } from "@odoo/owl";
export class StateSelectionField extends Component {
setup() {
if (this.props.record.activeFields[this.props.name].viewType === "form") {
const commandName = this.env._t("Set kanban state...");
useCommand(
commandName,
() => {
return {
placeholder: commandName,
providers: [
{
provide: () =>
this.options.map((value) => ({
name: value[1],
action: () => {
this.props.update(value[0]);
},
})),
},
],
};
},
{ category: "smart_action", hotkey: "alt+shift+r" }
);
}
this.colorPrefix = "o_status_";
this.colors = {
blocked: "red",
done: "green",
};
if (this.props.record.activeFields[this.props.name].viewType !== "form") {
return;
}
const hotkeys = ["D", "F", "G"];
for (const [index, [value, label]] of this.options.entries()) {
useCommand(
sprintf(this.env._t("Set kanban state as %s"), label),
() => this.props.update(value),
{
category: "smart_action",
hotkey: "alt+" + hotkeys[index],
isAvailable: () => this.props.value !== value,
}
);
}
}
get options() {
return this.props.record.fields[this.props.name].selection.map(([state, label]) => {
@@ -9,7 +9,6 @@ import { escape, sprintf } from "@web/core/utils/strings";
import { Domain } from "@web/core/domain";
import { _lt } from "@web/core/l10n/translation";
import { standardFieldProps } from "../standard_field_props";
import { Component } from "@odoo/owl";
export class StatusBarField extends Component {
@@ -27,7 +26,7 @@ export class StatusBarField extends Component {
providers: [
{
provide: () =>
this.computeItems().unfolded.map((value) => ({
this.computeItems(false).map((value) => ({
name: value.name,
action: () => {
this.selectItem(value);
@@ -39,11 +38,39 @@ export class StatusBarField extends Component {
},
{ category: "smart_action", hotkey: "alt+shift+x" }
);
useCommand(
sprintf(this.env._t(`Move to next %s`), this.props.displayName),
() => {
const options = this.computeItems(false);
const nextOption =
options[
options.findIndex(
(option) =>
option.id ===
(this.type === "many2one"
? this.props.value[0]
: this.props.value)
) + 1
];
this.selectItem(nextOption);
},
{
category: "smart_action",
hotkey: "alt+x",
isAvailable: () => {
const options = this.computeItems(false);
return (
options[options.length - 1].id !==
(this.type === "many2one" ? this.props.value[0] : this.props.value)
);
},
}
);
}
}
get currentName() {
switch (this.props.record.fields[this.props.name].type) {
switch (this.type) {
case "many2one": {
const item = this.options.find((item) => this.props.value && item.id === this.props.value[0]);
return item ? item.display_name : "";
@@ -56,7 +83,7 @@ export class StatusBarField extends Component {
throw new Error("Unsupported field type for StatusBarField");
}
get options() {
switch (this.props.record.fields[this.props.name].type) {
switch (this.type) {
case "many2one":
return this.props.record.preloadedData[this.props.name];
case "selection":
@@ -66,6 +93,10 @@ export class StatusBarField extends Component {
}
}
get type() {
return this.props.record.fields[this.props.name].type;
}
getDropdownItemClassNames(item) {
const classNames = [
"btn",
@@ -110,13 +141,16 @@ export class StatusBarField extends Component {
}));
}
computeItems() {
computeItems(grouped = true) {
let items = null;
if (this.props.type === "many2one") {
items = this.getVisibleMany2Ones();
} else {
items = this.getVisibleSelection();
}
if (!grouped) {
return items;
}
if (this.env.isSmall) {
return {
@@ -431,7 +431,7 @@ QUnit.module("Fields", (hooks) => {
});
QUnit.test(
'StateSelectionField edited by the smart action "Set kanban state..."',
'StateSelectionField edited by the smart actions "Set kanban state as <state name>"',
async function (assert) {
await makeView({
type: "form",
@@ -448,20 +448,23 @@ QUnit.module("Fields", (hooks) => {
triggerHotkey("control+k");
await nextTick();
const idx = [...target.querySelectorAll(".o_command")]
.map((el) => el.textContent)
.indexOf("Set kanban state...ALT + SHIFT + R");
var commandTexts = [...target.querySelectorAll(".o_command")].map(
(el) => el.textContent
);
assert.ok(commandTexts.includes("Set kanban state as NormalALT + D"));
const idx = commandTexts.indexOf("Set kanban state as DoneALT + G");
assert.ok(idx >= 0);
await click([...target.querySelectorAll(".o_command")][idx]);
await nextTick();
assert.deepEqual(
[...target.querySelectorAll(".o_command")].map((el) => el.textContent),
["Normal", "Blocked", "Done"]
);
await click(target, "#o_command_2");
await nextTick();
assert.containsOnce(target, ".o_status_green");
triggerHotkey("control+k");
await nextTick();
commandTexts = [...target.querySelectorAll(".o_command")].map((el) => el.textContent);
assert.ok(commandTexts.includes("Set kanban state as NormalALT + D"));
assert.ok(commandTexts.includes("Set kanban state as BlockedALT + F"));
assert.notOk(commandTexts.includes("Set kanban state as DoneALT + G"));
}
);