[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:
Julien Carion (juca)
2024-10-23 15:45:29 +02:00
committed by Géry Debongnie
parent 5ecb4809ff
commit aff4019cde
15 changed files with 373 additions and 218 deletions
@@ -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>
@@ -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) {
@@ -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>
@@ -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,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)"/>
@@ -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
@@ -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">&gt;</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"/>:
&lt;<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:
&lt;<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:
&lt;<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>
@@ -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);
}
}
@@ -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"/>:
&lt;<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"/>:
&lt;<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"/>
+14 -26
View File
@@ -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) {