[REF] web: debug manager
We refactor some items in the debug registry. In particular, we want to make them usable in the legacy and new contexts. Part-of: odoo/odoo#73311
This commit is contained in:
committed by
Aaron Bohy
parent
c67b9f9907
commit
e0294887ec
@@ -1,4 +1,5 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registry } from "../registry";
|
||||
import { memoize } from "../utils/functions";
|
||||
import { useEffect } from "@web/core/utils/hooks";
|
||||
@@ -78,9 +79,12 @@ export function useEnvDebugContext() {
|
||||
}
|
||||
|
||||
export function useDebugCategory(category, context = {}) {
|
||||
const debugContext = useEnvDebugContext();
|
||||
useEffect(
|
||||
() => debugContext.activateCategory(category, context),
|
||||
() => []
|
||||
);
|
||||
const env = useEnv();
|
||||
if (env.debug) {
|
||||
const debugContext = useEnvDebugContext();
|
||||
useEffect(
|
||||
() => debugContext.activateCategory(category, context),
|
||||
() => []
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { browser } from "../browser/browser";
|
||||
import { routeToUrl } from "../browser/router_service";
|
||||
import { registry } from "../registry";
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import { routeToUrl } from "@web/core/browser/router_service";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
function activateAssetsDebugging({ env }) {
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
export function editModelDebug(env, title, model, id) {
|
||||
return env.services.action.doAction({
|
||||
res_model: model,
|
||||
res_id: id,
|
||||
name: title,
|
||||
type: "ir.actions.act_window",
|
||||
views: [[false, "form"]],
|
||||
view_mode: "form",
|
||||
target: "new",
|
||||
flags: { action_buttons: true, headless: true },
|
||||
});
|
||||
}
|
||||
@@ -1,212 +1,15 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { Dialog } from "../core/dialog/dialog";
|
||||
import { formatDateTime, parseDateTime } from "../core/l10n/dates";
|
||||
import { _lt } from "../core/l10n/translation";
|
||||
import { registry } from "../core/registry";
|
||||
import { _lt } from "@web/core/l10n/translation";
|
||||
import { Dialog } from "@web/core/dialog/dialog";
|
||||
import { formatDateTime, parseDateTime } from "@web/core/l10n/dates";
|
||||
import { formatMany2one } from "@web/fields/formatters";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
const { hooks, tags } = owl;
|
||||
const { hooks } = owl;
|
||||
const { useState } = hooks;
|
||||
|
||||
function editModelDebug(env, title, model, id) {
|
||||
return env.services.action.doAction({
|
||||
res_model: model,
|
||||
res_id: id,
|
||||
name: title,
|
||||
type: "ir.actions.act_window",
|
||||
views: [[false, "form"]],
|
||||
view_mode: "form",
|
||||
target: "new",
|
||||
flags: { action_buttons: true, headless: true },
|
||||
});
|
||||
}
|
||||
|
||||
// Action items
|
||||
|
||||
function actionSeparator() {
|
||||
return {
|
||||
type: "separator",
|
||||
sequence: 100,
|
||||
};
|
||||
}
|
||||
|
||||
function accessSeparator({ accessRights, action }) {
|
||||
const { canSeeModelAccess, canSeeRecordRules } = accessRights;
|
||||
if (!action.res_model || (!canSeeModelAccess && !canSeeRecordRules)) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
type: "separator",
|
||||
sequence: 200,
|
||||
};
|
||||
}
|
||||
|
||||
function editAction({ action, env }) {
|
||||
if (!action.id) {
|
||||
return null;
|
||||
}
|
||||
const description = env._t("Edit Action");
|
||||
return {
|
||||
type: "item",
|
||||
description,
|
||||
callback: () => {
|
||||
editModelDebug(env, description, action.type, action.id);
|
||||
},
|
||||
sequence: 110,
|
||||
};
|
||||
}
|
||||
|
||||
function viewFields({ action, env }) {
|
||||
if (!action.res_model) {
|
||||
return null;
|
||||
}
|
||||
const description = env._t("View Fields");
|
||||
return {
|
||||
type: "item",
|
||||
description,
|
||||
callback: async () => {
|
||||
const modelId = (
|
||||
await env.services.orm.search("ir.model", [["model", "=", action.res_model]], {
|
||||
limit: 1,
|
||||
})
|
||||
)[0];
|
||||
env.services.action.doAction({
|
||||
res_model: "ir.model.fields",
|
||||
name: description,
|
||||
views: [
|
||||
[false, "list"],
|
||||
[false, "form"],
|
||||
],
|
||||
domain: [["model_id", "=", modelId]],
|
||||
type: "ir.actions.act_window",
|
||||
context: {
|
||||
default_model_id: modelId,
|
||||
},
|
||||
});
|
||||
},
|
||||
sequence: 120,
|
||||
};
|
||||
}
|
||||
|
||||
function manageFilters({ action, env }) {
|
||||
if (!action.res_model) {
|
||||
return null;
|
||||
}
|
||||
const description = env._t("Manage Filters");
|
||||
return {
|
||||
type: "item",
|
||||
description,
|
||||
callback: () => {
|
||||
// manage_filters
|
||||
env.services.action.doAction({
|
||||
res_model: "ir.filters",
|
||||
name: description,
|
||||
views: [
|
||||
[false, "list"],
|
||||
[false, "form"],
|
||||
],
|
||||
type: "ir.actions.act_window",
|
||||
context: {
|
||||
search_default_my_filters: true,
|
||||
search_default_model_id: action.res_model,
|
||||
},
|
||||
});
|
||||
},
|
||||
sequence: 130,
|
||||
};
|
||||
}
|
||||
|
||||
function technicalTranslation({ action, env }) {
|
||||
if (!action.res_model) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
type: "item",
|
||||
description: env._t("Technical Translation"),
|
||||
callback: async () => {
|
||||
const result = await env.services.orm.call(
|
||||
"ir.translation",
|
||||
"get_technical_translations",
|
||||
[action.res_model]
|
||||
);
|
||||
env.services.action.doAction(result);
|
||||
},
|
||||
sequence: 140,
|
||||
};
|
||||
}
|
||||
|
||||
function viewAccessRights({ accessRights, action, env }) {
|
||||
if (!action.res_model || !accessRights.canSeeModelAccess) {
|
||||
return null;
|
||||
}
|
||||
const description = env._t("View Access Rights");
|
||||
return {
|
||||
type: "item",
|
||||
description,
|
||||
callback: async () => {
|
||||
const modelId = (
|
||||
await env.services.orm.search("ir.model", [["model", "=", action.res_model]], {
|
||||
limit: 1,
|
||||
})
|
||||
)[0];
|
||||
env.services.action.doAction({
|
||||
res_model: "ir.model.access",
|
||||
name: description,
|
||||
views: [
|
||||
[false, "list"],
|
||||
[false, "form"],
|
||||
],
|
||||
domain: [["model_id", "=", modelId]],
|
||||
type: "ir.actions.act_window",
|
||||
context: {
|
||||
default_model_id: modelId,
|
||||
},
|
||||
});
|
||||
},
|
||||
sequence: 210,
|
||||
};
|
||||
}
|
||||
|
||||
function viewRecordRules({ accessRights, action, env }) {
|
||||
if (!action.res_model || !accessRights.canSeeRecordRules) {
|
||||
return null;
|
||||
}
|
||||
const description = env._t("Model Record Rules");
|
||||
return {
|
||||
type: "item",
|
||||
description: env._t("View Record Rules"),
|
||||
callback: async () => {
|
||||
const modelId = (
|
||||
await env.services.orm.search("ir.model", [["model", "=", action.res_model]], {
|
||||
limit: 1,
|
||||
})
|
||||
)[0];
|
||||
env.services.action.doAction({
|
||||
res_model: "ir.rule",
|
||||
name: description,
|
||||
views: [
|
||||
[false, "list"],
|
||||
[false, "form"],
|
||||
],
|
||||
domain: [["model_id", "=", modelId]],
|
||||
type: "ir.actions.act_window",
|
||||
context: {
|
||||
default_model_id: modelId,
|
||||
},
|
||||
});
|
||||
},
|
||||
sequence: 220,
|
||||
};
|
||||
}
|
||||
|
||||
class FieldViewGetDialog extends Dialog {}
|
||||
FieldViewGetDialog.props = Object.assign({}, Dialog.props, {
|
||||
arch: { type: String },
|
||||
close: Function,
|
||||
});
|
||||
FieldViewGetDialog.bodyTemplate = tags.xml`<pre t-esc="props.arch"/>`;
|
||||
FieldViewGetDialog.title = _lt("Fields View Get");
|
||||
const debugRegistry = registry.category("debug");
|
||||
|
||||
class GetMetadataDialog extends Dialog {
|
||||
setup() {
|
||||
@@ -243,7 +46,6 @@ class GetMetadataDialog extends Dialog {
|
||||
}
|
||||
GetMetadataDialog.bodyTemplate = "web.DebugMenu.getMetadataBody";
|
||||
GetMetadataDialog.title = _lt("View Metadata");
|
||||
|
||||
class SetDefaultDialog extends Dialog {
|
||||
setup() {
|
||||
super.setup();
|
||||
@@ -389,66 +191,7 @@ SetDefaultDialog.bodyTemplate = "web.DebugMenu.setDefaultBody";
|
||||
SetDefaultDialog.footerTemplate = "web.DebugMenu.SetDefaultFooter";
|
||||
SetDefaultDialog.title = _lt("Set Default");
|
||||
|
||||
function viewSeparator() {
|
||||
return {
|
||||
type: "separator",
|
||||
sequence: 300,
|
||||
};
|
||||
}
|
||||
|
||||
function fieldsViewGet({ component, env }) {
|
||||
return {
|
||||
type: "item",
|
||||
description: env._t("Fields View Get"),
|
||||
callback: () => {
|
||||
const props = {
|
||||
arch: component.props.viewInfo.arch,
|
||||
};
|
||||
env.services.dialog.add(FieldViewGetDialog, props);
|
||||
},
|
||||
sequence: 340,
|
||||
};
|
||||
}
|
||||
|
||||
export function editView({ accessRights, action, component, env }) {
|
||||
if (!accessRights.canEditView) {
|
||||
return null;
|
||||
}
|
||||
let { view_id: viewId, type: viewType } = component.props.viewInfo;
|
||||
viewType = viewType === "tree" ? "list" : viewType;
|
||||
const displayName = action.views.find((v) => v.type === viewType).name.toString();
|
||||
const description = env._t("Edit View: ") + displayName;
|
||||
return {
|
||||
type: "item",
|
||||
description,
|
||||
callback: () => {
|
||||
editModelDebug(env, description, "ir.ui.view", viewId);
|
||||
},
|
||||
sequence: 350,
|
||||
};
|
||||
}
|
||||
|
||||
export function editSearchView({ accessRights, component, env }) {
|
||||
if (!accessRights.canEditView) {
|
||||
return null;
|
||||
}
|
||||
const description = env._t("Edit SearchView");
|
||||
return {
|
||||
type: "item",
|
||||
description,
|
||||
callback: () => {
|
||||
editModelDebug(
|
||||
env,
|
||||
description,
|
||||
"ir.ui.view",
|
||||
component.props.viewParams.controlPanelFieldsView.view_id
|
||||
);
|
||||
},
|
||||
sequence: 360,
|
||||
};
|
||||
}
|
||||
|
||||
// Form view itemss
|
||||
// Form view items
|
||||
|
||||
function setDefaults({ action, component, env }) {
|
||||
return {
|
||||
@@ -515,26 +258,6 @@ function manageAttachments({ action, component, env }) {
|
||||
};
|
||||
}
|
||||
|
||||
const debugRegistry = registry.category("debug");
|
||||
|
||||
debugRegistry
|
||||
.category("action")
|
||||
.add("actionSeparator", actionSeparator)
|
||||
.add("editAction", editAction)
|
||||
.add("viewFields", viewFields)
|
||||
.add("manageFilters", manageFilters)
|
||||
.add("technicalTranslation", technicalTranslation)
|
||||
.add("accessSeparator", accessSeparator)
|
||||
.add("viewAccessRights", viewAccessRights)
|
||||
.add("viewRecordRules", viewRecordRules);
|
||||
|
||||
debugRegistry
|
||||
.category("view")
|
||||
.add("viewSeparator", viewSeparator)
|
||||
.add("fieldsViewGet", fieldsViewGet)
|
||||
.add("editView", editView)
|
||||
.add("editSearchView", editSearchView);
|
||||
|
||||
debugRegistry
|
||||
.category("form")
|
||||
.add("setDefaults", setDefaults)
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { _lt } from "@web/core/l10n/translation";
|
||||
import { Dialog } from "@web/core/dialog/dialog";
|
||||
import { editModelDebug } from "@web/core/debug/debug_utils";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
const { tags } = owl;
|
||||
|
||||
const debugRegistry = registry.category("debug");
|
||||
|
||||
class FieldViewGetDialog extends Dialog {}
|
||||
FieldViewGetDialog.props = Object.assign({}, Dialog.props, {
|
||||
arch: { type: String },
|
||||
close: Function,
|
||||
});
|
||||
FieldViewGetDialog.bodyTemplate = tags.xml`<pre t-esc="props.arch"/>`;
|
||||
FieldViewGetDialog.title = _lt("Fields View Get");
|
||||
|
||||
function viewSeparator() {
|
||||
return {
|
||||
type: "separator",
|
||||
sequence: 300,
|
||||
};
|
||||
}
|
||||
|
||||
function fieldsViewGet({ component, env }) {
|
||||
let { arch } = component.props;
|
||||
if ("viewInfo" in component.props) {
|
||||
//legacy
|
||||
arch = component.props.viewInfo.arch;
|
||||
}
|
||||
return {
|
||||
type: "item",
|
||||
description: env._t("Fields View Get"),
|
||||
callback: () => {
|
||||
env.services.dialog.add(FieldViewGetDialog, { arch });
|
||||
},
|
||||
sequence: 340,
|
||||
};
|
||||
}
|
||||
|
||||
export function editView({ accessRights, component, env }) {
|
||||
if (!accessRights.canEditView) {
|
||||
return null;
|
||||
}
|
||||
let type;
|
||||
let { viewId } = component.props.info || {}; // fallback is there for legacy
|
||||
if ("viewInfo" in component.props) {
|
||||
// legacy
|
||||
viewId = component.props.viewInfo.view_id;
|
||||
type = component.props.viewInfo.type;
|
||||
type = type === "tree" ? "list" : type;
|
||||
} else {
|
||||
type = component.constructor.type;
|
||||
}
|
||||
const displayName = type[0].toUpperCase() + type.slice(1);
|
||||
const description = env._t("Edit View: ") + displayName;
|
||||
return {
|
||||
type: "item",
|
||||
description,
|
||||
callback: () => {
|
||||
editModelDebug(env, description, "ir.ui.view", viewId);
|
||||
},
|
||||
sequence: 350,
|
||||
};
|
||||
}
|
||||
|
||||
export function editSearchView({ accessRights, component, env }) {
|
||||
if (!accessRights.canEditView) {
|
||||
return null;
|
||||
}
|
||||
let { searchViewId } = component.props.info || {}; // fallback is there for legacy
|
||||
if ("viewParams" in component.props) {
|
||||
//legacy
|
||||
searchViewId = component.props.viewParams.action.controlPanelFieldsView.view_id;
|
||||
}
|
||||
const description = env._t("Edit ControlPanelView");
|
||||
return {
|
||||
type: "item",
|
||||
description,
|
||||
callback: () => {
|
||||
editModelDebug(env, description, "ir.ui.view", searchViewId);
|
||||
},
|
||||
sequence: 360,
|
||||
};
|
||||
}
|
||||
|
||||
debugRegistry
|
||||
.category("view")
|
||||
.add("viewSeparator", viewSeparator)
|
||||
.add("fieldsViewGet", fieldsViewGet)
|
||||
.add("editView", editView)
|
||||
.add("editSearchView", editSearchView);
|
||||
@@ -9,6 +9,7 @@ import { evaluateExpr } from "@web/core/py_js/py";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { KeepLast } from "@web/core/utils/concurrency";
|
||||
import { sprintf } from "@web/core/utils/strings";
|
||||
import { useDebugCategory } from "@web/core/debug/debug_context";
|
||||
import { ActionDialog } from "./action_dialog";
|
||||
|
||||
/** @typedef {number|false} ActionId */
|
||||
@@ -465,7 +466,7 @@ function makeActionManager(env) {
|
||||
setup() {
|
||||
this.Component = controller.Component;
|
||||
this.componentRef = hooks.useRef("component");
|
||||
this.registerCallback = null;
|
||||
useDebugCategory("action", { action });
|
||||
if (action.target !== "new") {
|
||||
let beforeLeaveFn;
|
||||
this.registerCallback = (type, fn) => {
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { editModelDebug } from "@web/core/debug/debug_utils";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
const debugRegistry = registry.category("debug");
|
||||
|
||||
function actionSeparator({ action }) {
|
||||
if (!action.id || !action.res_model) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
type: "separator",
|
||||
sequence: 100,
|
||||
};
|
||||
}
|
||||
|
||||
function accessSeparator({ accessRights, action }) {
|
||||
const { canSeeModelAccess, canSeeRecordRules } = accessRights;
|
||||
if (!action.res_model || (!canSeeModelAccess && !canSeeRecordRules)) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
type: "separator",
|
||||
sequence: 200,
|
||||
};
|
||||
}
|
||||
|
||||
function editAction({ action, env }) {
|
||||
if (!action.id) {
|
||||
return null;
|
||||
}
|
||||
const description = env._t("Edit Action");
|
||||
return {
|
||||
type: "item",
|
||||
description,
|
||||
callback: () => {
|
||||
editModelDebug(env, description, action.type, action.id);
|
||||
},
|
||||
sequence: 110,
|
||||
};
|
||||
}
|
||||
|
||||
function viewFields({ action, env }) {
|
||||
if (!action.res_model) {
|
||||
return null;
|
||||
}
|
||||
const description = env._t("View Fields");
|
||||
return {
|
||||
type: "item",
|
||||
description,
|
||||
callback: async () => {
|
||||
const modelId = (
|
||||
await env.services.orm.search("ir.model", [["model", "=", action.res_model]], {
|
||||
limit: 1,
|
||||
})
|
||||
)[0];
|
||||
env.services.action.doAction({
|
||||
res_model: "ir.model.fields",
|
||||
name: description,
|
||||
views: [
|
||||
[false, "list"],
|
||||
[false, "form"],
|
||||
],
|
||||
domain: [["model_id", "=", modelId]],
|
||||
type: "ir.actions.act_window",
|
||||
context: {
|
||||
default_model_id: modelId,
|
||||
},
|
||||
});
|
||||
},
|
||||
sequence: 120,
|
||||
};
|
||||
}
|
||||
|
||||
function manageFilters({ action, env }) {
|
||||
if (!action.res_model) {
|
||||
return null;
|
||||
}
|
||||
const description = env._t("Manage Filters");
|
||||
return {
|
||||
type: "item",
|
||||
description,
|
||||
callback: () => {
|
||||
// manage_filters
|
||||
env.services.action.doAction({
|
||||
res_model: "ir.filters",
|
||||
name: description,
|
||||
views: [
|
||||
[false, "list"],
|
||||
[false, "form"],
|
||||
],
|
||||
type: "ir.actions.act_window",
|
||||
context: {
|
||||
search_default_my_filters: true,
|
||||
search_default_model_id: action.res_model,
|
||||
},
|
||||
});
|
||||
},
|
||||
sequence: 130,
|
||||
};
|
||||
}
|
||||
|
||||
function technicalTranslation({ action, env }) {
|
||||
if (!action.res_model) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
type: "item",
|
||||
description: env._t("Technical Translation"),
|
||||
callback: async () => {
|
||||
const result = await env.services.orm.call(
|
||||
"ir.translation",
|
||||
"get_technical_translations",
|
||||
[action.res_model]
|
||||
);
|
||||
env.services.action.doAction(result);
|
||||
},
|
||||
sequence: 140,
|
||||
};
|
||||
}
|
||||
|
||||
function viewAccessRights({ accessRights, action, env }) {
|
||||
if (!action.res_model || !accessRights.canSeeModelAccess) {
|
||||
return null;
|
||||
}
|
||||
const description = env._t("View Access Rights");
|
||||
return {
|
||||
type: "item",
|
||||
description,
|
||||
callback: async () => {
|
||||
const modelId = (
|
||||
await env.services.orm.search("ir.model", [["model", "=", action.res_model]], {
|
||||
limit: 1,
|
||||
})
|
||||
)[0];
|
||||
env.services.action.doAction({
|
||||
res_model: "ir.model.access",
|
||||
name: description,
|
||||
views: [
|
||||
[false, "list"],
|
||||
[false, "form"],
|
||||
],
|
||||
domain: [["model_id", "=", modelId]],
|
||||
type: "ir.actions.act_window",
|
||||
context: {
|
||||
default_model_id: modelId,
|
||||
},
|
||||
});
|
||||
},
|
||||
sequence: 210,
|
||||
};
|
||||
}
|
||||
|
||||
function viewRecordRules({ accessRights, action, env }) {
|
||||
if (!action.res_model || !accessRights.canSeeRecordRules) {
|
||||
return null;
|
||||
}
|
||||
const description = env._t("Model Record Rules");
|
||||
return {
|
||||
type: "item",
|
||||
description: env._t("View Record Rules"),
|
||||
callback: async () => {
|
||||
const modelId = (
|
||||
await env.services.orm.search("ir.model", [["model", "=", action.res_model]], {
|
||||
limit: 1,
|
||||
})
|
||||
)[0];
|
||||
env.services.action.doAction({
|
||||
res_model: "ir.rule",
|
||||
name: description,
|
||||
views: [
|
||||
[false, "list"],
|
||||
[false, "form"],
|
||||
],
|
||||
domain: [["model_id", "=", modelId]],
|
||||
type: "ir.actions.act_window",
|
||||
context: {
|
||||
default_model_id: modelId,
|
||||
},
|
||||
});
|
||||
},
|
||||
sequence: 220,
|
||||
};
|
||||
}
|
||||
|
||||
debugRegistry
|
||||
.category("action")
|
||||
.add("actionSeparator", actionSeparator)
|
||||
.add("editAction", editAction)
|
||||
.add("viewFields", viewFields)
|
||||
.add("manageFilters", manageFilters)
|
||||
.add("technicalTranslation", technicalTranslation)
|
||||
.add("accessSeparator", accessSeparator)
|
||||
.add("viewAccessRights", viewAccessRights)
|
||||
.add("viewRecordRules", viewRecordRules);
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
import { click, getFixture, legacyExtraNextTick, patchWithCleanup } from "../../helpers/utils";
|
||||
import { createWebClient, doAction, getActionManagerServerData } from "../../webclient/helpers";
|
||||
import { openViewItem } from "@web/webclient/debug_items";
|
||||
import { editView, editSearchView } from "@web/legacy/debug_manager";
|
||||
import { editSearchView, editView } from "@web/views/debug_items";
|
||||
|
||||
const { Component, mount, tags } = owl;
|
||||
const { xml } = tags;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { editView } from "@web/legacy/debug_manager";
|
||||
import { editView } from "@web/views/debug_items";
|
||||
import { clearUncommittedChanges } from "@web/webclient/actions/action_service";
|
||||
import AbstractView from "web.AbstractView";
|
||||
import FormView from "web.FormView";
|
||||
|
||||
Reference in New Issue
Block a user