Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions app/controllers/component-tree.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ import {
isEmpty
} from '@ember/utils';

import {
schedule
} from '@ember/runloop';

import ComponentViewItem from 'ember-inspector/models/component-view-item';

/**
Expand Down Expand Up @@ -71,12 +75,19 @@ const flattenSearchTree = (

export default Controller.extend({
application: controller(),
queryParams: ['pinnedObjectId'],

/**
* The entry in the component tree corresponding to the pinnedObjectId
* will be selected
*/
pinnedObjectId: null,
inspectingViews: false,
components: true,
options: {
components: true,
},
viewTreeLoaded: false,

/**
* Bound to the search field to filter the component list.
Expand Down Expand Up @@ -138,13 +149,36 @@ export default Controller.extend({
this.set('expandedStateCache', {});
},

/**
* Expands the component tree so that entry for the given view will
* be shown. Recursively expands the entry's parents up to the root.
* @param {*} objectId The id of the ember view to show
*/
expandToNode(objectId) {
let node = this.get('filteredArray').find(item => item.get('id') === objectId);
if (node) {
node.expandParents();
}
},

/**
* This method is basically a trick to get the `{{vertical-collection}}` in the vicinity
* of the item that's been selected. We can't directly scroll to the element but we
* can guess at how far down the list the item is. Then we can manually set the scrollTop
* of the virtual scroll.
*/
scrollTreeToItem(objectId) {
let selectedItemIndex = this.get('displayedList').findIndex(item => item.view.objectId === objectId);

if (selectedItemIndex) {
const averageItemHeight = 22;
schedule('afterRender', () => {
document.querySelector('.js-component-tree').scrollTop = averageItemHeight * selectedItemIndex;
});
}
},


