siesta-lite
Version:
Stress-free JavaScript unit testing and functional testing tool, works in NodeJS and browsers
331 lines (245 loc) • 11.9 kB
JavaScript
/*
Siesta 5.6.1
Copyright(c) 2009-2022 Bryntum AB
https://bryntum.com/contact
https://bryntum.com/products/siesta/license
*/
Ext.define('Siesta.Project.Browser.UI.ComponentInspector', {
extend : 'Ext.util.Observable',
inspectedComponent : null,
inspectedComponentXType : null,
boxIndicatorEl : null,
active : false,
window : null,
trackMouse : true,
showSelectorText : true,
bufferTime : 30,
injectIntoTestFrame : false,
getIndicatorEl : function () {
return this.boxIndicatorEl;
},
getExt : function () {
return this.window.Ext;
},
start : function (window, containerEl) {
window = window || this.window;
if (!window) throw 'Must provide a window context for the inspector';
this.window = window;
var _Ext = this.getExt();
var me = this;
var wrap = this.injectIntoTestFrame ? window.document.body : (containerEl || window.document.body);
var boxSizingStyle = 'box-sizing: border-box;-moz-box-sizing: border-box;-ms-box-sizing: border-box;-webkit-box-sizing: border-box;';
me.boxIndicatorEl = me.boxIndicatorEl || Ext.fly(wrap).createChild({
cls : 'target-inspector-box',
style : 'position : absolute;left:0;top:0;pointer-events : none;z-index : 100001; border : 2px solid red;transition-property : transform,border-color;transition-duration : 0.3s;',
children : [
{
tag : 'div',
style : 'border:1px solid;border-color:inherit;width : 13px; height : 13px;position : absolute;margin-top : -8px;margin-left : -8px;border-radius:19px;transition-property : transform;transition-duration : 0.3s;' + boxSizingStyle,
cls : 'target-inspector-coordinate',
html : '<div style="border-left: 1px solid;border-color:inherit;position: absolute;top: -3px;left: 5px;height: 6px;' + boxSizingStyle +'"></div>' +
'<div style="border-left: 1px solid;border-color:inherit;position: absolute;bottom: -3px;left: 5px;height: 6px;' + boxSizingStyle +'"></div>' +
'<div style="border-top: 1px solid;border-color:inherit;position: absolute;top: 5px;left: -3px;width: 6px;' + boxSizingStyle +'"></div>' +
'<div style="border-top: 1px solid;border-color:inherit;position: absolute;top: 5px;right: -3px;width: 6px;' + boxSizingStyle +'"></div>'
}
].concat(this.showSelectorText ? {
tag : 'a',
cls : 'target-inspector-label',
target : '_blank'
} : [])
});
if (this.trackMouse && _Ext && _Ext.getBody) {
this.toggleMouseMoveListener(true);
_Ext.getBody().on('click', this.onInspectionClick, { me : this });
}
this.fireEvent('start', this);
this.active = true;
},
stop : function (suppressEvent) {
if (!this.active) return;
this.active = false;
var _Ext = this.getExt();
Ext.destroy(this.boxIndicatorEl);
this.boxIndicatorEl = null;
if (!suppressEvent) {
this.fireEvent('stop', this);
}
if (_Ext && _Ext.getBody) {
this.toggleMouseMoveListener(false);
_Ext.getBody().un('click', this.onInspectionClick, { me : this });
}
this.inspectedComponent = this.inspectedComponentXType = null;
},
// Listen for mousemove in the frame and any direct iframe children too
toggleMouseMoveListener : function (enabled) {
var _Ext = this.getExt();
if (!_Ext) return;
var frames = _Ext.getBody().select('iframe');
var fn = enabled ? 'on' : 'un';
// Avoid using "this" directly due to Touch incompatibilities with Ext
_Ext.getBody()[fn]('mousemove', this.onMouseMove, { me : this }, { buffer : this.bufferTime });
for (var i = 0; i < frames.getCount(); i++) {
var innerExt = frames.item(i).dom.contentWindow.Ext;
// Avoid using "this" directly due to Touch incompatibilities with Ext
innerExt && innerExt.getBody && innerExt.getBody()[fn]('mousemove', this.onMouseMove, { me : this }, { buffer : this.bufferTime });
}
},
onInspectionClick : function (e, t) {
if (!this.boxIndicatorEl) return;
// Avoid using "this" directly due to Touch incompatibilities with Ext
var me = this.me;
me.toggleMouseMoveListener(false);
// If user clicks on a non-component, or clicking outside currently selected component - we abort
if (!me.inspectedComponent || me.findComponentByTarget(t) !== me.inspectedComponent) {
me.stop();
} else {
me.fireEvent('targetselected', me, me.inspectedComponent, me.inspectedComponentXType);
}
},
onMouseMove : function (e, t) {
//Have to avoid using "this" directly due to Touch incompatibilities with Ext
var me = this.me;
if (!me.boxIndicatorEl) return;
var cmp = me.findComponentByTarget(t);
if (cmp) {
if (cmp === me.inspectedComponent) return;
var xtype = me.resolveComponentXtype(cmp);
me.inspectedComponent = cmp;
me.inspectedComponentXType = xtype;
me.highlightTarget(cmp.el.dom);
me.updateHighlightContent(cmp, xtype);
me.fireEvent('targethover', me, me.inspectedComponent, me.inspectedComponentXType);
}
},
resolveComponentXtype : function (cmp) {
var xtype = (cmp.getXType && cmp.getXType()) || cmp.xtype;
// If the found component doesn't have an own xtype, look up the superclass chain to find one
if (!xtype) {
var cls = cmp;
for (var i = 0; i < 10 && !xtype; i++) {
cls = cmp.superclass;
xtype = cls.xtype;
}
}
return xtype;
},
updateHighlightContent : function (cmp, xtype) {
var html;
var link = {
tag : 'a',
cls : 'target-inspector-label',
href : '#'
};
if (typeof cmp === 'string') {
html = cmp;
} else if (Ext.ClassManager) {
// If recorder is visible, let's add some targeting suggestions
var recorderPanel = Ext.ComponentQuery.query('recorderpanel')[0];
var targetWindow = cmp.el.dom.ownerDocument.defaultView;
var pageExtHasCQ = targetWindow && targetWindow.Ext && targetWindow.Ext.ComponentQuery;
if (recorderPanel && recorderPanel.isVisible()) {
var cq;
// Make sure this is an Ext JS page and the recorder used is the Ext JS recorder (not the generic one)
if (pageExtHasCQ && recorderPanel.recorder.extractor.findComponentQueryFor) {
cq = recorderPanel.recorder.extractor.findComponentQueryFor(cmp);
cq = (cq && cq.query) || xtype;
}
html = '>>' + (cq || xtype);
} else {
var clsName = this.findExtAncestorClassName(cmp);
if (clsName) {
var docsPath = Siesta.Resource('Siesta.Project.Browser.UI.DomContainer', 'docsUrlText');
var framework;
if (Ext.versions.touch) {
framework = 'touch';
} else {
framework = 'extjs';
}
link.target = '_blank';
link.href = Ext.String.format(docsPath, framework, clsName);
link.title = Siesta.Resource('Siesta.Project.Browser.UI.DomContainer', 'viewDocsText') + clsName;
}
html = xtype;
}
}
if (html) {
link.html = html;
var linkEl = this.boxIndicatorEl.down('.target-inspector-label', true);
linkEl.href = link.href;
linkEl.title = link.title;
linkEl.innerHTML = html;
}
},
highlightTarget : function (node, content, point) {
var boxStyle = this.boxIndicatorEl.dom.style;
if (!this.active) {
this.start();
}
if (node) {
var offsets = this.getOffsets(node);
var left = (Ext.fly(node).getX() - 2 + offsets[0]);
var top = (Ext.fly(node).getY() - 2 + offsets[1]);
var width = ((Ext.fly(node).getWidth() || (parseInt(node.style.width.substring(0, node.style.width.length - 2), 10))) + 2);
var height = ((Ext.fly(node).getHeight() || (parseInt(node.style.height.substring(0, node.style.height.length - 2), 10))) + 2);
if (this.injectIntoTestFrame) {
left += this.window.document.body.scrollLeft;
top += this.window.document.body.scrollTop;
}
var translateStyle = bowser.opera ? ('translate(' + left + 'px,' + top + 'px)') :
('translate3d(' + left + 'px,' + top + 'px, 0)');
// Regular getWidth/getHeight doesn't work if another iframe is on the page
boxStyle.setProperty('transform', translateStyle)
boxStyle.width = width + 'px';
boxStyle.height = height + 'px';
boxStyle['border-color'] = 'red';
if (this.showSelectorText && content) {
this.updateHighlightContent(content);
}
} else {
boxStyle['border-color'] = '#ccc';
}
var crosshair = this.boxIndicatorEl.down('.target-inspector-coordinate', true);
if (point) {
translateStyle = bowser.opera ? ('translate(' + point[0] + 'px,' + point[1] + 'px)') :
('translate3d(' + point[0] + 'px, ' + point[1] + 'px, 0)');
crosshair.style.setProperty('display', 'block');
crosshair.style.setProperty('transform', translateStyle)
} else {
crosshair.style.setProperty('display', 'none');
}
},
findComponentByTarget : function (target) {
var Ext = this.getExt();
var testDoc = this.window.document;
// Handle potentially having another Ext copy loaded in another frame
if (target.ownerDocument !== testDoc) {
var innerFrame = (target.ownerDocument.parentWindow || target.ownerDocument.defaultView).frameElement;
Ext = innerFrame.contentWindow.Ext;
}
var cmp
while (!cmp && target && target.nodeName !== 'BODY') {
cmp = Ext.getCmp(target.id);
target = target.parentNode;
}
return cmp;
},
getOffsets : function (node) {
var targetDoc = this.window.document;
var offsets = [0, 0]
if (node.ownerDocument !== targetDoc) {
var innerFrame = (node.ownerDocument.parentWindow || node.ownerDocument.defaultView).frameElement;
offsets = Ext.fly(innerFrame).getXY();
offsets[0] -= node.ownerDocument.body.scrollLeft;
offsets[1] -= node.ownerDocument.body.scrollTop;
}
return offsets;
},
findExtAncestorClassName : function (cmp) {
while (cmp) {
var name = Ext.ClassManager.getName(cmp);
if (name.match(/^Ext./)) return name;
cmp = cmp.superclass;
}
return '';
}
});