diff --git a/tools/devtools/src/devtools_app/context_menu/context_menu.js b/tools/devtools/src/devtools_app/context_menu/context_menu.js new file mode 100644 index 00000000..7be2a673 --- /dev/null +++ b/tools/devtools/src/devtools_app/context_menu/context_menu.js @@ -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; + } +} diff --git a/tools/devtools/src/devtools_app/context_menu/context_menu.xml b/tools/devtools/src/devtools_app/context_menu/context_menu.xml new file mode 100644 index 00000000..8eb29530 --- /dev/null +++ b/tools/devtools/src/devtools_app/context_menu/context_menu.xml @@ -0,0 +1,10 @@ + + + +
+
    +
  • +
+
+
+
diff --git a/tools/devtools/src/devtools_app/devtools_window/components_tab/details_window/details_window.js b/tools/devtools/src/devtools_app/devtools_window/components_tab/details_window/details_window.js index 1934c968..52f8d250 100644 --- a/tools/devtools/src/devtools_app/devtools_window/components_tab/details_window/details_window.js +++ b/tools/devtools/src/devtools_app/devtools_window/components_tab/details_window/details_window.js @@ -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) { diff --git a/tools/devtools/src/devtools_app/devtools_window/components_tab/details_window/details_window.xml b/tools/devtools/src/devtools_app/devtools_window/components_tab/details_window/details_window.xml index ba477b71..2007adea 100644 --- a/tools/devtools/src/devtools_app/devtools_window/components_tab/details_window/details_window.xml +++ b/tools/devtools/src/devtools_app/devtools_window/components_tab/details_window/details_window.xml @@ -76,21 +76,5 @@ -
- -
- \ No newline at end of file + diff --git a/tools/devtools/src/devtools_app/devtools_window/components_tab/details_window/object_tree_element/object_tree_element.js b/tools/devtools/src/devtools_app/devtools_window/components_tab/details_window/object_tree_element/object_tree_element.js index c50072ab..0cd37a0f 100644 --- a/tools/devtools/src/devtools_app/devtools_window/components_tab/details_window/object_tree_element/object_tree_element.js +++ b/tools/devtools/src/devtools_app/devtools_window/components_tab/details_window/object_tree_element/object_tree_element.js @@ -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() { diff --git a/tools/devtools/src/devtools_app/devtools_window/components_tab/details_window/object_tree_element/object_tree_element.xml b/tools/devtools/src/devtools_app/devtools_window/components_tab/details_window/object_tree_element/object_tree_element.xml index 6e1a2d40..17d904e4 100644 --- a/tools/devtools/src/devtools_app/devtools_window/components_tab/details_window/object_tree_element/object_tree_element.xml +++ b/tools/devtools/src/devtools_app/devtools_window/components_tab/details_window/object_tree_element/object_tree_element.xml @@ -4,7 +4,7 @@
- : + : @@ -20,25 +20,17 @@ - + - + +/-
-
- -
diff --git a/tools/devtools/src/devtools_app/devtools_window/components_tab/tree_element/tree_element.js b/tools/devtools/src/devtools_app/devtools_window/components_tab/tree_element/tree_element.js index 88ae721b..c7dee723 100644 --- a/tools/devtools/src/devtools_app/devtools_window/components_tab/tree_element/tree_element.js +++ b/tools/devtools/src/devtools_app/devtools_window/components_tab/tree_element/tree_element.js @@ -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 diff --git a/tools/devtools/src/devtools_app/devtools_window/components_tab/tree_element/tree_element.xml b/tools/devtools/src/devtools_app/devtools_window/components_tab/tree_element/tree_element.xml index c126b5da..28728db9 100644 --- a/tools/devtools/src/devtools_app/devtools_window/components_tab/tree_element/tree_element.xml +++ b/tools/devtools/src/devtools_app/devtools_window/components_tab/tree_element/tree_element.xml @@ -2,15 +2,15 @@
@@ -26,29 +26,6 @@ > owl=
-
-
    -
  • Expand children
  • -
  • Fold all children
  • -
  • Fold direct children
  • -
  • Inspect source code
  • - -
  • Store as global variable
  • -
  • Inspect in Elements tab
  • -
  • Force rerender
  • -
  • Store observed states as global variable
  • -
  • Inspect compiled template
  • -
  • Log raw template
  • -
    - -
  • Store as global variable
  • -
    -
  • - Don't fold component by default - Fold component by default -
  • -
-
diff --git a/tools/devtools/src/devtools_app/devtools_window/devtools_window.js b/tools/devtools/src/devtools_app/devtools_window/devtools_window.js index c2ac83f4..74a9c5ba 100644 --- a/tools/devtools/src/devtools_app/devtools_window/devtools_window.js +++ b/tools/devtools/src/devtools_app/devtools_window/devtools_window.js @@ -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(); } diff --git a/tools/devtools/src/devtools_app/devtools_window/devtools_window.xml b/tools/devtools/src/devtools_app/devtools_window/devtools_window.xml index b75896d2..a8936d5d 100644 --- a/tools/devtools/src/devtools_app/devtools_window/devtools_window.xml +++ b/tools/devtools/src/devtools_app/devtools_window/devtools_window.xml @@ -28,6 +28,7 @@ Owl is not loaded on this page. +
diff --git a/tools/devtools/src/devtools_app/devtools_window/profiler_tab/event/event.js b/tools/devtools/src/devtools_app/devtools_window/profiler_tab/event/event.js index 0ba34b31..7bf28362 100644 --- a/tools/devtools/src/devtools_app/devtools_window/profiler_tab/event/event.js +++ b/tools/devtools/src/devtools_app/devtools_window/profiler_tab/event/event.js @@ -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); } } } diff --git a/tools/devtools/src/devtools_app/devtools_window/profiler_tab/event/event.xml b/tools/devtools/src/devtools_app/devtools_window/profiler_tab/event/event.xml index 4c152e3a..369d3a26 100644 --- a/tools/devtools/src/devtools_app/devtools_window/profiler_tab/event/event.xml +++ b/tools/devtools/src/devtools_app/devtools_window/profiler_tab/event/event.xml @@ -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'}};" /> - : + : < @@ -29,10 +29,10 @@
- origin: - < @@ -43,22 +43,6 @@
-
-
    -
  • Inspect source code
  • - -
  • Store as global variable
  • -
  • Inspect in Elements tab
  • -
  • Force rerender
  • -
  • Store observed states as global variable
  • -
  • Inspect compiled template
  • -
  • Log raw template
  • -
    - -
  • Store as global variable
  • -
    -
