mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[REF] devtools: linting and context menu rework
This commit cleans up linting issues and proposes a new implementation for the devtools custom context menus.
This commit is contained in:
committed by
Géry Debongnie
parent
5ecb4809ff
commit
aff4019cde
@@ -0,0 +1,36 @@
|
||||
import { useStore } from "../store/store";
|
||||
|
||||
const { Component, useEffect, useRef } = owl;
|
||||
|
||||
export class ContextMenu extends Component {
|
||||
static template = "devtools.ContextMenu";
|
||||
static props = {
|
||||
items: Array,
|
||||
};
|
||||
setup() {
|
||||
this.store = useStore();
|
||||
this.contextMenu = useRef("contextmenu");
|
||||
useEffect(
|
||||
(position) => {
|
||||
const menu = this.contextMenu.el;
|
||||
const menuWidth = menu.offsetWidth;
|
||||
const menuHeight = menu.offsetHeight;
|
||||
let { x, y } = position;
|
||||
if (x + menuWidth > window.innerWidth) {
|
||||
x = window.innerWidth - menuWidth;
|
||||
}
|
||||
if (y + menuHeight > window.innerHeight) {
|
||||
y = window.innerHeight - menuHeight;
|
||||
}
|
||||
menu.style.left = x + "px";
|
||||
// Need 25px offset because of the main navbar from the browser devtools
|
||||
menu.style.top = y + "px";
|
||||
},
|
||||
() => [this.store.contextMenu?.position]
|
||||
);
|
||||
}
|
||||
onClickItem(action) {
|
||||
action();
|
||||
this.store.contextMenu = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="devtools.ContextMenu">
|
||||
<div class="custom-menu" t-ref="contextmenu">
|
||||
<ul class="my-1">
|
||||
<li t-foreach="props.items" t-as="item" t-key="item_index" t-if="item.show" t-esc="item.title" t-on-click.stop="() => this.onClickItem(item.action)" class="custom-menu-item py-1 px-4"/>
|
||||
</ul>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
+55
-14
@@ -1,4 +1,4 @@
|
||||
const { Component, useRef, useEffect } = owl;
|
||||
const { Component } = owl;
|
||||
import { useStore } from "../../../store/store";
|
||||
import { ObjectTreeElement } from "./object_tree_element/object_tree_element";
|
||||
|
||||
@@ -7,23 +7,64 @@ export class DetailsWindow extends Component {
|
||||
static components = { ObjectTreeElement };
|
||||
setup() {
|
||||
this.store = useStore();
|
||||
this.contextMenu = useRef("contextmenu");
|
||||
this.contextMenuId = this.store.contextMenu.id++;
|
||||
this.contextMenuEvent;
|
||||
// Open the context menu when the ids match
|
||||
useEffect(
|
||||
(menuId) => {
|
||||
if (menuId === this.contextMenuId) {
|
||||
this.store.contextMenu.open(this.contextMenuEvent, this.contextMenu.el);
|
||||
}
|
||||
}
|
||||
|
||||
get contextMenuItems() {
|
||||
return [
|
||||
{
|
||||
title: "Inspect source code",
|
||||
show: true,
|
||||
action: () => this.store.inspectComponent("source", this.store.activeComponent.path),
|
||||
},
|
||||
() => [this.store.contextMenu.activeMenu]
|
||||
);
|
||||
{
|
||||
title: "Store as global variable",
|
||||
show: this.store.activeComponent.path.length !== 1,
|
||||
action: () =>
|
||||
this.store.logObjectInConsole([
|
||||
...this.store.activeComponent.path,
|
||||
{ type: "item", value: "component" },
|
||||
]),
|
||||
},
|
||||
{
|
||||
title: "Inspect in Elements tab",
|
||||
show: this.store.activeComponent.path.length !== 1,
|
||||
action: () => this.store.inspectComponent("DOM", this.store.activeComponent.path),
|
||||
},
|
||||
{
|
||||
title: "Force rerender",
|
||||
show: this.store.activeComponent.path.length !== 1,
|
||||
action: () => this.store.refreshComponent(this.store.activeComponent.path),
|
||||
},
|
||||
{
|
||||
title: "Store observed states as global variable",
|
||||
show: this.store.activeComponent.path.length !== 1,
|
||||
action: () =>
|
||||
this.store.logObjectInConsole([
|
||||
...this.store.activeComponent.path,
|
||||
{ type: "item", value: "subscriptions" },
|
||||
]),
|
||||
},
|
||||
{
|
||||
title: "Inspect compiled template",
|
||||
show: this.store.activeComponent.path.length !== 1,
|
||||
action: () =>
|
||||
this.store.inspectComponent("compiled template", this.store.activeComponent.path),
|
||||
},
|
||||
{
|
||||
title: "Log raw template",
|
||||
show: this.store.activeComponent.path.length !== 1,
|
||||
action: () => this.store.inspectComponent("raw template", this.store.activeComponent.path),
|
||||
},
|
||||
{
|
||||
title: "Store as global variable",
|
||||
show: this.store.activeComponent.path.length === 1,
|
||||
action: () => this.store.logObjectInConsole([...this.store.activeComponent.path]),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
openMenu(ev) {
|
||||
this.contextMenuEvent = ev;
|
||||
this.store.contextMenu.activeMenu = this.contextMenuId;
|
||||
this.store.openContextMenu(ev, this.contextMenuItems);
|
||||
}
|
||||
|
||||
toggleCategory(ev, category) {
|
||||
|
||||
+1
-17
@@ -76,21 +76,5 @@
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
<div t-if="store.contextMenu.activeMenu === contextMenuId" class="custom-menu" t-ref="contextmenu">
|
||||
<ul class="my-1">
|
||||
<li t-on-click.stop="() => this.store.inspectComponent('source', store.activeComponent.path)" class="custom-menu-item py-1 px-4">Inspect source code</li>
|
||||
<t t-if="store.activeComponent.path.length !== 1">
|
||||
<li t-on-click.stop="() => this.store.logObjectInConsole([...store.activeComponent.path, { type: 'item', value: 'component'}])" class="custom-menu-item py-1 px-4">Store as global variable</li>
|
||||
<li t-on-click.stop="() => this.store.inspectComponent('DOM', store.activeComponent.path)" class="custom-menu-item py-1 px-4">Inspect in Elements tab</li>
|
||||
<li t-on-click.stop="() => this.store.refreshComponent(store.activeComponent.path)" class="custom-menu-item py-1 px-4">Force rerender</li>
|
||||
<li t-on-click.stop="() => this.store.logObjectInConsole([...store.activeComponent.path, { type: 'item', value: 'subscriptions'}])" class="custom-menu-item py-1 px-4">Store observed states as global variable</li>
|
||||
<li t-on-click.stop="() => this.store.inspectComponent('compiled template', store.activeComponent.path)" class="custom-menu-item py-1 px-4">Inspect compiled template</li>
|
||||
<li t-on-click.stop="() => this.store.inspectComponent('raw template', store.activeComponent.path)" class="custom-menu-item py-1 px-4">Log raw template</li>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<li t-on-click.stop="() => this.store.logObjectInConsole([...store.activeComponent.path])" class="custom-menu-item py-1 px-4">Store as global variable</li>
|
||||
</t>
|
||||
</ul>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</templates>
|
||||
|
||||
+16
-13
@@ -13,19 +13,8 @@ export class ObjectTreeElement extends Component {
|
||||
menuTop: 0,
|
||||
menuLeft: 0,
|
||||
});
|
||||
this.contextMenu = useRef("contextmenu");
|
||||
const inputRef = useRef("input");
|
||||
this.store = useStore();
|
||||
this.contextMenuId = this.store.contextMenu.id++;
|
||||
this.contextMenuEvent,
|
||||
useEffect(
|
||||
(menuId) => {
|
||||
if (menuId === this.contextMenuId) {
|
||||
this.store.contextMenu.open(this.contextMenuEvent, this.contextMenu.el);
|
||||
}
|
||||
},
|
||||
() => [this.store.contextMenu.activeMenu]
|
||||
);
|
||||
useEffect(
|
||||
(editMode) => {
|
||||
// Focus on the input when it is created
|
||||
@@ -63,9 +52,23 @@ export class ObjectTreeElement extends Component {
|
||||
return this.props.object.depth * 0.8 + 0.3;
|
||||
}
|
||||
|
||||
get contextMenuItems() {
|
||||
return [
|
||||
{
|
||||
title: "Store as global variable",
|
||||
show: true,
|
||||
action: () => this.store.logObjectInConsole(this.props.object.path),
|
||||
},
|
||||
{
|
||||
title: "Inspect function source code",
|
||||
show: this.props.object.contentType === "function",
|
||||
action: () => this.store.inspectFunctionSource(this.props.object.path),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
openMenu(ev) {
|
||||
this.contextMenuEvent = ev;
|
||||
this.store.contextMenu.activeMenu = this.contextMenuId;
|
||||
this.store.openContextMenu(ev, this.contextMenuItems);
|
||||
}
|
||||
|
||||
setupEditMode() {
|
||||
|
||||
+4
-12
@@ -4,7 +4,7 @@
|
||||
<div class="m-0 p-0 text-nowrap w-100 object-line"
|
||||
t-att-class="props.class + (props.object.hasChildren ? ' bg-feedback' : '')"
|
||||
t-on-click.stop="() => this.store.toggleObjectTreeElementsDisplay(this.props.object)"
|
||||
t-on-contextmenu.prevent="openMenu"
|
||||
t-on-contextmenu.prevent="openMenu"
|
||||
>
|
||||
<div t-attf-style="padding-left: {{objectPadding}}rem">
|
||||
<i class="fa px-1 pointer-icon caret"
|
||||
@@ -12,7 +12,7 @@
|
||||
t-attf-style="visibility: {{props.object.hasChildren ? '' : 'hidden'}};"
|
||||
/>
|
||||
<t t-esc="props.object.name"/>
|
||||
<t t-if="props.object.content.length > 0">: </t>
|
||||
<t t-if="props.object.content.length > 0">: </t>
|
||||
<t t-if="props.object.contentType == 'getter'">
|
||||
<span class="getter-content object-content" t-att-class="objectLineClass" t-on-click.stop="() => this.store.loadGetterContent(this.props.object)">
|
||||
<t t-esc="props.object.content"/>
|
||||
@@ -20,25 +20,17 @@
|
||||
</t>
|
||||
<t t-else="">
|
||||
<span class="object-content" t-att-class="objectLineClass" t-on-dblclick.stop="setupEditMode">
|
||||
<t t-if="state.editMode">
|
||||
<t t-if="state.editMode">
|
||||
<input t-attf-id="objectEditionInput/{{pathAsString}}" t-ref="input" type="text" placeholder="" t-att-value="props.object.content" t-on-keydown.stop="editObject"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<t t-esc="props.object.content"/>
|
||||
</t>
|
||||
</span>
|
||||
</span>
|
||||
</t>
|
||||
<span t-if="keyChanges" class="key-changes ms-1 badge p-1" title="Key additions/deletions are observed">+/-</span>
|
||||
</div>
|
||||
</div>
|
||||
<div t-if="store.contextMenu.activeMenu === contextMenuId" class="custom-menu" t-ref="contextmenu">
|
||||
<ul class="my-1">
|
||||
<li t-on-click="() => this.store.logObjectInConsole(this.props.object.path)" class="custom-menu-item py-1 px-4 text-nowrap">Store as global variable</li>
|
||||
<t t-if='props.object.contentType == "function"'>
|
||||
<li t-on-click="() => this.store.inspectFunctionSource(this.props.object.path)" class="custom-menu-item py-1 px-4 text-nowrap">Inspect function source code</li>
|
||||
</t>
|
||||
</ul>
|
||||
</div>
|
||||
<t t-if="props.object.toggled" t-key="contextMenuId">
|
||||
<t t-foreach="props.object.children" t-as="child" t-key="child_index">
|
||||
<ObjectTreeElement object="child" class="this.classFor(child)"/>
|
||||
|
||||
+79
-14
@@ -16,10 +16,7 @@ export class TreeElement extends Component {
|
||||
searched: false,
|
||||
});
|
||||
this.store = useStore();
|
||||
this.contextMenu = useRef("contextmenu");
|
||||
this.element = useRef("element");
|
||||
this.contextMenuId = this.store.contextMenu.id++;
|
||||
this.contextMenuEvent;
|
||||
this.stringifiedPath = JSON.stringify(this.props.component.path);
|
||||
// Scroll to the selected element when it changes
|
||||
onMounted(() => {
|
||||
@@ -38,15 +35,6 @@ export class TreeElement extends Component {
|
||||
},
|
||||
() => [this.props.component.selected]
|
||||
);
|
||||
// Open the context menu when the ids match
|
||||
useEffect(
|
||||
(menuId) => {
|
||||
if (menuId === this.contextMenuId) {
|
||||
this.store.contextMenu.open(this.contextMenuEvent, this.contextMenu.el);
|
||||
}
|
||||
},
|
||||
() => [this.store.contextMenu.activeMenu]
|
||||
);
|
||||
// Effect to apply a short highlight effect to the component when it is rendered
|
||||
useEffect(
|
||||
() => {
|
||||
@@ -86,9 +74,86 @@ export class TreeElement extends Component {
|
||||
return minimizeKey(this.props.component.key);
|
||||
}
|
||||
|
||||
get contextMenuItems() {
|
||||
return [
|
||||
{
|
||||
title: "Expand children",
|
||||
show: true,
|
||||
action: () => this.store.toggleComponentAndChildren(this.props.component, true),
|
||||
},
|
||||
{
|
||||
title: "Fold all children",
|
||||
show: true,
|
||||
action: () => this.store.toggleComponentAndChildren(this.props.component, false),
|
||||
},
|
||||
{
|
||||
title: "Fold direct children",
|
||||
show: true,
|
||||
action: () => this.store.foldDirectChildren(this.props.component),
|
||||
},
|
||||
{
|
||||
title: "Inspect source code",
|
||||
show: true,
|
||||
action: () => this.store.inspectComponent("source", this.props.component.path),
|
||||
},
|
||||
{
|
||||
title: "Store as global variable",
|
||||
show: this.props.component.path.length !== 1,
|
||||
action: () =>
|
||||
this.store.logObjectInConsole([
|
||||
...this.props.component.path,
|
||||
{ type: "item", value: "component" },
|
||||
]),
|
||||
},
|
||||
{
|
||||
title: "Inspect in Elements tab",
|
||||
show: this.props.component.path.length !== 1,
|
||||
action: () => this.store.inspectComponent("DOM", this.props.component.path),
|
||||
},
|
||||
{
|
||||
title: "Force rerender",
|
||||
show: this.props.component.path.length !== 1,
|
||||
action: () => this.store.refreshComponent(this.props.component.path),
|
||||
},
|
||||
{
|
||||
title: "Store observed states as global variable",
|
||||
show: this.props.component.path.length !== 1,
|
||||
action: () =>
|
||||
this.store.logObjectInConsole([
|
||||
...this.props.component.path,
|
||||
{ type: "item", value: "subscriptions" },
|
||||
]),
|
||||
},
|
||||
{
|
||||
title: "Inspect compiled template",
|
||||
show: this.props.component.path.length !== 1,
|
||||
action: () => this.store.inspectComponent("compiled template", this.props.component.path),
|
||||
},
|
||||
{
|
||||
title: "Log raw template",
|
||||
show: this.props.component.path.length !== 1,
|
||||
action: () => this.store.inspectComponent("raw template", this.props.component.path),
|
||||
},
|
||||
{
|
||||
title: "Store as global variable",
|
||||
show: this.props.component.path.length === 1,
|
||||
action: () => this.store.logObjectInConsole([...this.props.component.path]),
|
||||
},
|
||||
{
|
||||
title: "Don't fold component by default",
|
||||
show: this.store.settings.componentsToggleBlacklist.has(this.props.component.name),
|
||||
action: () => this.toggleComponentToBlacklist(),
|
||||
},
|
||||
{
|
||||
title: "Fold component by default",
|
||||
show: !this.store.settings.componentsToggleBlacklist.has(this.props.component.name),
|
||||
action: () => this.toggleComponentToBlacklist(),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
openMenu(ev) {
|
||||
this.contextMenuEvent = ev;
|
||||
this.store.contextMenu.activeMenu = this.contextMenuId;
|
||||
this.store.openContextMenu(ev, this.contextMenuItems);
|
||||
}
|
||||
|
||||
// Expand/fold the component node
|
||||
|
||||
+5
-28
@@ -2,15 +2,15 @@
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="devtools.TreeElement" owl="1">
|
||||
<div t-ref="element"
|
||||
t-att-class="{'component-selected': props.component.selected,'component-highlighted': props.component.highlighted}"
|
||||
class="tree-component m-0 p-0 w-100 text-nowrap user-select-none"
|
||||
t-on-contextmenu.prevent="openMenu"
|
||||
t-on-mouseover.stop="() => this.store.highlightComponent(props.component.path)"
|
||||
t-att-class="{'component-selected': props.component.selected,'component-highlighted': props.component.highlighted}"
|
||||
class="tree-component m-0 p-0 w-100 text-nowrap user-select-none"
|
||||
t-on-contextmenu.prevent="openMenu"
|
||||
t-on-mouseover.stop="() => this.store.highlightComponent(props.component.path)"
|
||||
t-on-click.stop="toggleComponent"
|
||||
>
|
||||
<div class="component-wrapper" t-attf-style="padding-left: {{componentPadding}}rem">
|
||||
<i class="fa px-1 pointer-icon caret"
|
||||
t-att-class="{'fa-caret-right': !props.component.toggled, 'fa-caret-down': props.component.toggled}"
|
||||
t-att-class="{'fa-caret-right': !props.component.toggled, 'fa-caret-down': props.component.toggled}"
|
||||
t-on-click.stop="toggleDisplay"
|
||||
t-attf-style="{{props.component.children.length > 0 ? '' : 'visibility: hidden;'}}"
|
||||
/>
|
||||
@@ -26,29 +26,6 @@
|
||||
<span t-if="props.component.depth">></span>
|
||||
<span class="version" t-else="">owl=<t t-esc="props.component.version"/></span>
|
||||
</div>
|
||||
<div t-if="store.contextMenu.activeMenu === contextMenuId" class="custom-menu" t-ref="contextmenu">
|
||||
<ul class="my-1">
|
||||
<li t-on-click.stop="() => this.store.toggleComponentAndChildren(props.component, true)" class="custom-menu-item py-1 px-4">Expand children</li>
|
||||
<li t-on-click.stop="() => this.store.toggleComponentAndChildren(props.component, false)" class="custom-menu-item py-1 px-4">Fold all children</li>
|
||||
<li t-on-click.stop="() => this.store.foldDirectChildren(props.component)" class="custom-menu-item py-1 px-4">Fold direct children</li>
|
||||
<li t-on-click.stop="() => this.store.inspectComponent('source', props.component.path)" class="custom-menu-item py-1 px-4">Inspect source code</li>
|
||||
<t t-if="props.component.path.length !== 1">
|
||||
<li t-on-click.stop="() => this.store.logObjectInConsole([...props.component.path, { type: 'item', value: 'component'}])" class="custom-menu-item py-1 px-4">Store as global variable</li>
|
||||
<li t-on-click.stop="() => this.store.inspectComponent('DOM', props.component.path)" class="custom-menu-item py-1 px-4">Inspect in Elements tab</li>
|
||||
<li t-on-click.stop="() => this.store.refreshComponent(props.component.path)" class="custom-menu-item py-1 px-4">Force rerender</li>
|
||||
<li t-on-click.stop="() => this.store.logObjectInConsole([...props.component.path, { type: 'item', value: 'subscriptions'}])" class="custom-menu-item py-1 px-4">Store observed states as global variable</li>
|
||||
<li t-on-click.stop="() => this.store.inspectComponent('compiled template', props.component.path)" class="custom-menu-item py-1 px-4">Inspect compiled template</li>
|
||||
<li t-on-click.stop="() => this.store.inspectComponent('raw template', props.component.path)" class="custom-menu-item py-1 px-4">Log raw template</li>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<li t-on-click.stop="() => this.store.logObjectInConsole([...props.component.path])" class="custom-menu-item py-1 px-4">Store as global variable</li>
|
||||
</t>
|
||||
<li t-on-click.stop="() => this.toggleComponentToBlacklist()" class="custom-menu-item py-1 px-4">
|
||||
<t t-if="store.settings.componentsToggleBlacklist.has(props.component.name)">Don't fold component by default</t>
|
||||
<t t-else="">Fold component by default</t>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<t t-if="props.component.toggled">
|
||||
<t t-foreach="props.component.children" t-as="child" t-key="child.key">
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
const { Component } = owl;
|
||||
import { ComponentsTab } from "./components_tab/components_tab";
|
||||
import { Tab } from "./tab/tab";
|
||||
import { ProfilerTab } from "./profiler_tab/profiler_tab";
|
||||
import { ContextMenu } from "../context_menu/context_menu";
|
||||
import { useStore } from "../store/store";
|
||||
import { ComponentsTab } from "./components_tab/components_tab";
|
||||
import { ProfilerTab } from "./profiler_tab/profiler_tab";
|
||||
import { Tab } from "./tab/tab";
|
||||
|
||||
export class DevtoolsWindow extends Component {
|
||||
static props = [];
|
||||
static template = "devtools.DevtoolsWindow";
|
||||
static components = { ComponentsTab, Tab, ProfilerTab };
|
||||
static components = { ComponentsTab, Tab, ProfilerTab, ContextMenu };
|
||||
setup() {
|
||||
this.store = useStore();
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
Owl is not loaded on this page.
|
||||
</div>
|
||||
</t>
|
||||
<ContextMenu t-if="store.contextMenu" items="store.contextMenu.items"/>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
|
||||
@@ -1,24 +1,13 @@
|
||||
import { minimizeKey } from "../../../../utils";
|
||||
import { useStore } from "../../../store/store";
|
||||
|
||||
const { Component, useEffect, useRef } = owl;
|
||||
const { Component } = owl;
|
||||
|
||||
export class Event extends Component {
|
||||
static template = "devtools.Event";
|
||||
|
||||
setup() {
|
||||
this.store = useStore();
|
||||
this.componentContextMenu = useRef("componentContextmenu");
|
||||
this.componentContextMenuId = this.store.contextMenu.id++;
|
||||
this.contextMenuEvent,
|
||||
useEffect(
|
||||
(menuId) => {
|
||||
if (menuId === this.componentContextMenuId) {
|
||||
this.store.contextMenu.open(this.contextMenuEvent, this.componentContextMenu.el);
|
||||
}
|
||||
},
|
||||
() => [this.store.contextMenu.activeMenu]
|
||||
);
|
||||
}
|
||||
|
||||
// Formatting for displaying the key of the component
|
||||
@@ -54,13 +43,65 @@ export class Event extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
openComponentMenu(ev) {
|
||||
get contextMenuItems() {
|
||||
return [
|
||||
{
|
||||
title: "Inspect source code",
|
||||
show: true,
|
||||
action: () => this.store.inspectComponent("source", this.props.event.path),
|
||||
},
|
||||
{
|
||||
title: "Store as global variable",
|
||||
show: this.props.event.path.length !== 1,
|
||||
action: () =>
|
||||
this.store.logObjectInConsole([
|
||||
...this.props.event.path,
|
||||
{ type: "item", value: "component" },
|
||||
]),
|
||||
},
|
||||
{
|
||||
title: "Inspect in Elements tab",
|
||||
show: this.props.event.path.length !== 1,
|
||||
action: () => this.store.inspectComponent("DOM", this.props.event.path),
|
||||
},
|
||||
{
|
||||
title: "Force rerender",
|
||||
show: this.props.event.path.length !== 1,
|
||||
action: () => this.store.refreshComponent(this.props.event.path),
|
||||
},
|
||||
{
|
||||
title: "Store observed states as global variable",
|
||||
show: this.props.event.path.length !== 1,
|
||||
action: () =>
|
||||
this.store.logObjectInConsole([
|
||||
...this.props.event.path,
|
||||
{ type: "item", value: "subscriptions" },
|
||||
]),
|
||||
},
|
||||
{
|
||||
title: "Inspect compiled template",
|
||||
show: this.props.event.path.length !== 1,
|
||||
action: () => this.store.inspectComponent("compiled template", this.props.event.path),
|
||||
},
|
||||
{
|
||||
title: "Log raw template",
|
||||
show: this.props.event.path.length !== 1,
|
||||
action: () => this.store.inspectComponent("raw template", this.props.event.path),
|
||||
},
|
||||
{
|
||||
title: "Store as global variable",
|
||||
show: this.props.event.path.length === 1,
|
||||
action: () => this.store.logObjectInConsole([...this.props.event.path]),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
openMenu(ev) {
|
||||
if (this.props.event.type === "destroy") {
|
||||
return;
|
||||
} else {
|
||||
ev.preventDefault();
|
||||
this.contextMenuEvent = ev;
|
||||
this.store.contextMenu.activeMenu = this.componentContextMenuId;
|
||||
this.store.openContextMenu(ev, this.contextMenuItems);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
t-att-class="{'fa-caret-right': !props.event.toggled, 'fa-caret-down': props.event.toggled}"
|
||||
t-attf-style="visibility: {{props.event.origin ? '' : 'hidden'}};"
|
||||
/>
|
||||
<t t-esc="props.event.type"/>:
|
||||
<t t-esc="props.event.type"/>:
|
||||
<<span style="cursor:pointer; color: var(--component-color);"
|
||||
t-on-click.stop="() => this.store.selectComponent(props.event.path)"
|
||||
t-on-mouseover.stop="() => this.store.highlightComponent(props.event.path)"
|
||||
t-on-contextmenu="openComponentMenu"
|
||||
t-on-click.stop="() => this.store.selectComponent(props.event.path)"
|
||||
t-on-mouseover.stop="() => this.store.highlightComponent(props.event.path)"
|
||||
t-on-contextmenu="openMenu"
|
||||
t-esc="props.event.component"
|
||||
/>
|
||||
<t t-if="minimizedKey.length > 0">
|
||||
@@ -29,10 +29,10 @@
|
||||
<div class="my-0 pt-1 object-line">
|
||||
<i class="fa fa-caret-right mx-1 pe-2" style="visibility: hidden;"></i>
|
||||
<span>
|
||||
origin:
|
||||
<<span style="cursor:pointer; color: var(--component-color);"
|
||||
t-on-click.stop="() => this.store.selectComponent(props.event.origin.path)"
|
||||
t-on-mouseover.stop="() => this.store.highlightComponent(props.event.origin.path)"
|
||||
origin:
|
||||
<<span style="cursor:pointer; color: var(--component-color);"
|
||||
t-on-click.stop="() => this.store.selectComponent(props.event.origin.path)"
|
||||
t-on-mouseover.stop="() => this.store.highlightComponent(props.event.origin.path)"
|
||||
t-esc="props.event.origin.component"
|
||||
/>
|
||||
<t t-if="originMinimizedKey.length > 0">
|
||||
@@ -43,22 +43,6 @@
|
||||
</span>
|
||||
</div>
|
||||
</t>
|
||||
<div t-if="store.contextMenu.activeMenu === componentContextMenuId" class="custom-menu" t-ref="componentContextmenu">
|
||||
<ul class="my-1">
|
||||
<li t-on-click.stop="() => this.store.inspectComponent('source', props.event.path)" class="custom-menu-item py-1 px-4">Inspect source code</li>
|
||||
<t t-if="props.event.path.length !== 1">
|
||||
<li t-on-click.stop="() => this.store.logObjectInConsole([...props.event.path, { type: 'item', value: 'component'}])" class="custom-menu-item py-1 px-4">Store as global variable</li>
|
||||
<li t-on-click.stop="() => this.store.inspectComponent('DOM', props.event.path)" class="custom-menu-item py-1 px-4">Inspect in Elements tab</li>
|
||||
<li t-on-click.stop="() => this.store.refreshComponent(props.event.path)" class="custom-menu-item py-1 px-4">Force rerender</li>
|
||||
<li t-on-click.stop="() => this.store.logObjectInConsole([...props.event.path, { type: 'item', value: 'subscriptions'}])" class="custom-menu-item py-1 px-4">Store observed states as global variable</li>
|
||||
<li t-on-click.stop="() => this.store.inspectComponent('compiled template', props.event.path)" class="custom-menu-item py-1 px-4">Inspect compiled template</li>
|
||||
<li t-on-click.stop="() => this.store.inspectComponent('raw template', props.event.path)" class="custom-menu-item py-1 px-4">Log raw template</li>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<li t-on-click.stop="() => this.store.logObjectInConsole([...props.event.path])" class="custom-menu-item py-1 px-4">Store as global variable</li>
|
||||
</t>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
|
||||
+76
-21
@@ -1,7 +1,7 @@
|
||||
import { minimizeKey } from "../../../../utils";
|
||||
import { useStore } from "../../../store/store";
|
||||
|
||||
const { Component, useRef, useEffect } = owl;
|
||||
const { Component } = owl;
|
||||
|
||||
export class EventNode extends Component {
|
||||
static template = "devtools.EventNode";
|
||||
@@ -10,33 +10,89 @@ export class EventNode extends Component {
|
||||
|
||||
setup() {
|
||||
this.store = useStore();
|
||||
this.nodeContextMenu = useRef("nodeContextMenu");
|
||||
this.nodeContextMenuId = this.store.contextMenu.id++;
|
||||
this.componentContextMenu = useRef("componentContextmenu");
|
||||
this.componentContextMenuId = this.store.contextMenu.id++;
|
||||
this.contextMenuEvent,
|
||||
useEffect(
|
||||
(menuId) => {
|
||||
if (menuId === this.nodeContextMenuId) {
|
||||
this.store.contextMenu.open(this.contextMenuEvent, this.nodeContextMenu.el);
|
||||
}
|
||||
if (menuId === this.componentContextMenuId) {
|
||||
this.store.contextMenu.open(this.contextMenuEvent, this.componentContextMenu.el);
|
||||
}
|
||||
},
|
||||
() => [this.store.contextMenu.activeMenu]
|
||||
);
|
||||
}
|
||||
|
||||
get eventPadding() {
|
||||
return this.props.event.depth * 0.8 + 0.3;
|
||||
}
|
||||
|
||||
get nodeContextMenuItems() {
|
||||
return [
|
||||
{
|
||||
title: "Expand children",
|
||||
show: true,
|
||||
action: () => this.store.toggleEventAndChildren(this.props.event, true),
|
||||
},
|
||||
{
|
||||
title: "Fold all children",
|
||||
show: true,
|
||||
action: () => this.store.toggleEventAndChildren(this.props.event, false),
|
||||
},
|
||||
{
|
||||
title: "Fold direct children",
|
||||
show: true,
|
||||
action: () => this.store.foldDirectChildren(this.props.event),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
get componentContextMenuItems() {
|
||||
return [
|
||||
{
|
||||
title: "Inspect source code",
|
||||
show: true,
|
||||
action: () => this.store.inspectComponent("source", this.props.event.path),
|
||||
},
|
||||
{
|
||||
title: "Store as global variable",
|
||||
show: this.props.event.path.length !== 1,
|
||||
action: () =>
|
||||
this.store.logObjectInConsole([
|
||||
...this.props.event.path,
|
||||
{ type: "item", value: "component" },
|
||||
]),
|
||||
},
|
||||
{
|
||||
title: "Inspect in Elements tab",
|
||||
show: this.props.event.path.length !== 1,
|
||||
action: () => this.store.inspectComponent("DOM", this.props.event.path),
|
||||
},
|
||||
{
|
||||
title: "Force rerender",
|
||||
show: this.props.event.path.length !== 1,
|
||||
action: () => this.store.refreshComponent(this.props.event.path),
|
||||
},
|
||||
{
|
||||
title: "Store observed states as global variable",
|
||||
show: this.props.event.path.length !== 1,
|
||||
action: () =>
|
||||
this.store.logObjectInConsole([
|
||||
...this.props.event.path,
|
||||
{ type: "item", value: "subscriptions" },
|
||||
]),
|
||||
},
|
||||
{
|
||||
title: "Inspect compiled template",
|
||||
show: this.props.event.path.length !== 1,
|
||||
action: () => this.store.inspectComponent("compiled template", this.props.event.path),
|
||||
},
|
||||
{
|
||||
title: "Log raw template",
|
||||
show: this.props.event.path.length !== 1,
|
||||
action: () => this.store.inspectComponent("raw template", this.props.event.path),
|
||||
},
|
||||
{
|
||||
title: "Store as global variable",
|
||||
show: this.props.event.path.length === 1,
|
||||
action: () => this.store.logObjectInConsole([...this.props.event.path]),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
openNodeMenu(ev) {
|
||||
if (this.props.event.children.length) {
|
||||
ev.preventDefault();
|
||||
this.contextMenuEvent = ev;
|
||||
this.store.contextMenu.activeMenu = this.nodeContextMenuId;
|
||||
this.store.openContextMenu(ev, this.nodeContextMenuItems);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,8 +101,7 @@ export class EventNode extends Component {
|
||||
return;
|
||||
} else {
|
||||
ev.preventDefault();
|
||||
this.contextMenuEvent = ev;
|
||||
this.store.contextMenu.activeMenu = this.componentContextMenuId;
|
||||
this.store.openContextMenu(ev, this.componentContextMenuItems);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+7
-30
@@ -2,8 +2,8 @@
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="devtools.EventNode" owl="1">
|
||||
<div class="my-0 p-0 object-line"
|
||||
t-on-click.stop="toggleDisplay"
|
||||
t-on-contextmenu="openNodeMenu"
|
||||
t-on-click.stop="toggleDisplay"
|
||||
t-on-contextmenu="openNodeMenu"
|
||||
>
|
||||
<div class="text-nowrap" t-attf-style="padding-left: {{eventPadding}}rem">
|
||||
<i class="fa px-1 pointer-icon caret"
|
||||
@@ -11,11 +11,11 @@
|
||||
t-attf-style="visibility: {{props.event.children.length > 0 ? '' : 'hidden'}};"
|
||||
/>
|
||||
<span>
|
||||
<t t-esc="props.event.type"/>:
|
||||
<<span style="cursor:pointer; color: var(--component-color);"
|
||||
t-on-click.stop="() => this.store.selectComponent(props.event.path)"
|
||||
t-on-mouseover.stop="() => this.store.highlightComponent(props.event.path)"
|
||||
t-on-contextmenu.stop="openComponentMenu"
|
||||
<t t-esc="props.event.type"/>:
|
||||
<<span style="cursor:pointer; color: var(--component-color);"
|
||||
t-on-click.stop="() => this.store.selectComponent(props.event.path)"
|
||||
t-on-mouseover.stop="() => this.store.highlightComponent(props.event.path)"
|
||||
t-on-contextmenu.stop="openComponentMenu"
|
||||
t-esc="props.event.component"/>
|
||||
<t t-if="minimizedKey.length > 0">
|
||||
<span t-if="minimizedKey.length > 0" style="color: var(--key-name);"> key</span>=<span style="color: var(--key-content);">
|
||||
@@ -28,29 +28,6 @@
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div t-if="store.contextMenu.activeMenu === nodeContextMenuId" class="custom-menu" t-ref="nodeContextMenu">
|
||||
<ul class="my-1">
|
||||
<li t-on-click.stop="() => this.store.toggleEventAndChildren(props.event, true)" class="custom-menu-item py-1 px-4">Expand children</li>
|
||||
<li t-on-click.stop="() => this.store.toggleEventAndChildren(props.event, false)" class="custom-menu-item py-1 px-4">Fold all children</li>
|
||||
<li t-on-click.stop="() => this.store.foldDirectChildren(props.event)" class="custom-menu-item py-1 px-4">Fold direct children</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div t-if="store.contextMenu.activeMenu === componentContextMenuId" class="custom-menu" t-ref="componentContextmenu">
|
||||
<ul class="my-1">
|
||||
<li t-on-click.stop="() => this.store.inspectComponent('source', props.event.path)" class="custom-menu-item py-1 px-4">Inspect source code</li>
|
||||
<t t-if="props.event.path.length !== 1">
|
||||
<li t-on-click.stop="() => this.store.logObjectInConsole([...props.event.path, { type: 'item', value: 'component'}])" class="custom-menu-item py-1 px-4">Store as global variable</li>
|
||||
<li t-on-click.stop="() => this.store.inspectComponent('DOM', props.event.path)" class="custom-menu-item py-1 px-4">Inspect in Elements tab</li>
|
||||
<li t-on-click.stop="() => this.store.refreshComponent(props.event.path)" class="custom-menu-item py-1 px-4">Force rerender</li>
|
||||
<li t-on-click.stop="() => this.store.logObjectInConsole([...props.event.path, { type: 'item', value: 'subscriptions'}])" class="custom-menu-item py-1 px-4">Store observed states as global variable</li>
|
||||
<li t-on-click.stop="() => this.store.inspectComponent('compiled template', props.event.path)" class="custom-menu-item py-1 px-4">Inspect compiled template</li>
|
||||
<li t-on-click.stop="() => this.store.inspectComponent('raw template', props.event.path)" class="custom-menu-item py-1 px-4">Log raw template</li>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<li t-on-click.stop="() => this.store.logObjectInConsole([...props.event.path])" class="custom-menu-item py-1 px-4">Store as global variable</li>
|
||||
</t>
|
||||
</ul>
|
||||
</div>
|
||||
<t t-if="props.event.toggled">
|
||||
<t t-foreach="props.event.children" t-as="child" t-key="child.id">
|
||||
<EventNode event="child"/>
|
||||
|
||||
@@ -11,30 +11,7 @@ export const store = reactive({
|
||||
darkmode: false,
|
||||
componentsToggleBlacklist: new Set(),
|
||||
},
|
||||
contextMenu: {
|
||||
id: 0,
|
||||
activeMenu: -1,
|
||||
// Opens the context menu corresponding with the given menu html element
|
||||
open(event, menu) {
|
||||
const menuWidth = menu.offsetWidth;
|
||||
const menuHeight = menu.offsetHeight;
|
||||
let x = event.clientX;
|
||||
let y = event.clientY;
|
||||
if (x + menuWidth > window.innerWidth) {
|
||||
x = window.innerWidth - menuWidth;
|
||||
}
|
||||
if (y + menuHeight > window.innerHeight) {
|
||||
y = window.innerHeight - menuHeight;
|
||||
}
|
||||
menu.style.left = x + "px";
|
||||
// Need 25px offset because of the main navbar from the browser devtools
|
||||
menu.style.top = y - 25 + "px";
|
||||
},
|
||||
// Close the currently displayed context menu
|
||||
close() {
|
||||
this.activeMenu = -1;
|
||||
},
|
||||
},
|
||||
contextMenu: null,
|
||||
isFirefox: IS_FIREFOX,
|
||||
frameUrls: ["top"],
|
||||
activeFrame: "top",
|
||||
@@ -93,6 +70,16 @@ export const store = reactive({
|
||||
evalFunctionInWindow("disableHTMLSelector", [], this.activeFrame);
|
||||
},
|
||||
|
||||
openContextMenu(event, items) {
|
||||
this.contextMenu = {
|
||||
position: {
|
||||
x: event.clientX,
|
||||
y: event.clientY,
|
||||
},
|
||||
items,
|
||||
};
|
||||
},
|
||||
|
||||
// Load all data related to the components tree using the global hook loaded on the page
|
||||
// Use fromOld to specify if we want to keep most of the toggled/selected data of the old tree
|
||||
// when generating the new one
|
||||
@@ -646,8 +633,9 @@ async function init() {
|
||||
store.updateIFrameList();
|
||||
|
||||
// Global listeners to close the currently shown context menu when the user clicks or opens another
|
||||
document.addEventListener("click", () => store.contextMenu.close(), { capture: true });
|
||||
document.addEventListener("contextmenu", () => store.contextMenu.close(), { capture: true });
|
||||
document.addEventListener("click", () => (store.contextMenu = null), { capture: true });
|
||||
document.addEventListener("contextmenu", () => (store.contextMenu = null), { capture: true });
|
||||
window.addEventListener("blur", () => (store.contextMenu = null), { capture: true });
|
||||
|
||||
// Make sure the events recorder is at its initial state in every frame
|
||||
for (const frame of store.frameUrls) {
|
||||
|
||||
Reference in New Issue
Block a user