actions: {
previewLayer({
view: {
Expand Down Expand Up @@ -199,12 +233,16 @@ export default Controller.extend({
if (objectId) {
this.set('pinnedObjectId', objectId);
this.expandToNode(objectId);
this.scrollTreeToItem(objectId);
this.get('port').send('objectInspector:inspectById', {
objectId,
});
}
},

/**
* Scrolls the main page to put the selected element into view
*/
scrollToElement(elementId) {
this.get('port').send('view:scrollToElement', {
elementId
Expand Down
10 changes: 10 additions & 0 deletions app/routes/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export default Route.extend({
port.on('objectInspector:updateErrors', this, this.updateErrors);
port.on('objectInspector:droppedObject', this, this.droppedObject);
port.on('deprecation:count', this, this.setDeprecationCount);
port.on('view:inspectComponent', this, this.inspectComponent);
port.send('deprecation:getCount');
},

Expand All @@ -28,6 +29,15 @@ export default Route.extend({
port.off('objectInspector:updateErrors', this, this.updateErrors);
port.off('objectInspector:droppedObject', this, this.droppedObject);
port.off('deprecation:count', this, this.setDeprecationCount);
port.off('view:inspectComponent', this, this.inspectComponent);
},

inspectComponent({ viewId }) {
this.transitionTo('component-tree', {
queryParams: {
pinnedObjectId: viewId
}
});
},

updateObject(options) {
Expand Down
33 changes: 29 additions & 4 deletions app/routes/component-tree.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import TabRoute from "ember-inspector/routes/tab";

export default TabRoute.extend({
queryParams: {
pinnedObjectId: {
replace: true
}
},

setupController() {
this._super(...arguments);
this.get('port').on('view:viewTree', this, this.setViewTree);
this.get('port').on('view:stopInspecting', this, this.stopInspecting);
this.get('port').on('view:startInspecting', this, this.startInspecting);
this.get('port').on('view:inspectDOMElement', this, this.inspectDOMElement);
this.get('port').on('view:inspectComponent', this, this.inspectComponent);

this.set('controller.viewTreeLoaded', false);
this.get('port').send('view:setOptions', { options: this.get('controller.options') });
this.get('port').send('view:getTree');
},
Expand All @@ -17,12 +24,17 @@ export default TabRoute.extend({
this.get('port').off('view:stopInspecting', this, this.stopInspecting);
this.get('port').off('view:startInspecting', this, this.startInspecting);
this.get('port').off('view:inspectDOMElement', this, this.inspectDOMElement);
this.get('port').off('view:inspectComponent', this, this.inspectComponent);

},

setViewTree(options) {
this.set('controller.viewTree', options.tree);
this.set('controller.viewTreeLoaded', true);

// If we're waiting for view tree to inspect a component
const componentToInspect = this.get('controller.pinnedObjectId');
if (componentToInspect) {
this.inspectComponent(componentToInspect);
}
},

startInspecting() {
Expand All @@ -33,11 +45,24 @@ export default TabRoute.extend({
this.set('controller.inspectingViews', false);
},

inspectComponent({ viewId }) {
inspectComponent(viewId) {
if (!this.get('controller.viewTreeLoaded')) {
return;
}

this.get('controller').send('inspect', viewId);
},

inspectDOMElement({ elementSelector }) {
this.get('port.adapter').inspectDOMElement(elementSelector);
},

actions: {
queryParamsDidChange(params) {
const { pinnedObjectId } = params;
if (pinnedObjectId) {
this.inspectComponent(pinnedObjectId);
}
}
}
});
2 changes: 1 addition & 1 deletion app/templates/component-tree.hbs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<div class="list__content" style="height: 100%;">
<div class="list__content js-component-tree" style="height: 100%;">
{{#vertical-collection displayedList estimateHeight=20 as |item i|}}
<div onmouseenter={{action "previewLayer" item}}
onmouseleave={{action "hidePreview"}}>
Expand Down
3 changes: 3 additions & 0 deletions ember_debug/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ const EmberDebug = EmberObject.extend({
this.reset();

this.get('adapter').debug('Ember Inspector Active');
this.get('adapter').sendMessage({
type: 'inspectorLoaded'
});
},

destroyContainer() {
Expand Down
4 changes: 3 additions & 1 deletion ember_debug/object-inspector.js
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,9 @@ export default EmberObject.extend(PortMixin, {
},
inspectById(message) {
const obj = this.sentObjects[message.objectId];
this.sendObject(obj);
if (obj) {
this.sendObject(obj);
}
},
inspectByContainerLookup(message) {
const container = this.get('namespace.owner');
Expand Down
24 changes: 24 additions & 0 deletions ember_debug/view-debug.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ export default EmberObject.extend(PortMixin, {
if (model) {
this.get('objectInspector').sendValueToConsole(model);
}
},
contextMenu() {
this.inspectComponentForNode(this.lastClickedElement);
}
},

Expand All @@ -131,6 +134,14 @@ export default EmberObject.extend(PortMixin, {
previewDiv.setAttribute('data-label', 'preview-div');
document.body.appendChild(previewDiv);

// Store last clicked element for context menu
this.lastClickedHandler = (event) => {
if (event.button === 2) {
this.lastClickedElement = event.target;
}
};
window.addEventListener('mousedown', this.lastClickedHandler);

this.resizeHandler = () => {
if (this.glimmerTree) {
this.hideLayer();
Expand All @@ -155,6 +166,18 @@ export default EmberObject.extend(PortMixin, {
}
},

inspectComponentForNode(domNode) {
let viewElem = this.findNearestView(domNode);
if (!viewElem) {
this.get('adapter').log('No Ember component found.');
return;
}

this.sendMessage('inspectComponent', {
viewId: viewElem.id
});
},

updateDurations(durations) {
for (let guid in durations) {
if (!durations.hasOwnProperty(guid)) {
Expand Down Expand Up @@ -187,6 +210,7 @@ export default EmberObject.extend(PortMixin, {
willDestroy() {
this._super();
window.removeEventListener('resize', this.resizeHandler);
window.removeEventListener('mousedown', this.lastClickedHandler);
document.body.removeChild(layerDiv);
document.body.removeChild(previewDiv);
this.get('_lastNodes').clear();
Expand Down
65 changes: 54 additions & 11 deletions skeletons/web-extension/background-script.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
"use strict";

var activeTabs = {},
activeTabId,
contextMenuAdded = false,
emberInspectorChromePorts = {};

/**
Expand All @@ -33,7 +35,7 @@
* Creates the title for the pageAction for the current ClientApp
* @param {Number} tabId - the current tab
*/
function setActionTitle(tabId){
function setActionTitle(tabId) {
chrome.pageAction.setTitle({
tabId: tabId,
title: generateVersionsTooltip(activeTabs[tabId])
Expand All @@ -46,7 +48,7 @@
* is updated to display the ClientApp's information in the tooltip.
* @param {Number} tabId - the current tab
*/
function updateTabAction(tabId){
function updateTabAction(tabId) {
chrome.storage.sync.get("options", function(data) {
if (!data.options || !data.options.showTomster) { return; }
chrome.pageAction.show(tabId);
Expand All @@ -59,11 +61,44 @@
* Typically used to clearout the icon after reload.
* @param {Number} tabId - the current tab
*/
function hideAction(tabId){
delete activeTabs[tabId];
function hideAction(tabId) {
if (!activeTabs[tabId]) {
return;
}

chrome.pageAction.hide(tabId);
}

/**
* Update the tab's contextMenu: https://developer.chrome.com/extensions/contextMenus
* Add a menu item called "Inspect Ember Component" that shows info
* about the component in the inspector.
* @param {Boolean} force don't use the activeTabs array to check for an existing context menu
*/
function updateContextMenu(force) {
// Only add context menu item when an Ember app has been detected
var isEmberApp = !!activeTabs[activeTabId] || force;
if (!isEmberApp && contextMenuAdded) {
chrome.contextMenus.remove('inspect-ember-component');
contextMenuAdded = false;
}

if (isEmberApp && !contextMenuAdded) {
chrome.contextMenus.create({
id: 'inspect-ember-component',
title: 'Inspect Ember Component',
contexts: ['all'],
onclick: function() {
chrome.tabs.sendMessage(activeTabId, {
from: 'devtools',
type: 'view:contextMenu'
});
}
});
contextMenuAdded = true;
}
}

/**
* Listen for a connection request from the EmberInspector.
* When the EmberInspector connects to the extension a messageListener
Expand Down Expand Up @@ -113,29 +148,37 @@
} else if (request && request.type === 'emberVersion') {
// set the version info and update title
activeTabs[sender.tab.id] = request.versions;

updateTabAction(sender.tab.id);
updateContextMenu();
} else if (request && request.type === 'resetEmberIcon') {
// hide the Tomster icon
hideAction(sender.tab.id);
} else if (request && request.type === 'inspectorLoaded') {
updateContextMenu(true);
} else {
// forward the message to EmberInspector
var emberInspectorChromePort = emberInspectorChromePorts[sender.tab.id];
if (emberInspectorChromePort) { emberInspectorChromePort.postMessage(request); }
}
});



/**
* Event listener for when the tab is updated, usually reloaded.
* Check to see if a ClientApp exists for this tab, and reset the icon
* to show the latest data.
* @param {Number} tabId - the current tab
* Keep track of which browser tab is active and update the context menu.
*/
chrome.tabs.onUpdated.addListener(function(tabId) {
chrome.tabs.onActivated.addListener(({ tabId }) => {
activeTabId = tabId;
if (activeTabs[tabId]) {
updateTabAction(tabId);
}
updateContextMenu();
});

/**
* Only keep track of active tabs
*/
chrome.tabs.onRemoved.addListener(({ tabId }) => {
delete activeTabs[tabId];
});

}());
3 changes: 2 additions & 1 deletion skeletons/web-extension/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@

"permissions": [
"<all_urls>",
"storage"
"storage",
"contextMenus"
],

"content_security_policy": "script-src 'self'; object-src 'self'",
Expand Down
Loading