-
diff --git a/tools/devtools/src/devtools_app/devtools_window/profiler_tab/event_node/event_node.js b/tools/devtools/src/devtools_app/devtools_window/profiler_tab/event_node/event_node.js index 8ad57b90..95ede0c6 100644 --- a/tools/devtools/src/devtools_app/devtools_window/profiler_tab/event_node/event_node.js +++ b/tools/devtools/src/devtools_app/devtools_window/profiler_tab/event_node/event_node.js @@ -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); } } diff --git a/tools/devtools/src/devtools_app/devtools_window/profiler_tab/event_node/event_node.xml b/tools/devtools/src/devtools_app/devtools_window/profiler_tab/event_node/event_node.xml index caa7e565..ce3c3bf1 100644 --- a/tools/devtools/src/devtools_app/devtools_window/profiler_tab/event_node/event_node.xml +++ b/tools/devtools/src/devtools_app/devtools_window/profiler_tab/event_node/event_node.xml @@ -2,8 +2,8 @@
- : - <: + < key= @@ -28,29 +28,6 @@
-
-
    -
  • Expand children
  • -
  • Fold all children
  • -
  • Fold direct children
  • -
-
-
-
    -
  • Inspect source code
  • - -
  • Store as global variable
  • -
  • Inspect in Elements tab
  • -
  • Force rerender
  • -
  • Store observed states as global variable
  • -
  • Inspect compiled template
  • -
  • Log raw template
  • -
    - -
  • Store as global variable
  • -
    -
-
diff --git a/tools/devtools/src/devtools_app/store/store.js b/tools/devtools/src/devtools_app/store/store.js index b749d137..e2d493fd 100644 --- a/tools/devtools/src/devtools_app/store/store.js +++ b/tools/devtools/src/devtools_app/store/store.js @@ -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) {