refactor(devtools): run ng-dev format on angular devtools files

Formats the entire devtools directory with the ng-dev formatting tool. Previously we relied on prettier, so this commit also remove prettier from devtools' dependencies.
This commit is contained in:
AleksanderBodurri
2021-12-09 00:44:17 -05:00
parent ac387e2055
commit 8b84412d76
214 changed files with 2910 additions and 3079 deletions
-1
View File
@@ -12,7 +12,6 @@ export const format: FormatConfig = {
'clang-format': {
'matchers': [
'**/*.{js,ts}',
'!devtools/**',
// TODO: burn down format failures and remove aio and integration exceptions.
'!aio/**',
'!integration/**',
@@ -15,6 +15,9 @@ describe('Comment nodes', () => {
it('should find comment nodes when the setting is enabled', () => {
showComments();
cy.get('.tree-wrapper').find('.tree-node:contains("#comment")').its('length').should('not.eq', 0);
cy.get('.tree-wrapper')
.find('.tree-node:contains("#comment")')
.its('length')
.should('not.eq', 0);
});
});
@@ -10,24 +10,28 @@ describe('Tracking items from application to component tree', () => {
getBody().find('app-todo').contains('Buy milk');
});
cy.get('.tree-wrapper').find('.tree-node:contains("app-todo[TooltipDirective]")').its('length').should('eq', 2);
cy.get('.tree-wrapper')
.find('.tree-node:contains("app-todo[TooltipDirective]")')
.its('length')
.should('eq', 2);
});
it('should be able to detect a new todo from user and add it to the tree', () => {
cy.enter('#sample-app')
.then((getBody) => {
getBody().find('input.new-todo').type('Buy cookies{enter}');
})
.then(() => {
cy.enter('#sample-app').then((getBody) => {
getBody().find('app-todo').contains('Buy milk');
.then((getBody) => {
getBody().find('input.new-todo').type('Buy cookies{enter}');
})
.then(() => {
cy.enter('#sample-app').then((getBody) => {
getBody().find('app-todo').contains('Buy milk');
getBody().find('app-todo').contains('Build something fun!');
getBody().find('app-todo').contains('Build something fun!');
getBody().find('app-todo').contains('Buy cookies');
getBody().find('app-todo').contains('Buy cookies');
});
});
});
cy.get('.tree-wrapper .tree-node:contains("app-todo[TooltipDirective]")').should('have.length', 3);
cy.get('.tree-wrapper .tree-node:contains("app-todo[TooltipDirective]")')
.should('have.length', 3);
});
});
@@ -3,7 +3,7 @@ function checkSearchedNodesLength(type, length) {
}
function inputSearchText(text) {
cy.get('.filter-input').type(text, { force: true });
cy.get('.filter-input').type(text, {force: true});
}
function checkComponentName(name) {
@@ -75,9 +75,9 @@ describe('Search items in component tree', () => {
const amountOfBreadcrumbButtons = 4;
const amountOfScrollButtons = 2;
cy.get('ng-breadcrumbs')
.find('button')
.its('length')
.should('eq', amountOfScrollButtons + amountOfBreadcrumbButtons);
.find('button')
.its('length')
.should('eq', amountOfScrollButtons + amountOfBreadcrumbButtons);
// should display correct text in explorer panel
checkComponentName('app-todos');
@@ -90,7 +90,7 @@ describe('Search items in component tree', () => {
});
it('should focus search input when search icon is clicked', () => {
cy.get('.filter label .search-icon').click({ force: true });
cy.get('.filter label .search-icon').click({force: true});
cy.get('.filter label input').should('have.focus');
});
});
@@ -9,7 +9,10 @@ describe('node selection', () => {
it('should deselect node if it is no longer on the page', () => {
cy.get('.tree-wrapper').get('.tree-node.selected').should('not.exist');
cy.get('.tree-wrapper').find('.tree-node:contains("app-todo[TooltipDirective]")').first().click({ force: true });
cy.get('.tree-wrapper')
.find('.tree-node:contains("app-todo[TooltipDirective]")')
.first()
.click({force: true});
cy.get('.tree-wrapper').find('.tree-node.selected').its('length').should('eq', 1);
@@ -27,7 +30,10 @@ describe('node selection', () => {
getBody().find('input.new-todo').type('Buy cookies{enter}');
});
cy.get('.tree-wrapper').find('.tree-node:contains("app-todo[TooltipDirective]")').last().click({ force: true });
cy.get('.tree-wrapper')
.find('.tree-node:contains("app-todo[TooltipDirective]")')
.last()
.click({force: true});
cy.enter('#sample-app').then((getBody) => {
getBody().find('app-todo:contains("Buy milk")').find('.destroy').click();
@@ -37,94 +43,87 @@ describe('node selection', () => {
});
it('should select nodes with same name', () => {
cy.get('.tree-wrapper').find('.tree-node:contains("app-todo[TooltipDirective]")').first().click({ force: true });
cy.get('.tree-wrapper')
.find('.tree-node:contains("app-todo[TooltipDirective]")')
.first()
.click({force: true});
cy.get('.tree-wrapper').find('.tree-node:contains("app-todo[TooltipDirective]")').last().click({ force: true });
cy.get('.tree-wrapper')
.find('.tree-node:contains("app-todo[TooltipDirective]")')
.last()
.click({force: true});
cy.get('ng-property-view').last().find('mat-tree-node:contains("todo")').click();
cy.get('ng-property-view')
.last()
.find('mat-tree-node:contains("Build something fun!")')
.its('length')
.should('eq', 1);
.last()
.find('mat-tree-node:contains("Build something fun!")')
.its('length')
.should('eq', 1);
});
});
describe('breadcrumb logic', () => {
it('should overflow when breadcrumb list is long enough', () => {
cy.get('.tree-wrapper')
.find('.tree-node:contains("div[TooltipDirective]")')
.last()
.click({ force: true })
.then(() => {
cy.get('ng-breadcrumbs')
.find('.breadcrumbs')
.then((breadcrumbsContainer) => {
const hasOverflowX = () => breadcrumbsContainer[0].scrollWidth > breadcrumbsContainer[0].clientWidth;
.find('.tree-node:contains("div[TooltipDirective]")')
.last()
.click({force: true})
.then(() => {
cy.get('ng-breadcrumbs').find('.breadcrumbs').then((breadcrumbsContainer) => {
const hasOverflowX = () =>
breadcrumbsContainer[0].scrollWidth > breadcrumbsContainer[0].clientWidth;
expect(hasOverflowX()).to.be.true;
});
});
});
});
it('should scroll right when right scroll button is clicked', () => {
cy.get('.tree-wrapper')
.find('.tree-node:contains("div[TooltipDirective]")')
.last()
.click({ force: true })
.then(() => {
cy.get('ng-breadcrumbs')
.find('.breadcrumbs')
.then((el) => {
el[0].style.scrollBehavior = 'auto';
})
.then((breadcrumbsContainer) => {
const scrollLeft = () => breadcrumbsContainer[0].scrollLeft;
expect(scrollLeft()).to.eql(0);
.find('.tree-node:contains("div[TooltipDirective]")')
.last()
.click({force: true})
.then(() => {
cy.get('ng-breadcrumbs')
.find('.breadcrumbs')
.then((el) => {
el[0].style.scrollBehavior = 'auto';
})
.then((breadcrumbsContainer) => {
const scrollLeft = () => breadcrumbsContainer[0].scrollLeft;
expect(scrollLeft()).to.eql(0);
cy.get('ng-breadcrumbs')
.find('.scroll-button')
.last()
.click()
.then(() => {
expect(scrollLeft()).to.be.greaterThan(0);
cy.get('ng-breadcrumbs').find('.scroll-button').last().click().then(() => {
expect(scrollLeft()).to.be.greaterThan(0);
});
});
});
});
});
});
it('should scroll left when left scroll button is clicked', () => {
cy.get('.tree-wrapper')
.find('.tree-node:contains("div[TooltipDirective]")')
.last()
.click({ force: true })
.then(() => {
cy.get('ng-breadcrumbs')
.find('.breadcrumbs')
.then((el) => {
el[0].style.scrollBehavior = 'auto';
})
.then((breadcrumbsContainer) => {
const scrollLeft = () => breadcrumbsContainer[0].scrollLeft;
expect(scrollLeft()).to.eql(0);
.find('.tree-node:contains("div[TooltipDirective]")')
.last()
.click({force: true})
.then(() => {
cy.get('ng-breadcrumbs')
.find('.breadcrumbs')
.then((el) => {
el[0].style.scrollBehavior = 'auto';
})
.then((breadcrumbsContainer) => {
const scrollLeft = () => breadcrumbsContainer[0].scrollLeft;
expect(scrollLeft()).to.eql(0);
cy.get('ng-breadcrumbs')
.find('.scroll-button')
.last()
.click()
.then(() => {
expect(scrollLeft()).to.be.greaterThan(0);
cy.get('ng-breadcrumbs').find('.scroll-button').last().click().then(() => {
expect(scrollLeft()).to.be.greaterThan(0);
cy.get('ng-breadcrumbs')
.find('.scroll-button')
.first()
.click()
.then(() => {
cy.get('ng-breadcrumbs').find('.scroll-button').first().click().then(() => {
expect(scrollLeft()).to.eql(0);
});
});
});
});
});
});
});
});
});
@@ -8,7 +8,10 @@ describe('edit properties of directive in the property view tab', () => {
describe('edit app-todo component', () => {
beforeEach(() => {
// select todo node in component tree
cy.get('.tree-wrapper').find('.tree-node:contains("app-todo[TooltipDirective]")').first().click({ force: true });
cy.get('.tree-wrapper')
.find('.tree-node:contains("app-todo[TooltipDirective]")')
.first()
.click({force: true});
});
it('should be able to enable editMode', () => {
@@ -17,13 +20,13 @@ describe('edit properties of directive in the property view tab', () => {
});
cy.get('.explorer-panel:contains("app-todo")')
.find('ng-property-view mat-tree-node:contains("editMode")')
.find('ng-property-editor .editor')
.click({ force: true })
.find('.editor-input')
.clear()
.type('true')
.type('{enter}');
.find('ng-property-view mat-tree-node:contains("editMode")')
.find('ng-property-editor .editor')
.click({force: true})
.find('.editor-input')
.clear()
.type('true')
.type('{enter}');
cy.enter('#sample-app').then((getBody) => {
getBody().find('app-todo input.edit').should('be.visible');
@@ -33,7 +36,9 @@ describe('edit properties of directive in the property view tab', () => {
describe('edit todo property', () => {
beforeEach(() => {
// expand todo state
cy.get('.explorer-panel:contains("app-todo")').find('ng-property-view mat-tree-node:contains("todo")').click();
cy.get('.explorer-panel:contains("app-todo")')
.find('ng-property-view mat-tree-node:contains("todo")')
.click();
});
it('should change todo label in app when edited', () => {
@@ -44,13 +49,13 @@ describe('edit properties of directive in the property view tab', () => {
// find label variable and run through edit logic
cy.get('.explorer-panel:contains("app-todo")')
.find('ng-property-view mat-tree-node:contains("label")')
.find('ng-property-editor .editor')
.click()
.find('.editor-input')
.clear()
.type('Buy cookies')
.type('{enter}');
.find('ng-property-view mat-tree-node:contains("label")')
.find('ng-property-editor .editor')
.click()
.find('.editor-input')
.clear()
.type('Buy cookies')
.type('{enter}');
// assert that the page has been updated
cy.enter('#sample-app').then((getBody) => {
@@ -66,13 +71,13 @@ describe('edit properties of directive in the property view tab', () => {
// find completed variable and run through edit logic
cy.get('.explorer-panel:contains("app-todo")')
.find('ng-property-view mat-tree-node:contains("completed")')
.find('ng-property-editor .editor')
.click()
.find('.editor-input')
.clear()
.type('true')
.type('{enter}');
.find('ng-property-view mat-tree-node:contains("completed")')
.find('ng-property-editor .editor')
.click()
.find('.editor-input')
.clear()
.type('true')
.type('{enter}');
// assert that the page has been updated
cy.enter('#sample-app').then((getBody) => {
@@ -12,17 +12,21 @@ describe('change of the state should reflect in property update', () => {
});
// Select the todo item
cy.get('.tree-wrapper').find('.tree-node:contains("app-todo[TooltipDirective]")').first().click({ force: true });
cy.get('.tree-wrapper')
.find('.tree-node:contains("app-todo[TooltipDirective]")')
.first()
.click({force: true});
// Expand the todo in the property explorer
cy.get('.explorer-panel:contains("app-todo")').find('ng-property-view mat-tree-node:contains("todo")').click();
cy.get('.explorer-panel:contains("app-todo")')
.find('ng-property-view mat-tree-node:contains("todo")')
.click();
// Verify its value is now completed
cy.contains(
'.explorer-panel:contains("app-todo") ' +
'ng-property-view mat-tree-node:contains("completed") ' +
'ng-property-editor .editor',
'true'
);
'.explorer-panel:contains("app-todo") ' +
'ng-property-view mat-tree-node:contains("completed") ' +
'ng-property-editor .editor',
'true');
});
});
@@ -1,5 +1,5 @@
const prepareHeaderExpansionPanelForAssertions = (selector) => {
cy.get('.tree-wrapper').find(selector).first().click({ force: true });
cy.get('.tree-wrapper').find(selector).first().click({force: true});
cy.get('.element-header .component-name').click();
};
@@ -9,34 +9,37 @@ describe('Viewing component metadata', () => {
});
describe('viewing TodoComponent', () => {
beforeEach(() => prepareHeaderExpansionPanelForAssertions('.tree-node:contains("app-todo[TooltipDirective]")'));
beforeEach(
() => prepareHeaderExpansionPanelForAssertions(
'.tree-node:contains("app-todo[TooltipDirective]")'));
it('should display view encapsulation', () => {
cy.contains('.meta-data-container .mat-button:first', 'View Encapsulation: Emulated')
});
it('should display view encapsulation',
() => {
cy.contains('.meta-data-container .mat-button:first', 'View Encapsulation: Emulated')});
it('should display change detection strategy', () => {
cy.contains('.meta-data-container .mat-button:last', 'Change Detection Strategy: OnPush')
});
it('should display change detection strategy',
() => {cy.contains(
'.meta-data-container .mat-button:last', 'Change Detection Strategy: OnPush')});
});
describe('viewing DemoAppComponent', () => {
beforeEach(() => prepareHeaderExpansionPanelForAssertions('.tree-node:contains("app-demo-component")'));
beforeEach(
() =>
prepareHeaderExpansionPanelForAssertions('.tree-node:contains("app-demo-component")'));
it('should display view encapsulation', () => {
cy.contains('.meta-data-container .mat-button:first', 'View Encapsulation: None')
});
it('should display view encapsulation',
() => {cy.contains('.meta-data-container .mat-button:first', 'View Encapsulation: None')});
it('should display change detection strategy', () => {
cy.contains('.meta-data-container .mat-button:last', 'Change Detection Strategy: Default')
});
it('should display change detection strategy',
() => {cy.contains(
'.meta-data-container .mat-button:last', 'Change Detection Strategy: Default')});
it('should display correct set of inputs', () => {
cy.contains('.cy-inputs', '@Inputs');
cy.contains('.cy-inputs mat-tree-node:first span:first', 'inputOne');
cy.contains('.cy-inputs mat-tree-node:last span:first', 'inputTwo');
});
it('should display correct set of outputs', () => {
cy.contains('.cy-outputs', '@Outputs');
cy.contains('.cy-outputs mat-tree-node:first span:first', 'outputOne');
+2 -2
View File
@@ -2,7 +2,7 @@
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const { SpecReporter } = require('jasmine-spec-reporter');
const {SpecReporter} = require('jasmine-spec-reporter');
/**
* @type { import("protractor").Config }
@@ -25,6 +25,6 @@ exports.config = {
require('ts-node').register({
project: require('path').join(__dirname, './tsconfig.json'),
});
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
jasmine.getEnv().addReporter(new SpecReporter({spec: {displayStacktrace: true}}));
},
};
+7 -11
View File
@@ -1,5 +1,6 @@
import { AppPage } from './app.po';
import { browser, logging } from 'protractor';
import {browser, logging} from 'protractor';
import {AppPage} from './app.po';
describe('workspace-project App', () => {
let page: AppPage;
@@ -15,14 +16,9 @@ describe('workspace-project App', () => {
afterEach(async () => {
// Assert that there are no errors emitted from the browser
const logs = await browser
.manage()
.logs()
.get(logging.Type.BROWSER);
expect(logs).not.toContain(
jasmine.objectContaining({
level: logging.Level.SEVERE,
} as logging.Entry)
);
const logs = await browser.manage().logs().get(logging.Type.BROWSER);
expect(logs).not.toContain(jasmine.objectContaining({
level: logging.Level.SEVERE,
} as logging.Entry));
});
});
+1 -1
View File
@@ -1,4 +1,4 @@
import { browser, by, element } from 'protractor';
import {browser, by, element} from 'protractor';
export class AppPage {
navigateTo(): Promise<any> {
+2 -2
View File
@@ -1,7 +1,7 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
module.exports = function(config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
@@ -13,7 +13,7 @@ module.exports = function (config) {
require('@angular-devkit/build-angular/plugins/karma'),
],
client: {
clearContext: false, // leave Jasmine Spec Runner output visible in browser
clearContext: false, // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, './coverage/angular-devtools'),
+1 -11
View File
@@ -22,14 +22,7 @@
"start:ci": "bazelisk run src:devserver",
"cy:ci": "start-server-and-test start:ci http-get://localhost:4200 cy:run",
"test:ci": "bazelisk test //...",
"build:chrome:ci": "bazelisk build projects/shell-chrome/src:prodapp",
"prettier": "prettier --write \"{,!(node_modules|dist|build|coverage)/**/}*.{js,jsx,ts,tsx,json}\"",
"prettier:fix": "pretty-quick --staged"
},
"husky": {
"hooks": {
"pre-commit": "yarn prettier:fix"
}
"build:chrome:ci": "bazelisk build projects/shell-chrome/src:prodapp"
},
"private": true,
"dependencies": {
@@ -93,7 +86,6 @@
"document-register-element": "^1.7.2",
"history-server": "^1.3.1",
"html-insert-assets": "^0.14.2",
"husky": "^4.3.8",
"jasmine-core": "~3.10.0",
"jasmine-spec-reporter": "~7.0.0",
"karma": "~4.1.0",
@@ -105,8 +97,6 @@
"karma-sourcemap-loader": "0.3.7",
"ng-packagr": "^12.0.0",
"ngx-build-plus": "^11.0.0",
"prettier": "^2.0.0",
"pretty-quick": "^3.0.0",
"protractor": "~7.0.0",
"requirejs": "2.3.6",
"rollup": "2.44.0",
@@ -2,7 +2,7 @@
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const { SpecReporter } = require('jasmine-spec-reporter');
const {SpecReporter} = require('jasmine-spec-reporter');
/**
* @type { import("protractor").Config }
@@ -19,12 +19,12 @@ exports.config = {
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
print: function () {},
print: function() {},
},
onPrepare() {
require('ts-node').register({
project: require('path').join(__dirname, './tsconfig.json'),
});
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
jasmine.getEnv().addReporter(new SpecReporter({spec: {displayStacktrace: true}}));
},
};
@@ -1,5 +1,6 @@
import { AppPage } from './app.po';
import { browser, logging } from 'protractor';
import {browser, logging} from 'protractor';
import {AppPage} from './app.po';
describe('workspace-project App', () => {
let page: AppPage;
@@ -16,10 +17,8 @@ describe('workspace-project App', () => {
afterEach(async () => {
// Assert that there are no errors emitted from the browser
const logs = await browser.manage().logs().get(logging.Type.BROWSER);
expect(logs).not.toContain(
jasmine.objectContaining({
level: logging.Level.SEVERE,
} as logging.Entry)
);
expect(logs).not.toContain(jasmine.objectContaining({
level: logging.Level.SEVERE,
} as logging.Entry));
});
});
@@ -1,4 +1,4 @@
import { browser, by, element } from 'protractor';
import {browser, by, element} from 'protractor';
export class AppPage {
navigateTo(): Promise<any> {
+2 -2
View File
@@ -1,7 +1,7 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
module.exports = function(config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
@@ -13,7 +13,7 @@ module.exports = function (config) {
require('@angular-devkit/build-angular/plugins/karma'),
],
client: {
clearContext: true, // leave Jasmine Spec Runner output visible in browser
clearContext: true, // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, '../../coverage/demo-no-zone'),
@@ -1,4 +1,4 @@
import { Component, ChangeDetectorRef } from '@angular/core';
import {ChangeDetectorRef, Component} from '@angular/core';
@Component({
selector: 'app-root',
@@ -1,7 +1,7 @@
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import {NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import { AppComponent } from './app.component';
import {AppComponent} from './app.component';
@NgModule({
declarations: [AppComponent],
@@ -9,4 +9,5 @@ import { AppComponent } from './app.component';
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}
export class AppModule {
}
+6 -6
View File
@@ -1,9 +1,9 @@
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import {AppModule} from './app/app.module';
platformBrowserDynamic()
.bootstrapModule(AppModule, {
ngZone: 'noop',
})
.catch((err) => console.error(err));
.bootstrapModule(AppModule, {
ngZone: 'noop',
})
.catch((err) => console.error(err));
@@ -41,9 +41,10 @@
*
* The following flags will work for all browsers.
*
* (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
* (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
* (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
* (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch
* requestAnimationFrame (window as any).__Zone_disable_on_property = true; // disable patch
* onProperty such as onclick (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll',
* 'mousemove']; // disable patch specified eventNames
*
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
* with the following flag, it will bypass `zone.js` patch for IE/Edge
@@ -1,4 +1,4 @@
const { nodeResolve } = require('@rollup/plugin-node-resolve');
const {nodeResolve} = require('@rollup/plugin-node-resolve');
const commonjs = require('@rollup/plugin-commonjs');
module.exports = {
+3 -2
View File
@@ -1,8 +1,9 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js/dist/zone-testing';
import { getTestBed } from '@angular/core/testing';
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
import {getTestBed} from '@angular/core/testing';
import {BrowserDynamicTestingModule, platformBrowserDynamicTesting} from '@angular/platform-browser-dynamic/testing';
declare const require: any;
@@ -1,7 +1,7 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
module.exports = function(config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
@@ -13,7 +13,7 @@ module.exports = function (config) {
require('@angular-devkit/build-angular/plugins/karma'),
],
client: {
clearContext: true, // leave Jasmine Spec Runner output visible in browser
clearContext: true, // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, '../../coverage/ng-devtools-backend'),
@@ -1,12 +1,7 @@
import {
appIsAngularInDevMode,
appIsAngularIvy,
appIsAngular,
appIsSupportedAngularVersion,
getAngularVersion,
} from './angular-check';
import {appIsAngular, appIsAngularInDevMode, appIsAngularIvy, appIsSupportedAngularVersion, getAngularVersion,} from './angular-check';
const setNgVersion = (version = '12.0.0'): void => document.documentElement.setAttribute('ng-version', version);
const setNgVersion = (version = '12.0.0'): void =>
document.documentElement.setAttribute('ng-version', version);
const removeNgVersion = (): void => document.documentElement.removeAttribute('ng-version');
describe('angular-check', () => {
@@ -54,7 +49,7 @@ describe('angular-check', () => {
describe('appIsAngularIvy', () => {
it('should not recognize VE apps', () => {
(window as any).ng = {
probe(): void {},
probe(): void{},
};
setNgVersion();
expect(appIsAngularIvy()).toBeFalse();
@@ -82,7 +77,7 @@ describe('angular-check', () => {
it('should detect VE apps', () => {
(window as any).ng = {
probe(): void {},
probe(): void{},
};
setNgVersion();
@@ -91,7 +86,7 @@ describe('angular-check', () => {
it('should detect Ivy apps', () => {
(window as any).ng = {
getComponent(): void {},
getComponent(): void{},
};
setNgVersion();
expect(appIsAngularInDevMode()).toBeTrue();
@@ -34,10 +34,11 @@ export const appIsSupportedAngularVersion = (): boolean => {
* @returns if the app has global ng debug object
*/
const appHasGlobalNgDebugObject = (): boolean => {
return typeof ng === 'object' && (typeof ng.getComponent === 'function' || typeof ng.probe === 'function');
return typeof ng === 'object' &&
(typeof ng.getComponent === 'function' || typeof ng.probe === 'function');
};
export const getAngularVersion = (): string | null => {
export const getAngularVersion = (): string|null => {
const el = document.querySelector('[ng-version]');
if (!el) {
return null;
@@ -1,35 +1,22 @@
import {
DirectivePosition,
ElementPosition,
Events,
MessageBus,
DevToolsNode,
DirectiveType,
ComponentType,
ProfilerFrame,
ComponentExplorerViewQuery,
} from 'protocol';
import { ComponentTreeNode } from './interfaces';
import { getLatestComponentState, queryDirectiveForest, updateState } from './component-tree';
import { start as startProfiling, stop as stopProfiling } from './hooks/capture';
import { serializeDirectiveState } from './state-serializer/state-serializer';
import { ComponentInspector } from './component-inspector/component-inspector';
import { setConsoleReference } from './set-console-reference';
import { unHighlight } from './highlighter';
import {
getAngularVersion,
appIsAngularInDevMode,
appIsSupportedAngularVersion,
appIsAngularIvy,
} from './angular-check';
import { debounceTime } from 'rxjs/operators';
import { disableTimingAPI, enableTimingAPI, initializeOrGetDirectiveForestHooks } from './hooks';
import { runOutsideAngular } from './utils';
import {ComponentExplorerViewQuery, ComponentType, DevToolsNode, DirectivePosition, DirectiveType, ElementPosition, Events, MessageBus, ProfilerFrame,} from 'protocol';
import {debounceTime} from 'rxjs/operators';
import {appIsAngularInDevMode, appIsAngularIvy, appIsSupportedAngularVersion, getAngularVersion,} from './angular-check';
import {ComponentInspector} from './component-inspector/component-inspector';
import {getLatestComponentState, queryDirectiveForest, updateState} from './component-tree';
import {unHighlight} from './highlighter';
import {disableTimingAPI, enableTimingAPI, initializeOrGetDirectiveForestHooks} from './hooks';
import {start as startProfiling, stop as stopProfiling} from './hooks/capture';
import {ComponentTreeNode} from './interfaces';
import {setConsoleReference} from './set-console-reference';
import {serializeDirectiveState} from './state-serializer/state-serializer';
import {runOutsideAngular} from './utils';
export const subscribeToClientEvents = (messageBus: MessageBus<Events>): void => {
messageBus.on('shutdown', shutdownCallback(messageBus));
messageBus.on('getLatestComponentExplorerView', getLatestComponentExplorerViewCallback(messageBus));
messageBus.on(
'getLatestComponentExplorerView', getLatestComponentExplorerViewCallback(messageBus));
messageBus.on('queryNgAvailability', checkForAngularCallback(messageBus));
@@ -54,8 +41,8 @@ export const subscribeToClientEvents = (messageBus: MessageBus<Events>): void =>
// once every 250ms
runOutsideAngular(() => {
initializeOrGetDirectiveForestHooks()
.profiler.changeDetection$.pipe(debounceTime(250))
.subscribe(() => messageBus.emit('componentTreeDirty'));
.profiler.changeDetection$.pipe(debounceTime(250))
.subscribe(() => messageBus.emit('componentTreeDirty'));
});
}
};
@@ -68,69 +55,73 @@ const shutdownCallback = (messageBus: MessageBus<Events>) => () => {
messageBus.destroy();
};
const getLatestComponentExplorerViewCallback =
(messageBus: MessageBus<Events>) => (query?: ComponentExplorerViewQuery) => {
// We want to force re-indexing of the component tree.
// Pressing the refresh button means the user saw stuck UI.
const getLatestComponentExplorerViewCallback = (messageBus: MessageBus<Events>) =>
(query?: ComponentExplorerViewQuery) => {
// We want to force re-indexing of the component tree.
// Pressing the refresh button means the user saw stuck UI.
initializeOrGetDirectiveForestHooks().indexForest();
initializeOrGetDirectiveForestHooks().indexForest();
if (!query) {
if (!query) {
messageBus.emit('latestComponentExplorerView', [
{
forest: prepareForestForSerialization(
initializeOrGetDirectiveForestHooks().getIndexedDirectiveForest()),
},
]);
return;
}
messageBus.emit('latestComponentExplorerView', [
{
forest: prepareForestForSerialization(initializeOrGetDirectiveForestHooks().getIndexedDirectiveForest()),
forest: prepareForestForSerialization(
initializeOrGetDirectiveForestHooks().getIndexedDirectiveForest()),
properties: getLatestComponentState(
query, initializeOrGetDirectiveForestHooks().getDirectiveForest()),
},
]);
return;
}
messageBus.emit('latestComponentExplorerView', [
{
forest: prepareForestForSerialization(initializeOrGetDirectiveForestHooks().getIndexedDirectiveForest()),
properties: getLatestComponentState(query, initializeOrGetDirectiveForestHooks().getDirectiveForest()),
},
]);
};
};
const checkForAngularCallback = (messageBus: MessageBus<Events>) => () => checkForAngular(messageBus);
const checkForAngularCallback = (messageBus: MessageBus<Events>) => () =>
checkForAngular(messageBus);
const getRoutesCallback = (messageBus: MessageBus<Events>) => () => getRoutes(messageBus);
const startProfilingCallback = (messageBus: MessageBus<Events>) => () =>
startProfiling((frame: ProfilerFrame) => {
messageBus.emit('sendProfilerChunk', [frame]);
});
startProfiling((frame: ProfilerFrame) => {
messageBus.emit('sendProfilerChunk', [frame]);
});
const stopProfilingCallback = (messageBus: MessageBus<Events>) => () => {
messageBus.emit('profilerResults', [stopProfiling()]);
};
const selectedComponentCallback = (position: ElementPosition) => {
const node = queryDirectiveForest(position, initializeOrGetDirectiveForestHooks().getIndexedDirectiveForest());
setConsoleReference({ node, position });
const node = queryDirectiveForest(
position, initializeOrGetDirectiveForestHooks().getIndexedDirectiveForest());
setConsoleReference({node, position});
};
const getNestedPropertiesCallback =
(messageBus: MessageBus<Events>) => (position: DirectivePosition, propPath: string[]) => {
const emitEmpty = () => messageBus.emit('nestedProperties', [position, { props: {} }, propPath]);
const node = queryDirectiveForest(
position.element,
initializeOrGetDirectiveForestHooks().getIndexedDirectiveForest()
);
if (!node) {
return emitEmpty();
const getNestedPropertiesCallback = (messageBus: MessageBus<Events>) => (
position: DirectivePosition, propPath: string[]) => {
const emitEmpty = () => messageBus.emit('nestedProperties', [position, {props: {}}, propPath]);
const node = queryDirectiveForest(
position.element, initializeOrGetDirectiveForestHooks().getIndexedDirectiveForest());
if (!node) {
return emitEmpty();
}
const current =
position.directive === undefined ? node.component : node.directives[position.directive];
if (!current) {
return emitEmpty();
}
let data = current.instance;
for (const prop of propPath) {
data = data[prop];
if (!data) {
console.error('Cannot access the properties', propPath, 'of', node);
}
const current = position.directive === undefined ? node.component : node.directives[position.directive];
if (!current) {
return emitEmpty();
}
let data = current.instance;
for (const prop of propPath) {
data = data[prop];
if (!data) {
console.error('Cannot access the properties', propPath, 'of', node);
}
}
messageBus.emit('nestedProperties', [position, { props: serializeDirectiveState(data) }, propPath]);
};
}
messageBus.emit('nestedProperties', [position, {props: serializeDirectiveState(data)}, propPath]);
};
//
// Subscribe Helpers
@@ -155,7 +146,7 @@ const checkForAngular = (messageBus: MessageBus<Events>): void => {
}
messageBus.emit('ngAvailability', [
{ version: ngVersion.toString(), devMode: appIsAngularInDevMode(), ivy: appIsIvy },
{version: ngVersion.toString(), devMode: appIsAngularInDevMode(), ivy: appIsIvy},
]);
};
@@ -189,30 +180,31 @@ export interface SerializableComponentInstanceType extends ComponentType {
id: number;
}
export interface SerializableComponentTreeNode
extends DevToolsNode<SerializableDirectiveInstanceType, SerializableComponentInstanceType> {
export interface SerializableComponentTreeNode extends
DevToolsNode<SerializableDirectiveInstanceType, SerializableComponentInstanceType> {
children: SerializableComponentTreeNode[];
}
// Here we drop properties to prepare the tree for serialization.
// We don't need the component instance, so we just traverse the tree
// and leave the component name.
const prepareForestForSerialization = (roots: ComponentTreeNode[]): SerializableComponentTreeNode[] => {
return roots.map((node) => {
return {
element: node.element,
component: node.component
? {
const prepareForestForSerialization =
(roots: ComponentTreeNode[]): SerializableComponentTreeNode[] => {
return roots.map((node) => {
return {
element: node.element,
component: node.component ? {
name: node.component.name,
isElement: node.component.isElement,
id: initializeOrGetDirectiveForestHooks().getDirectiveId(node.component.instance),
}
: null,
directives: node.directives.map((d) => ({
name: d.name,
id: initializeOrGetDirectiveForestHooks().getDirectiveId(d.instance),
})),
children: prepareForestForSerialization(node.children),
} as SerializableComponentTreeNode;
});
};
} :
null,
directives: node.directives.map(
(d) => ({
name: d.name,
id: initializeOrGetDirectiveForestHooks().getDirectiveId(d.instance),
})),
children: prepareForestForSerialization(node.children),
} as SerializableComponentTreeNode;
});
};
@@ -1,4 +1,4 @@
import { ComponentInspector } from './component-inspector';
import {ComponentInspector} from './component-inspector';
describe('ComponentInspector', () => {
it('should create instance from class', () => {
@@ -1,11 +1,12 @@
import { unHighlight, highlight, findComponentAndHost } from '../highlighter';
import { findNodeInForest } from '../component-tree';
import { ComponentTreeNode } from '../interfaces';
import { ElementPosition } from 'protocol';
import { initializeOrGetDirectiveForestHooks } from '../hooks';
import {ElementPosition} from 'protocol';
import {findNodeInForest} from '../component-tree';
import {findComponentAndHost, highlight, unHighlight} from '../highlighter';
import {initializeOrGetDirectiveForestHooks} from '../hooks';
import {ComponentTreeNode} from '../interfaces';
interface Type<T> extends Function {
new (...args: any[]): T;
new(...args: any[]): T;
}
export interface ComponentInspectorOptions {
onComponentEnter: (id: number) => void;
@@ -14,18 +15,16 @@ export interface ComponentInspectorOptions {
}
export class ComponentInspector {
private _selectedComponent: { component: Type<unknown>; host: HTMLElement | null };
private _selectedComponent: {component: Type<unknown>; host: HTMLElement | null};
private readonly _onComponentEnter;
private readonly _onComponentSelect;
private readonly _onComponentLeave;
constructor(
componentOptions: ComponentInspectorOptions = {
onComponentEnter: () => {},
onComponentLeave: () => {},
onComponentSelect: () => {},
}
) {
constructor(componentOptions: ComponentInspectorOptions = {
onComponentEnter: () => {},
onComponentLeave: () => {},
onComponentSelect: () => {},
}) {
this.bindMethods();
this._onComponentEnter = componentOptions.onComponentEnter;
this._onComponentSelect = componentOptions.onComponentSelect;
@@ -49,7 +48,8 @@ export class ComponentInspector {
e.preventDefault();
if (this._selectedComponent.component && this._selectedComponent.host) {
this._onComponentSelect(initializeOrGetDirectiveForestHooks().getDirectiveId(this._selectedComponent.component));
this._onComponentSelect(
initializeOrGetDirectiveForestHooks().getDirectiveId(this._selectedComponent.component));
}
}
@@ -64,7 +64,8 @@ export class ComponentInspector {
unHighlight();
if (this._selectedComponent.component && this._selectedComponent.host) {
highlight(this._selectedComponent.host);
this._onComponentEnter(initializeOrGetDirectiveForestHooks().getDirectiveId(this._selectedComponent.component));
this._onComponentEnter(
initializeOrGetDirectiveForestHooks().getDirectiveId(this._selectedComponent.component));
}
}
@@ -84,7 +85,7 @@ export class ComponentInspector {
highlightByPosition(position: ElementPosition): void {
const forest: ComponentTreeNode[] = initializeOrGetDirectiveForestHooks().getDirectiveForest();
const elementToHighlight: HTMLElement | null = findNodeInForest(position, forest);
const elementToHighlight: HTMLElement|null = findNodeInForest(position, forest);
if (elementToHighlight) {
highlight(elementToHighlight);
}
@@ -1,14 +1,7 @@
import { deeplySerializeSelectedProperties, serializeDirectiveState } from './state-serializer/state-serializer';
import {ComponentExplorerViewQuery, DirectiveMetadata, DirectivesProperties, ElementPosition, PropertyQueryTypes, UpdatedStateData,} from 'protocol';
import {
ComponentExplorerViewQuery,
DirectiveMetadata,
DirectivesProperties,
ElementPosition,
PropertyQueryTypes,
UpdatedStateData,
} from 'protocol';
import { buildDirectiveTree, getLViewFromDirectiveOrElementInstance } from './directive-forest/index';
import {buildDirectiveTree, getLViewFromDirectiveOrElementInstance} from './directive-forest/index';
import {deeplySerializeSelectedProperties, serializeDirectiveState} from './state-serializer/state-serializer';
// Need to be kept in sync with Angular framework
// We can't directly import it from framework now
@@ -19,46 +12,46 @@ enum ChangeDetectionStrategy {
Default = 1,
}
import { ComponentTreeNode, DirectiveInstanceType, ComponentInstanceType } from './interfaces';
import {ComponentTreeNode, DirectiveInstanceType, ComponentInstanceType} from './interfaces';
const ngDebug = () => (window as any).ng;
export const getLatestComponentState = (
query: ComponentExplorerViewQuery,
directiveForest?: ComponentTreeNode[]
): DirectivesProperties | undefined => {
// if a directive forest is passed in we don't have to build the forest again.
directiveForest = directiveForest ?? buildDirectiveForest();
export const getLatestComponentState =
(query: ComponentExplorerViewQuery, directiveForest?: ComponentTreeNode[]):
DirectivesProperties|undefined => {
// if a directive forest is passed in we don't have to build the forest again.
directiveForest = directiveForest ?? buildDirectiveForest();
const node = queryDirectiveForest(query.selectedElement, directiveForest);
if (!node) {
return;
}
const node = queryDirectiveForest(query.selectedElement, directiveForest);
if (!node) {
return;
}
const result: DirectivesProperties = {};
const result: DirectivesProperties = {};
const populateResultSet = (dir: DirectiveInstanceType | ComponentInstanceType) => {
if (query.propertyQuery.type === PropertyQueryTypes.All) {
result[dir.name] = {
props: serializeDirectiveState(dir.instance),
metadata: getDirectiveMetadata(dir.instance),
};
}
if (query.propertyQuery.type === PropertyQueryTypes.Specified) {
result[dir.name] = {
props: deeplySerializeSelectedProperties(dir.instance, query.propertyQuery.properties[dir.name] || []),
metadata: getDirectiveMetadata(dir.instance),
};
}
};
const populateResultSet = (dir: DirectiveInstanceType|ComponentInstanceType) => {
if (query.propertyQuery.type === PropertyQueryTypes.All) {
result[dir.name] = {
props: serializeDirectiveState(dir.instance),
metadata: getDirectiveMetadata(dir.instance),
};
}
if (query.propertyQuery.type === PropertyQueryTypes.Specified) {
result[dir.name] = {
props: deeplySerializeSelectedProperties(
dir.instance, query.propertyQuery.properties[dir.name] || []),
metadata: getDirectiveMetadata(dir.instance),
};
}
};
node.directives.forEach(populateResultSet);
if (node.component) {
populateResultSet(node.component);
}
node.directives.forEach(populateResultSet);
if (node.component) {
populateResultSet(node.component);
}
return result;
};
return result;
};
const enum DirectiveMetadataKey {
INPUTS = 'inputs',
@@ -121,7 +114,7 @@ const getRootLViewsHelper = (element: Element, rootLViews = new Set<any>()): Set
const getRoots = () => {
const roots = Array.from(document.documentElement.querySelectorAll('[ng-version]'));
const isTopLevel = (element: HTMLElement) => {
let parent: HTMLElement | null = element;
let parent: HTMLElement|null = element;
while (parent?.parentElement) {
parent = parent.parentElement;
if (parent.hasAttribute('ng-version')) {
@@ -140,39 +133,41 @@ export const buildDirectiveForest = (): ComponentTreeNode[] => {
// Based on an ElementID we return a specific component node.
// If we can't find any, we return null.
export const queryDirectiveForest = (
position: ElementPosition,
forest: ComponentTreeNode[]
): ComponentTreeNode | null => {
if (!position.length) {
return null;
}
let node: null | ComponentTreeNode = null;
for (const i of position) {
node = forest[i];
if (!node) {
return null;
}
forest = node.children;
}
return node;
};
export const queryDirectiveForest =
(position: ElementPosition, forest: ComponentTreeNode[]): ComponentTreeNode|null => {
if (!position.length) {
return null;
}
let node: null|ComponentTreeNode = null;
for (const i of position) {
node = forest[i];
if (!node) {
return null;
}
forest = node.children;
}
return node;
};
export const findNodeInForest = (position: ElementPosition, forest: ComponentTreeNode[]): HTMLElement | null => {
const foundComponent: ComponentTreeNode | null = queryDirectiveForest(position, forest);
return foundComponent ? (foundComponent.nativeElement as HTMLElement) : null;
};
export const findNodeInForest =
(position: ElementPosition, forest: ComponentTreeNode[]): HTMLElement|null => {
const foundComponent: ComponentTreeNode|null = queryDirectiveForest(position, forest);
return foundComponent ? (foundComponent.nativeElement as HTMLElement) : null;
};
export const findNodeFromSerializedPosition = (serializedPosition: string): ComponentTreeNode | null => {
const position: number[] = serializedPosition.split(',').map((index) => parseInt(index, 10));
return queryDirectiveForest(position, buildDirectiveForest());
};
export const findNodeFromSerializedPosition =
(serializedPosition: string): ComponentTreeNode|null => {
const position: number[] = serializedPosition.split(',').map((index) => parseInt(index, 10));
return queryDirectiveForest(position, buildDirectiveForest());
};
export const updateState = (updatedStateData: UpdatedStateData): void => {
const ngd = ngDebug();
const node = queryDirectiveForest(updatedStateData.directiveId.element, buildDirectiveForest());
if (!node) {
console.warn('Could not update the state of component', updatedStateData, 'because the component was not found');
console.warn(
'Could not update the state of component', updatedStateData,
'because the component was not found');
return;
}
if (updatedStateData.directiveId.directive !== undefined) {
@@ -204,5 +199,6 @@ const mutateComponentOrDirective = (updatedStateData: UpdatedStateData, compOrDi
// the line below could throw an error.
try {
parentObjectOfValueToUpdate[valueKey] = updatedStateData.newValue;
} catch {}
} catch {
}
};
@@ -1,12 +1,12 @@
import { LTreeStrategy } from './ltree';
import { RTreeStrategy } from './render-tree';
import {LTreeStrategy} from './ltree';
import {RTreeStrategy} from './render-tree';
export { getLViewFromDirectiveOrElementInstance, getDirectiveHostElement, METADATA_PROPERTY_NAME } from './ltree';
export {getDirectiveHostElement, getLViewFromDirectiveOrElementInstance, METADATA_PROPERTY_NAME} from './ltree';
// The order of the strategies matters. Lower indices have higher priority.
const strategies = [new RTreeStrategy(), new LTreeStrategy()];
let strategy: null | RTreeStrategy | LTreeStrategy = null;
let strategy: null|RTreeStrategy|LTreeStrategy = null;
const selectStrategy = (element: Element) => {
for (const s of strategies) {
@@ -1,8 +1,9 @@
import { ComponentTreeNode, DirectiveInstanceType, ComponentInstanceType } from '../interfaces';
import { isCustomElement } from '../utils';
import { getDirectiveName } from '../highlighter';
import { SemVerDSL } from 'semver-dsl';
import { VERSION } from '../version';
import {SemVerDSL} from 'semver-dsl';
import {getDirectiveName} from '../highlighter';
import {ComponentInstanceType, ComponentTreeNode, DirectiveInstanceType} from '../interfaces';
import {isCustomElement} from '../utils';
import {VERSION} from '../version';
let HEADER_OFFSET = 19;
@@ -28,7 +29,7 @@ const isLView = (value: any): boolean => {
};
export const METADATA_PROPERTY_NAME = '__ngContext__';
export const getLViewFromDirectiveOrElementInstance = (dir: any): null | {} => {
export const getLViewFromDirectiveOrElementInstance = (dir: any): null|{} => {
if (!dir) {
return null;
}
@@ -67,7 +68,7 @@ export class LTreeStrategy {
private _getNode(lView: any, data: any, idx: number): ComponentTreeNode {
const directives: DirectiveInstanceType[] = [];
let component: ComponentInstanceType | null = null;
let component: ComponentInstanceType|null = null;
const tNode = data[idx];
const node = lView[idx][ELEMENT];
const element = (node.tagName || node.nodeName).toLowerCase();
@@ -1,4 +1,4 @@
import { RTreeStrategy } from './render-tree';
import {RTreeStrategy} from './render-tree';
describe('render tree extraction', () => {
let treeStrategy: RTreeStrategy;
@@ -11,7 +11,7 @@ describe('render tree extraction', () => {
componentMap = new Map();
(window as any).ng = {
getDirectiveMetadata(): void {},
getDirectiveMetadata(): void{},
getComponent(element: Element): any {
return componentMap.get(element);
},
@@ -1,63 +1,60 @@
import { ComponentTreeNode } from '../interfaces';
import { isCustomElement } from '../utils';
import {ComponentTreeNode} from '../interfaces';
import {isCustomElement} from '../utils';
const extractViewTree = (
domNode: Node | Element,
result: ComponentTreeNode[],
getComponent: (element: Element) => {},
getDirectives: (node: Node) => {}[]
): ComponentTreeNode[] => {
const directives = getDirectives(domNode);
if (!directives.length && !(domNode instanceof Element)) {
return result;
}
const componentTreeNode: ComponentTreeNode = {
children: [],
component: null,
directives: directives.map((dir) => {
return {
instance: dir,
name: dir.constructor.name,
const extractViewTree =
(domNode: Node|Element, result: ComponentTreeNode[], getComponent: (element: Element) => {},
getDirectives: (node: Node) => {}[]): ComponentTreeNode[] => {
const directives = getDirectives(domNode);
if (!directives.length && !(domNode instanceof Element)) {
return result;
}
const componentTreeNode: ComponentTreeNode = {
children: [],
component: null,
directives: directives.map((dir) => {
return {
instance: dir,
name: dir.constructor.name,
};
}),
element: domNode.nodeName.toLowerCase(),
nativeElement: domNode,
};
}),
element: domNode.nodeName.toLowerCase(),
nativeElement: domNode,
};
if (!(domNode instanceof Element)) {
result.push(componentTreeNode);
return result;
}
const component = getComponent(domNode);
if (component) {
componentTreeNode.component = {
instance: component,
isElement: isCustomElement(domNode),
name: domNode.nodeName.toLowerCase(),
if (!(domNode instanceof Element)) {
result.push(componentTreeNode);
return result;
}
const component = getComponent(domNode);
if (component) {
componentTreeNode.component = {
instance: component,
isElement: isCustomElement(domNode),
name: domNode.nodeName.toLowerCase(),
};
}
if (component || componentTreeNode.directives.length) {
result.push(componentTreeNode);
}
if (componentTreeNode.component || componentTreeNode.directives.length) {
domNode.childNodes.forEach(
(node) =>
extractViewTree(node, componentTreeNode.children, getComponent, getDirectives));
} else {
domNode.childNodes.forEach(
(node) => extractViewTree(node, result, getComponent, getDirectives));
}
return result;
};
}
if (component || componentTreeNode.directives.length) {
result.push(componentTreeNode);
}
if (componentTreeNode.component || componentTreeNode.directives.length) {
domNode.childNodes.forEach((node) =>
extractViewTree(node, componentTreeNode.children, getComponent, getDirectives)
);
} else {
domNode.childNodes.forEach((node) => extractViewTree(node, result, getComponent, getDirectives));
}
return result;
};
export class RTreeStrategy {
supports(_: any): boolean {
return ['getDirectiveMetadata', 'getComponent', 'getDirectives'].every(
(method) => typeof (window as any).ng[method] === 'function'
);
(method) => typeof (window as any).ng[method] === 'function');
}
build(element: Element): ComponentTreeNode[] {
// We want to start from the root element so that we can find components which are attached to the application ref
// and which host elements have been inserted with DOM APIs.
// We want to start from the root element so that we can find components which are attached to
// the application ref and which host elements have been inserted with DOM APIs.
while (element.parentElement) {
element = element.parentElement;
}
@@ -3,7 +3,7 @@ import * as highlighter from './highlighter';
describe('highlighter', () => {
describe('findComponentAndHost', () => {
it('should return undefined when no node is provided', () => {
expect(highlighter.findComponentAndHost(undefined)).toEqual({ component: null, host: null });
expect(highlighter.findComponentAndHost(undefined)).toEqual({component: null, host: null});
});
it('should return same component and host if component exists', () => {
@@ -4,7 +4,7 @@ let overlayContent: HTMLElement;
declare const ng: any;
interface Type<T> extends Function {
new (...args: any[]): T;
new(...args: any[]): T;
}
export const DEV_TOOLS_HIGHLIGHT_NODE_ID = '____ngDevToolsHighlight';
@@ -34,25 +34,26 @@ function init(): void {
overlay.appendChild(overlayContent);
}
export const findComponentAndHost = (el: Node | undefined): { component: any; host: HTMLElement | null } => {
if (!el) {
return { component: null, host: null };
}
while (el) {
const component = el instanceof HTMLElement && ng.getComponent(el);
if (component) {
return { component, host: el as HTMLElement };
}
if (!el.parentElement) {
break;
}
el = el.parentElement;
}
return { component: null, host: null };
};
export const findComponentAndHost =
(el: Node|undefined): {component: any; host: HTMLElement | null} => {
if (!el) {
return {component: null, host: null};
}
while (el) {
const component = el instanceof HTMLElement && ng.getComponent(el);
if (component) {
return {component, host: el as HTMLElement};
}
if (!el.parentElement) {
break;
}
el = el.parentElement;
}
return {component: null, host: null};
};
// Todo(aleksanderbodurri): this should not be part of the highlighter, move this somewhere else
export const getDirectiveName = (dir: Type<unknown> | undefined | null): string => {
export const getDirectiveName = (dir: Type<unknown>|undefined|null): string => {
if (dir) {
return dir.constructor.name;
}
@@ -93,10 +94,11 @@ export function inDoc(node: any): boolean {
}
const doc = node.ownerDocument.documentElement;
const parent = node.parentNode;
return doc === node || doc === parent || !!(parent && parent.nodeType === 1 && doc.contains(parent));
return doc === node || doc === parent ||
!!(parent && parent.nodeType === 1 && doc.contains(parent));
}
export function getComponentRect(el: Node): DOMRect | ClientRect | undefined {
export function getComponentRect(el: Node): DOMRect|ClientRect|undefined {
if (!(el instanceof HTMLElement)) {
return;
}
@@ -114,16 +116,15 @@ interface OverlayDimensionsAndPosition {
}
function showOverlay(
{ width = 0, height = 0, top = 0, left = 0 }: OverlayDimensionsAndPosition,
content: any[] = []
): void {
{width = 0, height = 0, top = 0, left = 0}: OverlayDimensionsAndPosition,
content: any[] = []): void {
overlay.style.width = ~~width + 'px';
overlay.style.height = ~~height + 'px';
overlay.style.top = ~~top + 'px';
overlay.style.left = ~~left + 'px';
while (overlayContent.children.length) {
const { children } = overlayContent;
const {children} = overlayContent;
overlayContent.removeChild(children[children.length - 1]);
}
@@ -1,10 +1,12 @@
import { DirectiveForestHooks } from './hooks';
import { ElementPosition, ProfilerFrame, ElementProfile, DirectiveProfile, LifecycleProfile } from 'protocol';
import { runOutsideAngular, isCustomElement } from '../utils';
import { getDirectiveName } from '../highlighter';
import { ComponentTreeNode } from '../interfaces';
import { initializeOrGetDirectiveForestHooks } from '.';
import { Hooks } from './profiler';
import {DirectiveProfile, ElementPosition, ElementProfile, LifecycleProfile, ProfilerFrame} from 'protocol';
import {getDirectiveName} from '../highlighter';
import {ComponentTreeNode} from '../interfaces';
import {isCustomElement, runOutsideAngular} from '../utils';
import {initializeOrGetDirectiveForestHooks} from '.';
import {DirectiveForestHooks} from './hooks';
import {Hooks} from './profiler';
let inProgress = false;
let inChangeDetection = false;
@@ -48,15 +50,17 @@ const getHooks = (onFrame: (frame: ProfilerFrame) => void): Partial<Hooks> => {
return {
// We flush here because it's possible the current node to overwrite
// an existing removed node.
onCreate(directive: any, node: Node, _: number, isComponent: boolean, position: ElementPosition): void {
eventMap.set(directive, {
isElement: isCustomElement(node),
name: getDirectiveName(directive),
isComponent,
lifecycle: {},
outputs: {},
});
},
onCreate(
directive: any, node: Node, _: number, isComponent: boolean, position: ElementPosition):
void {
eventMap.set(directive, {
isElement: isCustomElement(node),
name: getDirectiveName(directive),
isComponent,
lifecycle: {},
outputs: {},
});
},
onChangeDetectionStart(component: any, node: Node): void {
startEvent(timeStartMap, component, 'changeDetection');
if (!inChangeDetection) {
@@ -99,25 +103,22 @@ const getHooks = (onFrame: (frame: ProfilerFrame) => void): Partial<Hooks> => {
console.warn('Could not find profile for', component);
}
},
onDestroy(directive: any, node: Node, _: number, isComponent: boolean, __: ElementPosition): void {
// Make sure we reflect such directives in the report.
if (!eventMap.has(directive)) {
eventMap.set(directive, {
isElement: isComponent && isCustomElement(node),
name: getDirectiveName(directive),
isComponent,
lifecycle: {},
outputs: {},
});
}
},
onDestroy(directive: any, node: Node, _: number, isComponent: boolean, __: ElementPosition):
void {
// Make sure we reflect such directives in the report.
if (!eventMap.has(directive)) {
eventMap.set(directive, {
isElement: isComponent && isCustomElement(node),
name: getDirectiveName(directive),
isComponent,
lifecycle: {},
outputs: {},
});
}
},
onLifecycleHookStart(
directive: any,
hookName: keyof LifecycleProfile,
node: Node,
__: number,
isComponent: boolean
): void {
directive: any, hookName: keyof LifecycleProfile, node: Node, __: number,
isComponent: boolean): void {
startEvent(timeStartMap, directive, hookName);
if (!eventMap.has(directive)) {
eventMap.set(directive, {
@@ -129,7 +130,8 @@ const getHooks = (onFrame: (frame: ProfilerFrame) => void): Partial<Hooks> => {
});
}
},
onLifecycleHookEnd(directive: any, hookName: keyof LifecycleProfile, _: Node, __: number, ___: boolean): void {
onLifecycleHookEnd(
directive: any, hookName: keyof LifecycleProfile, _: Node, __: number, ___: boolean): void {
const dir = eventMap.get(directive);
const startTimestamp = getEventStart(timeStartMap, directive, hookName);
if (startTimestamp === undefined) {
@@ -143,18 +145,19 @@ const getHooks = (onFrame: (frame: ProfilerFrame) => void): Partial<Hooks> => {
dir.lifecycle[hookName] = (dir.lifecycle[hookName] || 0) + duration;
frameDuration += duration;
},
onOutputStart(componentOrDirective: any, outputName: string, node: Node, isComponent: boolean): void {
startEvent(timeStartMap, componentOrDirective, outputName);
if (!eventMap.has(componentOrDirective)) {
eventMap.set(componentOrDirective, {
isElement: isCustomElement(node),
name: getDirectiveName(componentOrDirective),
isComponent,
lifecycle: {},
outputs: {},
});
}
},
onOutputStart(componentOrDirective: any, outputName: string, node: Node, isComponent: boolean):
void {
startEvent(timeStartMap, componentOrDirective, outputName);
if (!eventMap.has(componentOrDirective)) {
eventMap.set(componentOrDirective, {
isElement: isCustomElement(node),
name: getDirectiveName(componentOrDirective),
isComponent,
lifecycle: {},
outputs: {},
});
}
},
onOutputEnd(componentOrDirective: any, outputName: string): void {
const name = outputName;
const entry = eventMap.get(componentOrDirective);
@@ -163,7 +166,9 @@ const getHooks = (onFrame: (frame: ProfilerFrame) => void): Partial<Hooks> => {
return;
}
if (!entry) {
console.warn('Could not find directive or component in onOutputEnd callback', componentOrDirective, outputName);
console.warn(
'Could not find directive or component in onOutputEnd callback', componentOrDirective,
outputName);
return;
}
const duration = performance.now() - startTimestamp;
@@ -202,32 +207,33 @@ const insertOrMerge = (lastFrame: ElementProfile, profile: DirectiveProfile) =>
}
};
const insertElementProfile = (frames: ElementProfile[], position: ElementPosition, profile?: DirectiveProfile) => {
if (!profile) {
return;
}
const original = frames;
for (let i = 0; i < position.length - 1; i++) {
const pos = position[i];
if (!frames[pos]) {
// TODO(mgechev): consider how to ensure we don't hit this case
console.warn('Unable to find parent node for', profile, original);
return;
}
frames = frames[pos].children;
}
const lastIdx = position[position.length - 1];
let lastFrame: ElementProfile = {
children: [],
directives: [],
};
if (frames[lastIdx]) {
lastFrame = frames[lastIdx];
} else {
frames[lastIdx] = lastFrame;
}
insertOrMerge(lastFrame, profile);
};
const insertElementProfile =
(frames: ElementProfile[], position: ElementPosition, profile?: DirectiveProfile) => {
if (!profile) {
return;
}
const original = frames;
for (let i = 0; i < position.length - 1; i++) {
const pos = position[i];
if (!frames[pos]) {
// TODO(mgechev): consider how to ensure we don't hit this case
console.warn('Unable to find parent node for', profile, original);
return;
}
frames = frames[pos].children;
}
const lastIdx = position[position.length - 1];
let lastFrame: ElementProfile = {
children: [],
directives: [],
};
if (frames[lastIdx]) {
lastFrame = frames[lastIdx];
} else {
frames[lastIdx] = lastFrame;
}
insertOrMerge(lastFrame, profile);
};
const prepareInitialFrame = (source: string, duration: number) => {
const frame: ProfilerFrame = {
@@ -238,7 +244,7 @@ const prepareInitialFrame = (source: string, duration: number) => {
const directiveForestHooks = initializeOrGetDirectiveForestHooks();
const directiveForest = directiveForestHooks.getIndexedDirectiveForest();
const traverse = (node: ComponentTreeNode, children = frame.directives) => {
let position: ElementPosition | undefined;
let position: ElementPosition|undefined;
if (node.component) {
position = directiveForestHooks.getDirectivePosition(node.component.instance);
} else {
@@ -1,7 +1,9 @@
import { ComponentTreeNode } from '../interfaces';
import { ElementPosition } from 'protocol';
import { IdentityTracker, IndexedNode } from './identity-tracker';
import { Profiler, selectProfilerStrategy } from './profiler';
import {ElementPosition} from 'protocol';
import {ComponentTreeNode} from '../interfaces';
import {IdentityTracker, IndexedNode} from './identity-tracker';
import {Profiler, selectProfilerStrategy} from './profiler';
/**
* Class to hook into directive forest.
@@ -18,7 +20,7 @@ export class DirectiveForestHooks {
profiler: Profiler = selectProfilerStrategy();
getDirectivePosition(dir: any): ElementPosition | undefined {
getDirectivePosition(dir: any): ElementPosition|undefined {
const result = this._tracker.getDirectivePosition(dir);
if (result === undefined) {
console.warn('Unable to find position of', dir);
@@ -26,7 +28,7 @@ export class DirectiveForestHooks {
return result;
}
getDirectiveId(dir: any): number | undefined {
getDirectiveId(dir: any): number|undefined {
const result = this._tracker.getDirectiveId(dir);
if (result === undefined) {
console.warn('Unable to find ID of', result);
@@ -47,7 +49,7 @@ export class DirectiveForestHooks {
}
indexForest(): void {
const { newNodes, removedNodes, indexedForest, directiveForest } = this._tracker.index();
const {newNodes, removedNodes, indexedForest, directiveForest} = this._tracker.index();
this._indexedForest = indexedForest;
this._forest = directiveForest;
this.profiler.onIndexForest(newNodes, removedNodes);
@@ -1,9 +1,10 @@
import { ComponentTreeNode, DirectiveInstanceType, ComponentInstanceType } from '../interfaces';
import { ElementPosition, DevToolsNode } from 'protocol';
import { buildDirectiveForest } from '../component-tree';
import {DevToolsNode, ElementPosition} from 'protocol';
import {buildDirectiveForest} from '../component-tree';
import {ComponentInstanceType, ComponentTreeNode, DirectiveInstanceType} from '../interfaces';
export declare interface Type<T> extends Function {
new (...args: any[]): T;
new(...args: any[]): T;
}
interface TreeNode {
@@ -13,8 +14,7 @@ interface TreeNode {
}
export type NodeArray = {
directive: any;
isComponent: boolean;
directive: any; isComponent: boolean;
}[];
export class IdentityTracker {
@@ -35,11 +35,11 @@ export class IdentityTracker {
return IdentityTracker._instance;
}
getDirectivePosition(dir: any): ElementPosition | undefined {
getDirectivePosition(dir: any): ElementPosition|undefined {
return this._currentDirectivePosition.get(dir);
}
getDirectiveId(dir: any): number | undefined {
getDirectiveId(dir: any): number|undefined {
return this._currentDirectiveId.get(dir);
}
@@ -48,9 +48,7 @@ export class IdentityTracker {
}
index(): {
newNodes: NodeArray;
removedNodes: NodeArray;
indexedForest: IndexedNode[];
newNodes: NodeArray; removedNodes: NodeArray; indexedForest: IndexedNode[];
directiveForest: ComponentTreeNode[];
} {
const directiveForest = buildDirectiveForest();
@@ -61,22 +59,19 @@ export class IdentityTracker {
indexedForest.forEach((root) => this._index(root, null, newNodes, allNodes));
this._currentDirectiveId.forEach((_: number, dir: any) => {
if (!allNodes.has(dir)) {
removedNodes.push({ directive: dir, isComponent: !!this.isComponent.get(dir) });
removedNodes.push({directive: dir, isComponent: !!this.isComponent.get(dir)});
// We can't clean these up because during profiling
// they might be requested for removed components
// this._currentDirectiveId.delete(dir);
// this._currentDirectivePosition.delete(dir);
}
});
return { newNodes, removedNodes, indexedForest, directiveForest };
return {newNodes, removedNodes, indexedForest, directiveForest};
}
private _index(
node: IndexedNode,
parent: TreeNode | null,
newNodes: { directive: any; isComponent: boolean }[],
allNodes: Set<any>
): void {
node: IndexedNode, parent: TreeNode|null, newNodes: {directive: any; isComponent: boolean}[],
allNodes: Set<any>): void {
if (node.component) {
allNodes.add(node.component.instance);
this.isComponent.set(node.component.instance, true);
@@ -93,7 +88,7 @@ export class IdentityTracker {
private _indexNode(directive: any, position: ElementPosition, newNodes: NodeArray): void {
this._currentDirectivePosition.set(directive, position);
if (!this._currentDirectiveId.has(directive)) {
newNodes.push({ directive, isComponent: !!this.isComponent.get(directive) });
newNodes.push({directive, isComponent: !!this.isComponent.get(directive)});
this._currentDirectiveId.set(directive, this._directiveIdCounter++);
}
}
@@ -110,21 +105,17 @@ export interface IndexedNode extends DevToolsNode<DirectiveInstanceType, Compone
}
const indexTree = <T extends DevToolsNode<DirectiveInstanceType, ComponentInstanceType>>(
node: T,
idx: number,
parentPosition: number[] = []
): IndexedNode => {
node: T, idx: number, parentPosition: number[] = []): IndexedNode => {
const position = parentPosition.concat([idx]);
return {
position,
element: node.element,
component: node.component,
directives: node.directives.map((d) => ({ position, ...d })),
directives: node.directives.map((d) => ({position, ...d})),
children: node.children.map((n, i) => indexTree(n, i, position)),
nativeElement: node.nativeElement,
} as IndexedNode;
};
export const indexForest = <T extends DevToolsNode<DirectiveInstanceType, ComponentInstanceType>>(
forest: T[]
): IndexedNode[] => forest.map((n, i) => indexTree(n, i));
forest: T[]): IndexedNode[] => forest.map((n, i) => indexTree(n, i));
@@ -1,12 +1,15 @@
import { getDirectiveName } from '../highlighter';
import { DirectiveForestHooks } from './hooks';
import { LifecycleProfile } from 'protocol';
import {LifecycleProfile} from 'protocol';
import {getDirectiveName} from '../highlighter';
import {DirectiveForestHooks} from './hooks';
const markName = (s: string, method: Method) => `🅰️ ${s}#${method}`;
const supportsPerformance = globalThis.performance && typeof globalThis.performance.getEntriesByName === 'function';
const supportsPerformance =
globalThis.performance && typeof globalThis.performance.getEntriesByName === 'function';
type Method = keyof LifecycleProfile | 'changeDetection' | string;
type Method = keyof LifecycleProfile|'changeDetection'|string;
const recordMark = (s: string, method: Method) => {
if (supportsPerformance) {
@@ -1,8 +1,8 @@
import { NgProfiler } from './native';
import { PatchingProfiler } from './polyfill';
import { Profiler } from './shared';
import {NgProfiler} from './native';
import {PatchingProfiler} from './polyfill';
import {Profiler} from './shared';
export { Profiler, Hooks } from './shared';
export {Hooks, Profiler} from './shared';
/**
* Factory method for creating profiler object.
@@ -1,8 +1,10 @@
import { ɵProfilerEvent } from '@angular/core';
import { getDirectiveHostElement } from '../../directive-forest';
import { runOutsideAngular } from '../../utils';
import { IdentityTracker, NodeArray } from '../identity-tracker';
import { getLifeCycleName, Hooks, Profiler } from './shared';
import {ɵProfilerEvent} from '@angular/core';
import {getDirectiveHostElement} from '../../directive-forest';
import {runOutsideAngular} from '../../utils';
import {IdentityTracker, NodeArray} from '../identity-tracker';
import {getLifeCycleName, Hooks, Profiler} from './shared';
type ProfilerCallback = (event: ɵProfilerEvent, instanceOrLView: {}, hookOrListener: any) => void;
@@ -10,7 +12,7 @@ type ProfilerCallback = (event: ɵProfilerEvent, instanceOrLView: {}, hookOrList
export class NgProfiler extends Profiler {
private _tracker = IdentityTracker.getInstance();
private _callbacks: ProfilerCallback[] = [];
private _lastDirectiveInstance: {} | null = null;
private _lastDirectiveInstance: {}|null = null;
constructor(config: Partial<Hooks> = {}) {
super(config);
@@ -26,9 +28,9 @@ export class NgProfiler extends Profiler {
private _initialize(): void {
const ng = (window as any).ng;
ng.ɵsetProfiler((event: ɵProfilerEvent, instanceOrLView: {}, hookOrListener: any) =>
this._callbacks.forEach((cb) => cb(event, instanceOrLView, hookOrListener))
);
ng.ɵsetProfiler(
(event: ɵProfilerEvent, instanceOrLView: {}, hookOrListener: any) =>
this._callbacks.forEach((cb) => cb(event, instanceOrLView, hookOrListener)));
}
private _setProfilerCallback(callback: ProfilerCallback): void {
@@ -41,7 +43,7 @@ export class NgProfiler extends Profiler {
onIndexForest(newNodes: NodeArray, removedNodes: NodeArray): void {
newNodes.forEach((node) => {
const { directive, isComponent } = node;
const {directive, isComponent} = node;
const position = this._tracker.getDirectivePosition(directive);
const id = this._tracker.getDirectiveId(directive);
@@ -49,7 +51,7 @@ export class NgProfiler extends Profiler {
});
removedNodes.forEach((node) => {
const { directive, isComponent } = node;
const {directive, isComponent} = node;
const position = this._tracker.getDirectivePosition(directive);
const id = this._tracker.getDirectiveId(directive);
@@ -94,11 +96,9 @@ export class NgProfiler extends Profiler {
}
this._onChangeDetectionStart(
this._lastDirectiveInstance,
getDirectiveHostElement(this._lastDirectiveInstance),
this._tracker.getDirectiveId(this._lastDirectiveInstance),
this._tracker.getDirectivePosition(this._lastDirectiveInstance)
);
this._lastDirectiveInstance, getDirectiveHostElement(this._lastDirectiveInstance),
this._tracker.getDirectiveId(this._lastDirectiveInstance),
this._tracker.getDirectivePosition(this._lastDirectiveInstance));
}
[ɵProfilerEvent.TemplateUpdateEnd](context: any, _hookOrListener: any): void {
@@ -111,11 +111,9 @@ export class NgProfiler extends Profiler {
}
this._onChangeDetectionEnd(
this._lastDirectiveInstance,
getDirectiveHostElement(this._lastDirectiveInstance),
this._tracker.getDirectiveId(this._lastDirectiveInstance),
this._tracker.getDirectivePosition(this._lastDirectiveInstance)
);
this._lastDirectiveInstance, getDirectiveHostElement(this._lastDirectiveInstance),
this._tracker.getDirectiveId(this._lastDirectiveInstance),
this._tracker.getDirectivePosition(this._lastDirectiveInstance));
}
[ɵProfilerEvent.LifecycleHookStart](directive: any, hook: any): void {
@@ -1,11 +1,8 @@
import {
getDirectiveHostElement,
getLViewFromDirectiveOrElementInstance,
METADATA_PROPERTY_NAME,
} from '../../directive-forest';
import { runOutsideAngular } from '../../utils';
import { IdentityTracker, NodeArray } from '../identity-tracker';
import { getLifeCycleName, Profiler } from './shared';
import {getDirectiveHostElement, getLViewFromDirectiveOrElementInstance, METADATA_PROPERTY_NAME,} from '../../directive-forest';
import {runOutsideAngular} from '../../utils';
import {IdentityTracker, NodeArray} from '../identity-tracker';
import {getLifeCycleName, Profiler} from './shared';
const hookTViewProperties = [
'preOrderHooks',
@@ -20,7 +17,10 @@ const hookTViewProperties = [
// Only used in older Angular versions prior to the introduction of `getDirectiveMetadata`
const componentMetadata = (instance: any) => instance?.constructor?.ɵcmp;
/** Implementation of Profiler that uses monkey patching of directive templates and lifecycle methods to fire profiler hooks. */
/**
* Implementation of Profiler that uses monkey patching of directive templates and lifecycle
* methods to fire profiler hooks.
*/
export class PatchingProfiler extends Profiler {
private _patched = new Map<any, () => void>();
private _undoLifecyclePatch: (() => void)[] = [];
@@ -74,7 +74,7 @@ export class PatchingProfiler extends Profiler {
if (original.patched) {
return;
}
declarations.tView.template = function (_: any, component: any): void {
declarations.tView.template = function(_: any, component: any): void {
if (!self._inChangeDetection) {
self._inChangeDetection = true;
runOutsideAngular(() => {
@@ -114,7 +114,7 @@ export class PatchingProfiler extends Profiler {
}
if (typeof el === 'function') {
const self = this;
current[idx] = function (): any {
current[idx] = function(): any {
// We currently don't want to notify the consumer
// for execution of lifecycle hooks of services and pipes.
// These two abstractions don't have `__ngContext__`, and
@@ -1,45 +1,34 @@
import { ElementPosition, LifecycleProfile } from 'protocol';
import { Subject } from 'rxjs';
import { NodeArray } from '../identity-tracker';
import {ElementPosition, LifecycleProfile} from 'protocol';
import {Subject} from 'rxjs';
type CreationHook = (
componentOrDirective: any,
node: Node,
id: number,
isComponent: boolean,
position: ElementPosition
) => void;
import {NodeArray} from '../identity-tracker';
type LifecycleStartHook = (
componentOrDirective: any,
hook: keyof LifecycleProfile | 'unknown',
node: Node,
id: number,
isComponent: boolean
) => void;
type CreationHook =
(componentOrDirective: any, node: Node, id: number, isComponent: boolean,
position: ElementPosition) => void;
type LifecycleEndHook = (
componentOrDirective: any,
hook: keyof LifecycleProfile | 'unknown',
node: Node,
id: number,
isComponent: boolean
) => void;
type LifecycleStartHook =
(componentOrDirective: any, hook: keyof LifecycleProfile|'unknown', node: Node, id: number,
isComponent: boolean) => void;
type ChangeDetectionStartHook = (component: any, node: Node, id: number, position: ElementPosition) => void;
type LifecycleEndHook =
(componentOrDirective: any, hook: keyof LifecycleProfile|'unknown', node: Node, id: number,
isComponent: boolean) => void;
type ChangeDetectionEndHook = (component: any, node: Node, id: number, position: ElementPosition) => void;
type ChangeDetectionStartHook =
(component: any, node: Node, id: number, position: ElementPosition) => void;
type DestroyHook = (
componentOrDirective: any,
node: Node,
id: number,
isComponent: boolean,
position: ElementPosition
) => void;
type ChangeDetectionEndHook = (component: any, node: Node, id: number, position: ElementPosition) =>
void;
type OutputStartHook = (componentOrDirective: any, outputName: string, node: Node, isComponent: boolean) => void;
type OutputEndHook = (componentOrDirective: any, outputName: string, node: Node, isComponent: boolean) => void;
type DestroyHook =
(componentOrDirective: any, node: Node, id: number, isComponent: boolean,
position: ElementPosition) => void;
type OutputStartHook =
(componentOrDirective: any, outputName: string, node: Node, isComponent: boolean) => void;
type OutputEndHook =
(componentOrDirective: any, outputName: string, node: Node, isComponent: boolean) => void;
export interface Hooks {
onCreate: CreationHook;
@@ -53,7 +42,8 @@ export interface Hooks {
}
/**
* Class for profiling angular applications. Handles hook subscriptions and emitting change detection events.
* Class for profiling angular applications. Handles hook subscriptions and emitting change
* detection events.
*/
export abstract class Profiler {
protected _inChangeDetection = false;
@@ -78,12 +68,8 @@ export abstract class Profiler {
}
protected _onCreate(
_: any,
__: Node,
id: number | undefined,
___: boolean,
position: ElementPosition | undefined
): void {
_: any, __: Node, id: number|undefined, ___: boolean,
position: ElementPosition|undefined): void {
if (id === undefined || position === undefined) {
return;
}
@@ -91,12 +77,8 @@ export abstract class Profiler {
}
protected _onDestroy(
_: any,
__: Node,
id: number | undefined,
___: boolean,
position: ElementPosition | undefined
): void {
_: any, __: Node, id: number|undefined, ___: boolean,
position: ElementPosition|undefined): void {
if (id === undefined || position === undefined) {
return;
}
@@ -104,11 +86,7 @@ export abstract class Profiler {
}
protected _onChangeDetectionStart(
_: any,
__: Node,
id: number | undefined,
position: ElementPosition | undefined
): void {
_: any, __: Node, id: number|undefined, position: ElementPosition|undefined): void {
if (id === undefined || position === undefined) {
return;
}
@@ -116,11 +94,7 @@ export abstract class Profiler {
}
protected _onChangeDetectionEnd(
_: any,
__: Node,
id: number | undefined,
position: ElementPosition | undefined
): void {
_: any, __: Node, id: number|undefined, position: ElementPosition|undefined): void {
if (id === undefined || position === undefined) {
return;
}
@@ -128,12 +102,8 @@ export abstract class Profiler {
}
protected _onLifecycleHookStart(
_: any,
__: keyof LifecycleProfile | 'unknown',
___: Node,
id: number | undefined,
____: boolean
): void {
_: any, __: keyof LifecycleProfile|'unknown', ___: Node, id: number|undefined,
____: boolean): void {
if (id === undefined) {
return;
}
@@ -141,26 +111,23 @@ export abstract class Profiler {
}
protected _onLifecycleHookEnd(
_: any,
__: keyof LifecycleProfile | 'unknown',
___: Node,
id: number | undefined,
____: boolean
): void {
_: any, __: keyof LifecycleProfile|'unknown', ___: Node, id: number|undefined,
____: boolean): void {
if (id === undefined) {
return;
}
this._invokeCallback('onLifecycleHookEnd', arguments);
}
protected _onOutputStart(_: any, __: string, ___: Node, id: number | undefined, ____: boolean): void {
protected _onOutputStart(_: any, __: string, ___: Node, id: number|undefined, ____: boolean):
void {
if (id === undefined) {
return;
}
this._invokeCallback('onOutputStart', arguments);
}
protected _onOutputEnd(_: any, __: string, ___: Node, id: number | undefined, ____: boolean): void {
protected _onOutputEnd(_: any, __: string, ___: Node, id: number|undefined, ____: boolean): void {
if (id === undefined) {
return;
}
@@ -190,7 +157,7 @@ const hookNames = [
const hookMethodNames = new Set(hookNames.map((hook) => `ng${hook}`));
export const getLifeCycleName = (obj: {}, fn: any): keyof LifecycleProfile | 'unknown' => {
export const getLifeCycleName = (obj: {}, fn: any): keyof LifecycleProfile|'unknown' => {
const proto = Object.getPrototypeOf(obj);
const keys = Object.getOwnPropertyNames(proto);
for (const propName of keys) {
@@ -1,5 +1,6 @@
import { MessageBus, Events } from 'protocol';
import { subscribeToClientEvents } from './client-event-subscribers';
import {Events, MessageBus} from 'protocol';
import {subscribeToClientEvents} from './client-event-subscribers';
export const initializeMessageBus = (messageBus: MessageBus<Events>) => {
subscribeToClientEvents(messageBus);
@@ -1,4 +1,4 @@
import { DevToolsNode } from 'protocol';
import {DevToolsNode} from 'protocol';
export interface DebuggingAPI {
getComponent(node: Node): any;
@@ -16,6 +16,7 @@ export interface ComponentInstanceType {
isElement: boolean;
}
export interface ComponentTreeNode extends DevToolsNode<DirectiveInstanceType, ComponentInstanceType> {
export interface ComponentTreeNode extends
DevToolsNode<DirectiveInstanceType, ComponentInstanceType> {
children: ComponentTreeNode[];
}
@@ -1,4 +1,4 @@
import { parseRoutes } from './router-tree';
import {parseRoutes} from './router-tree';
describe('parseRoutes', () => {
it('should work without any routes', () => {
@@ -104,7 +104,7 @@ describe('parseRoutes', () => {
},
{
handler: 'component-two',
data: [Object({ key: 'name', value: 'component-two' })],
data: [Object({key: 'name', value: 'component-two'})],
hash: null,
specificity: null,
name: 'component-two',
@@ -1,4 +1,4 @@
import { Route } from 'protocol';
import {Route} from 'protocol';
// todo(aleksanderbodurri): type these properly
type AngularRoute = any;
@@ -23,7 +23,7 @@ export function parseRoutes(router: Router): Route {
return root;
}
function assignChildrenToParent(parentPath: string | null, children: Routes): Route[] {
function assignChildrenToParent(parentPath: string|null, children: Routes): Route[] {
return children.map((child: AngularRoute) => {
const childName = childRouteName(child);
const childDescendents: [any] = (child as any)._loadedConfig?.routes || child.children;
@@ -1,9 +1,10 @@
import { arrayEquals } from 'shared-utils';
import { ElementPosition } from 'protocol';
import { ComponentTreeNode } from './interfaces';
import {ElementPosition} from 'protocol';
import {arrayEquals} from 'shared-utils';
import {ComponentTreeNode} from './interfaces';
interface ConsoleReferenceNode {
node: ComponentTreeNode | null;
node: ComponentTreeNode|null;
position: ElementPosition;
}
@@ -26,9 +27,8 @@ const _setConsoleReference = (referenceNode: ConsoleReferenceNode) => {
};
const prepareCurrentReferencesForInsertion = (referenceNode: ConsoleReferenceNode) => {
const foundIndex = nodesForConsoleReference.findIndex((nodeToLookFor) =>
arrayEquals(nodeToLookFor.position, referenceNode.position)
);
const foundIndex = nodesForConsoleReference.findIndex(
(nodeToLookFor) => arrayEquals(nodeToLookFor.position, referenceNode.position));
if (foundIndex !== -1) {
nodesForConsoleReference.splice(foundIndex, 1);
} else if (nodesForConsoleReference.length === CAPACITY) {
@@ -37,12 +37,12 @@ const prepareCurrentReferencesForInsertion = (referenceNode: ConsoleReferenceNod
};
const assignConsoleReferencesFrom = (referenceNodes: ConsoleReferenceNode[]) => {
referenceNodes.forEach((referenceNode, index) =>
setDirectiveKey(referenceNode.node, getConsoleReferenceWithIndexOf(index))
);
referenceNodes.forEach(
(referenceNode, index) =>
setDirectiveKey(referenceNode.node, getConsoleReferenceWithIndexOf(index)));
};
const setDirectiveKey = (node: ComponentTreeNode | null, key: string) => {
const setDirectiveKey = (node: ComponentTreeNode|null, key: string) => {
Object.defineProperty(window, key, {
get: () => {
if (node?.component) {
@@ -58,4 +58,4 @@ const setDirectiveKey = (node: ComponentTreeNode | null, key: string) => {
};
const getConsoleReferenceWithIndexOf = (consoleReferenceIndex: number) =>
`${CONSOLE_REFERENCE_PREFIX}${consoleReferenceIndex}`;
`${CONSOLE_REFERENCE_PREFIX}${consoleReferenceIndex}`;
@@ -1,20 +1,21 @@
import { Descriptor, NestedProp, PropType } from 'protocol';
import { getKeys } from './object-utils';
import {Descriptor, NestedProp, PropType} from 'protocol';
import {getKeys} from './object-utils';
// todo(aleksanderbodurri) pull this out of this file
const METADATA_PROPERTY_NAME = '__ngContext__';
export interface CompositeType {
type: Extract<PropType, PropType.Array | PropType.Object>;
type: Extract<PropType, PropType.Array|PropType.Object>;
prop: any;
}
export interface TerminalType {
type: Exclude<PropType, PropType.Array | PropType.Object>;
type: Exclude<PropType, PropType.Array|PropType.Object>;
prop: any;
}
export type PropertyData = TerminalType | CompositeType;
export type PropertyData = TerminalType|CompositeType;
export type Formatter<Result> = {
[key in PropType]: (data: any) => Result;
@@ -25,7 +26,7 @@ interface LevelOptions {
level?: number;
}
const serializable: { [key in PropType]: boolean } = {
const serializable: {[key in PropType]: boolean} = {
[PropType.Boolean]: true,
[PropType.String]: true,
[PropType.Null]: true,
@@ -57,7 +58,7 @@ const typeToDescriptorPreview: Formatter<string> = {
[PropType.Unknown]: (_: any) => 'unknown',
};
type Key = string | number;
type Key = string|number;
const ignoreList: Set<Key> = new Set([METADATA_PROPERTY_NAME, '__ngSimpleChanges__']);
const shallowPropTypeToTreeMetaData = {
@@ -107,7 +108,7 @@ const shallowPropTypeToTreeMetaData = {
},
};
const isEditable = (instance: any, propName: string | number, propData: TerminalType) => {
const isEditable = (instance: any, propName: string|number, propData: TerminalType) => {
if (typeof propName === 'symbol') {
return false;
}
@@ -124,7 +125,7 @@ const isEditable = (instance: any, propName: string | number, propData: Terminal
return shallowPropTypeToTreeMetaData[propData.type].editable;
};
const hasValue = (obj: {}, prop: string | number) => {
const hasValue = (obj: {}, prop: string|number) => {
const descriptor = Object.getOwnPropertyDescriptor(obj, prop);
if (!descriptor?.get && typeof descriptor?.value === 'undefined') {
return false;
@@ -132,133 +133,123 @@ const hasValue = (obj: {}, prop: string | number) => {
return true;
};
const getPreview = (instance: {}, propName: string | number, propData: TerminalType | CompositeType) => {
return hasValue(instance, propName) ? typeToDescriptorPreview[propData.type](propData.prop) : SETTER_FIELD_PREVIEW;
};
const getPreview =
(instance: {}, propName: string|number, propData: TerminalType|CompositeType) => {
return hasValue(instance, propName) ? typeToDescriptorPreview[propData.type](propData.prop) :
SETTER_FIELD_PREVIEW;
};
const SETTER_FIELD_PREVIEW = '[setter]';
export const createShallowSerializedDescriptor = (
instance: any,
propName: string | number,
propData: TerminalType
): Descriptor => {
const { type } = propData;
export const createShallowSerializedDescriptor =
(instance: any, propName: string|number, propData: TerminalType): Descriptor => {
const {type} = propData;
const shallowSerializedDescriptor: Descriptor = {
type,
expandable: shallowPropTypeToTreeMetaData[type].expandable,
editable: isEditable(instance, propName, propData),
preview: getPreview(instance, propName, propData),
};
const shallowSerializedDescriptor: Descriptor = {
type,
expandable: shallowPropTypeToTreeMetaData[type].expandable,
editable: isEditable(instance, propName, propData),
preview: getPreview(instance, propName, propData),
};
if (propData.prop !== undefined && serializable[type]) {
shallowSerializedDescriptor.value = propData.prop;
}
if (propData.prop !== undefined && serializable[type]) {
shallowSerializedDescriptor.value = propData.prop;
}
return shallowSerializedDescriptor;
};
return shallowSerializedDescriptor;
};
export const createLevelSerializedDescriptor = (
instance: {},
propName: string | number,
propData: CompositeType,
levelOptions: LevelOptions,
continuation: (instance: any, propName: string | number, level?: number, max?: number) => void
): Descriptor => {
const { type, prop } = propData;
export const createLevelSerializedDescriptor =
(instance: {}, propName: string|number, propData: CompositeType, levelOptions: LevelOptions,
continuation: (instance: any, propName: string|number, level?: number, max?: number) => void):
Descriptor => {
const {type, prop} = propData;
const levelSerializedDescriptor: Descriptor = {
type,
editable: false,
expandable: getKeys(prop).length > 0,
preview: getPreview(instance, propName, propData),
};
const levelSerializedDescriptor: Descriptor = {
type,
editable: false,
expandable: getKeys(prop).length > 0,
preview: getPreview(instance, propName, propData),
};
if (levelOptions.level !== undefined && levelOptions.currentLevel < levelOptions.level) {
const value = getLevelDescriptorValue(propData, levelOptions, continuation);
if (value !== undefined) {
levelSerializedDescriptor.value = value;
}
}
if (levelOptions.level !== undefined && levelOptions.currentLevel < levelOptions.level) {
const value = getLevelDescriptorValue(propData, levelOptions, continuation);
if (value !== undefined) {
levelSerializedDescriptor.value = value;
}
}
return levelSerializedDescriptor;
};
return levelSerializedDescriptor;
};
export const createNestedSerializedDescriptor = (
instance: {},
propName: string | number,
propData: CompositeType,
levelOptions: LevelOptions,
nodes: NestedProp[],
nestedSerializer: (instance: any, propName: string, nodes: NestedProp[], currentLevel: number, level?: number) => void
): Descriptor => {
const { type, prop } = propData;
export const createNestedSerializedDescriptor =
(instance: {}, propName: string|number, propData: CompositeType, levelOptions: LevelOptions,
nodes: NestedProp[],
nestedSerializer: (
instance: any, propName: string, nodes: NestedProp[], currentLevel: number,
level?: number) => void): Descriptor => {
const {type, prop} = propData;
const nestedSerializedDescriptor: Descriptor = {
type,
editable: false,
expandable: getKeys(prop).length > 0,
preview: getPreview(instance, propName, propData),
};
const nestedSerializedDescriptor: Descriptor = {
type,
editable: false,
expandable: getKeys(prop).length > 0,
preview: getPreview(instance, propName, propData),
};
if (nodes && nodes.length) {
const value = getNestedDescriptorValue(propData, levelOptions, nodes, nestedSerializer);
if (value !== undefined) {
nestedSerializedDescriptor.value = value;
}
}
return nestedSerializedDescriptor;
};
const getNestedDescriptorValue = (
propData: CompositeType,
levelOptions: LevelOptions,
nodes: NestedProp[],
nestedSerializer: (
instance: any,
propName: string | number,
nodes: NestedProp[],
currentLevel: number,
level?: number
) => void
) => {
const { type, prop } = propData;
const { currentLevel } = levelOptions;
switch (type) {
case PropType.Array:
return nodes.map((nestedProp) => nestedSerializer(prop, nestedProp.name, nestedProp.children, currentLevel + 1));
case PropType.Object:
return nodes.reduce((accumulator, nestedProp) => {
if (prop.hasOwnProperty(nestedProp.name) && !ignoreList.has(nestedProp.name)) {
accumulator[nestedProp.name] = nestedSerializer(prop, nestedProp.name, nestedProp.children, currentLevel + 1);
if (nodes && nodes.length) {
const value = getNestedDescriptorValue(propData, levelOptions, nodes, nestedSerializer);
if (value !== undefined) {
nestedSerializedDescriptor.value = value;
}
return accumulator;
}, {});
}
};
}
return nestedSerializedDescriptor;
};
const getLevelDescriptorValue = (
propData: CompositeType,
levelOptions: LevelOptions,
continuation: (instance: any, propName: string | number, level?: number, max?: number) => void
) => {
const { type, prop } = propData;
const { currentLevel, level } = levelOptions;
const getNestedDescriptorValue =
(propData: CompositeType, levelOptions: LevelOptions, nodes: NestedProp[],
nestedSerializer: (
instance: any, propName: string|number, nodes: NestedProp[], currentLevel: number,
level?: number) => void) => {
const {type, prop} = propData;
const {currentLevel} = levelOptions;
switch (type) {
case PropType.Array:
return prop.map((_: any, idx: number) => continuation(prop, idx, currentLevel + 1, level));
case PropType.Object:
return getKeys(prop).reduce((accumulator, propName) => {
if (!ignoreList.has(propName)) {
accumulator[propName] = continuation(prop, propName, currentLevel + 1, level);
}
return accumulator;
}, {});
}
};
switch (type) {
case PropType.Array:
return nodes.map(
(nestedProp) =>
nestedSerializer(prop, nestedProp.name, nestedProp.children, currentLevel + 1));
case PropType.Object:
return nodes.reduce((accumulator, nestedProp) => {
if (prop.hasOwnProperty(nestedProp.name) && !ignoreList.has(nestedProp.name)) {
accumulator[nestedProp.name] =
nestedSerializer(prop, nestedProp.name, nestedProp.children, currentLevel + 1);
}
return accumulator;
}, {});
}
};
const getLevelDescriptorValue =
(propData: CompositeType, levelOptions: LevelOptions,
continuation: (instance: any, propName: string|number, level?: number, max?: number) =>
void) => {
const {type, prop} = propData;
const {currentLevel, level} = levelOptions;
switch (type) {
case PropType.Array:
return prop.map(
(_: any, idx: number) => continuation(prop, idx, currentLevel + 1, level));
case PropType.Object:
return getKeys(prop).reduce((accumulator, propName) => {
if (!ignoreList.has(propName)) {
accumulator[propName] = continuation(prop, propName, currentLevel + 1, level);
}
return accumulator;
}, {});
}
};
const truncate = (str: string, max = 20): string => {
if (str.length > max) {
@@ -1,5 +1,6 @@
import { deeplySerializeSelectedProperties } from './state-serializer';
import { PropType } from 'protocol';
import {PropType} from 'protocol';
import {deeplySerializeSelectedProperties} from './state-serializer';
const QUERY_1_1 = [];
@@ -173,27 +174,26 @@ describe('deeplySerializeSelectedProperties', () => {
it('should work with getters', () => {
const result = deeplySerializeSelectedProperties(
{
get foo(): any {
return {
baz: {
qux: 3,
},
};
},
},
[
{
name: 'foo',
children: [
{
name: 'baz',
children: [],
},
],
get foo(): any {
return {
baz: {
qux: 3,
},
};
},
},
]
);
[
{
name: 'foo',
children: [
{
name: 'baz',
children: [],
},
],
},
]);
expect(result).toEqual({
foo: {
type: PropType.Object,
@@ -214,17 +214,16 @@ describe('deeplySerializeSelectedProperties', () => {
it('should getters should be readonly', () => {
const result = deeplySerializeSelectedProperties(
{
get foo(): number {
return 42;
{
get foo(): number {
return 42;
},
get bar(): number {
return 42;
},
set bar(val: number) {},
},
get bar(): number {
return 42;
},
set bar(val: number) {},
},
[]
);
[]);
expect(result).toEqual({
foo: {
type: PropType.Number,
@@ -247,52 +246,51 @@ describe('deeplySerializeSelectedProperties', () => {
it('should return the precise path requested', () => {
const result = deeplySerializeSelectedProperties(
{
state: {
nested: {
props: {
foo: 1,
bar: 2,
},
[Symbol(3)](): number {
return 1.618;
},
get foo(): number {
return 42;
{
state: {
nested: {
props: {
foo: 1,
bar: 2,
},
[Symbol(3)](): number {
return 1.618;
},
get foo(): number {
return 42;
},
},
},
},
},
[
{
name: 'state',
children: [
{
name: 'nested',
children: [
{
name: 'props',
children: [
{
name: 'foo',
children: [],
},
{
name: 'bar',
children: [],
},
],
},
{
name: 'foo',
children: [],
},
],
},
],
},
]
);
[
{
name: 'state',
children: [
{
name: 'nested',
children: [
{
name: 'props',
children: [
{
name: 'foo',
children: [],
},
{
name: 'bar',
children: [],
},
],
},
{
name: 'foo',
children: [],
},
],
},
],
},
]);
expect(result).toEqual({
state: {
type: PropType.Object,
@@ -344,14 +342,13 @@ describe('deeplySerializeSelectedProperties', () => {
it('should not show setters at all when associated getters or values are unavailable', () => {
const result = deeplySerializeSelectedProperties(
{
set foo(_: any) {},
get bar(): number {
return 1;
{
set foo(_: any) {},
get bar(): number {
return 1;
},
},
},
[]
);
[]);
expect(result).toEqual({
foo: {
type: PropType.Undefined,
@@ -1,11 +1,7 @@
import { Descriptor, NestedProp, PropType } from 'protocol';
import {
createLevelSerializedDescriptor,
createNestedSerializedDescriptor,
createShallowSerializedDescriptor,
PropertyData,
} from './serialized-descriptor-factory';
import { getKeys } from './object-utils';
import {Descriptor, NestedProp, PropType} from 'protocol';
import {getKeys} from './object-utils';
import {createLevelSerializedDescriptor, createNestedSerializedDescriptor, createShallowSerializedDescriptor, PropertyData,} from './serialized-descriptor-factory';
// todo(aleksanderbodurri) pull this out of this file
const METADATA_PROPERTY_NAME = '__ngContext__';
@@ -51,91 +47,80 @@ const getPropType = (prop: any): PropType => {
return PropType.Unknown;
};
const nestedSerializer = (
instance: any,
propName: string | number,
nodes: NestedProp[],
currentLevel = 0,
level = MAX_LEVEL
): Descriptor => {
const serializableInstance = instance[propName];
const propData: PropertyData = { prop: serializableInstance, type: getPropType(serializableInstance) };
const nestedSerializer =
(instance: any, propName: string|number, nodes: NestedProp[], currentLevel = 0,
level = MAX_LEVEL): Descriptor => {
const serializableInstance = instance[propName];
const propData:
PropertyData = {prop: serializableInstance, type: getPropType(serializableInstance)};
if (currentLevel < level) {
return levelSerializer(instance, propName, currentLevel, level, nestedSerializerContinuation(nodes, level));
}
if (currentLevel < level) {
return levelSerializer(
instance, propName, currentLevel, level, nestedSerializerContinuation(nodes, level));
}
switch (propData.type) {
case PropType.Array:
case PropType.Object:
return createNestedSerializedDescriptor(
instance,
propName,
propData,
{ level, currentLevel },
nodes,
nestedSerializer
);
default:
return createShallowSerializedDescriptor(instance, propName, propData);
}
};
switch (propData.type) {
case PropType.Array:
case PropType.Object:
return createNestedSerializedDescriptor(
instance, propName, propData, {level, currentLevel}, nodes, nestedSerializer);
default:
return createShallowSerializedDescriptor(instance, propName, propData);
}
};
const nestedSerializerContinuation =
(nodes: NestedProp[], level: number) => (instance: any, propName: string, nestedLevel: number) => {
const idx = nodes.findIndex((v) => v.name === propName);
if (idx < 0) {
// The property is not specified in the query.
return nestedSerializer(instance, propName, [], nestedLevel, level);
}
return nestedSerializer(instance, propName, nodes[idx].children, nestedLevel, level);
};
const nestedSerializerContinuation = (nodes: NestedProp[], level: number) =>
(instance: any, propName: string, nestedLevel: number) => {
const idx = nodes.findIndex((v) => v.name === propName);
if (idx < 0) {
// The property is not specified in the query.
return nestedSerializer(instance, propName, [], nestedLevel, level);
}
return nestedSerializer(instance, propName, nodes[idx].children, nestedLevel, level);
};
const levelSerializer = (
instance: any,
propName: string | number,
currentLevel = 0,
level = MAX_LEVEL,
continuation = levelSerializer
): Descriptor => {
const serializableInstance = instance[propName];
const propData: PropertyData = { prop: serializableInstance, type: getPropType(serializableInstance) };
const levelSerializer =
(instance: any, propName: string|number, currentLevel = 0, level = MAX_LEVEL,
continuation = levelSerializer): Descriptor => {
const serializableInstance = instance[propName];
const propData:
PropertyData = {prop: serializableInstance, type: getPropType(serializableInstance)};
switch (propData.type) {
case PropType.Array:
case PropType.Object:
return createLevelSerializedDescriptor(instance, propName, propData, { level, currentLevel }, continuation);
default:
return createShallowSerializedDescriptor(instance, propName, propData);
}
};
switch (propData.type) {
case PropType.Array:
case PropType.Object:
return createLevelSerializedDescriptor(
instance, propName, propData, {level, currentLevel}, continuation);
default:
return createShallowSerializedDescriptor(instance, propName, propData);
}
};
export const serializeDirectiveState = (instance: object, levels = MAX_LEVEL): { [key: string]: Descriptor } => {
const result = {};
getKeys(instance).forEach((prop) => {
if (typeof prop === 'string' && ignoreList.has(prop)) {
return;
}
result[prop] = levelSerializer(instance, prop, null, 0, levels);
});
return result;
};
export const serializeDirectiveState =
(instance: object, levels = MAX_LEVEL): {[key: string]: Descriptor} => {
const result = {};
getKeys(instance).forEach((prop) => {
if (typeof prop === 'string' && ignoreList.has(prop)) {
return;
}
result[prop] = levelSerializer(instance, prop, null, 0, levels);
});
return result;
};
export const deeplySerializeSelectedProperties = (
instance: any,
props: NestedProp[]
): { [name: string]: Descriptor } => {
const result = {};
getKeys(instance).forEach((prop) => {
if (ignoreList.has(prop)) {
return;
}
const idx = props.findIndex((v) => v.name === prop);
if (idx < 0) {
result[prop] = levelSerializer(instance, prop);
} else {
result[prop] = nestedSerializer(instance, prop, props[idx].children);
}
});
return result;
};
export const deeplySerializeSelectedProperties =
(instance: any, props: NestedProp[]): {[name: string]: Descriptor} => {
const result = {};
getKeys(instance).forEach((prop) => {
if (ignoreList.has(prop)) {
return;
}
const idx = props.findIndex((v) => v.name === prop);
if (idx < 0) {
result[prop] = levelSerializer(instance, prop);
} else {
result[prop] = nestedSerializer(instance, prop, props[idx].children);
}
});
return result;
};
@@ -3,4 +3,4 @@
*/
export * from './lib';
export { findNodeFromSerializedPosition } from './lib/component-tree';
export {findNodeFromSerializedPosition} from './lib/component-tree';
@@ -2,8 +2,9 @@
import 'zone.js/dist/zone';
import 'zone.js/dist/zone-testing';
import { getTestBed } from '@angular/core/testing';
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
import {getTestBed} from '@angular/core/testing';
import {BrowserDynamicTestingModule, platformBrowserDynamicTesting} from '@angular/platform-browser-dynamic/testing';
declare const require: any;
+2 -2
View File
@@ -1,7 +1,7 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
module.exports = function(config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
@@ -13,7 +13,7 @@ module.exports = function (config) {
require('@angular-devkit/build-angular/plugins/karma'),
],
client: {
clearContext: true, // leave Jasmine Spec Runner output visible in browser
clearContext: true, // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, '../../coverage/ng-devtools'),
@@ -1,4 +1,4 @@
import { DirectivePosition, ElementPosition } from 'protocol';
import {DirectivePosition, ElementPosition} from 'protocol';
export abstract class ApplicationOperations {
abstract viewSource(position: ElementPosition): void;
@@ -1,13 +1,15 @@
/// <reference types="resize-observer-browser" />
import { AfterViewInit, Component, Input, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { Events, MessageBus, Route } from 'protocol';
import { DirectiveExplorerComponent } from './directive-explorer/directive-explorer.component';
import { ApplicationEnvironment } from '../application-environment/index';
import { MatSlideToggleChange } from '@angular/material/slide-toggle';
import { TabUpdate } from './tab-update/index';
import { Theme, ThemeService } from '../theme-service';
import { Subscription } from 'rxjs';
import { MatTabNav } from '@angular/material/tabs';
import {AfterViewInit, Component, Input, OnDestroy, OnInit, ViewChild} from '@angular/core';
import {MatSlideToggleChange} from '@angular/material/slide-toggle';
import {MatTabNav} from '@angular/material/tabs';
import {Events, MessageBus, Route} from 'protocol';
import {Subscription} from 'rxjs';
import {ApplicationEnvironment} from '../application-environment/index';
import {Theme, ThemeService} from '../theme-service';
import {DirectiveExplorerComponent} from './directive-explorer/directive-explorer.component';
import {TabUpdate} from './tab-update/index';
@Component({
selector: 'ng-devtools-tabs',
@@ -15,11 +17,11 @@ import { MatTabNav } from '@angular/material/tabs';
styleUrls: ['./devtools-tabs.component.scss'],
})
export class DevToolsTabsComponent implements OnInit, OnDestroy, AfterViewInit {
@Input() angularVersion: string | undefined = undefined;
@Input() angularVersion: string|undefined = undefined;
@ViewChild(DirectiveExplorerComponent) directiveExplorer: DirectiveExplorerComponent;
@ViewChild('navBar', { static: true }) navbar: MatTabNav;
@ViewChild('navBar', {static: true}) navbar: MatTabNav;
activeTab: 'Components' | 'Profiler' | 'Router Tree' = 'Components';
activeTab: 'Components'|'Profiler'|'Router Tree' = 'Components';
inspectorRunning = false;
routerTreeEnabled = false;
@@ -31,14 +33,13 @@ export class DevToolsTabsComponent implements OnInit, OnDestroy, AfterViewInit {
routes: Route[] = [];
constructor(
public tabUpdate: TabUpdate,
public themeService: ThemeService,
private _messageBus: MessageBus<Events>,
private _applicationEnvironment: ApplicationEnvironment
) {}
public tabUpdate: TabUpdate, public themeService: ThemeService,
private _messageBus: MessageBus<Events>,
private _applicationEnvironment: ApplicationEnvironment) {}
ngOnInit(): void {
this._currentThemeSubscription = this.themeService.currentTheme.subscribe((theme) => (this.currentTheme = theme));
this._currentThemeSubscription =
this.themeService.currentTheme.subscribe((theme) => (this.currentTheme = theme));
this._messageBus.on('updateRouterTree', (routes) => {
this.routes = routes || [];
@@ -62,7 +63,7 @@ export class DevToolsTabsComponent implements OnInit, OnDestroy, AfterViewInit {
return this._applicationEnvironment.environment.LATEST_SHA.slice(0, 8);
}
changeTab(tab: 'Profiler' | 'Components' | 'Router Tree'): void {
changeTab(tab: 'Profiler'|'Components'|'Router Tree'): void {
this.activeTab = tab;
this.tabUpdate.notify();
if (tab === 'Router Tree') {
@@ -90,6 +91,7 @@ export class DevToolsTabsComponent implements OnInit, OnDestroy, AfterViewInit {
}
toggleTimingAPI(change: MatSlideToggleChange): void {
change.checked ? this._messageBus.emit('enableTimingAPI') : this._messageBus.emit('disableTimingAPI');
change.checked ? this._messageBus.emit('enableTimingAPI') :
this._messageBus.emit('disableTimingAPI');
}
}
@@ -1,18 +1,17 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { DevToolsTabsComponent } from './devtools-tabs.component';
import {CommonModule} from '@angular/common';
import {NgModule} from '@angular/core';
import {MatButtonModule} from '@angular/material/button';
import {MatIconModule} from '@angular/material/icon';
import {MatMenuModule} from '@angular/material/menu';
import {MatSlideToggleModule} from '@angular/material/slide-toggle';
import {MatTabsModule} from '@angular/material/tabs';
import {MatTooltipModule} from '@angular/material/tooltip';
import { MatTabsModule } from '@angular/material/tabs';
import { MatIconModule } from '@angular/material/icon';
import { DirectiveExplorerModule } from './directive-explorer/directive-explorer.module';
import { ProfilerModule } from './profiler/profiler.module';
import { RouterTreeModule } from './router-tree/router-tree.module';
import { MatMenuModule } from '@angular/material/menu';
import { MatButtonModule } from '@angular/material/button';
import { MatTooltipModule } from '@angular/material/tooltip';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { TabUpdate } from './tab-update/index';
import {DevToolsTabsComponent} from './devtools-tabs.component';
import {DirectiveExplorerModule} from './directive-explorer/directive-explorer.module';
import {ProfilerModule} from './profiler/profiler.module';
import {RouterTreeModule} from './router-tree/router-tree.module';
import {TabUpdate} from './tab-update/index';
@NgModule({
declarations: [DevToolsTabsComponent],
@@ -31,4 +30,5 @@ import { TabUpdate } from './tab-update/index';
providers: [TabUpdate],
exports: [DevToolsTabsComponent],
})
export class DevToolsTabModule {}
export class DevToolsTabModule {
}
@@ -1,7 +1,9 @@
import { DevToolsTabsComponent } from './devtools-tabs.component';
import { ApplicationEnvironment } from '../application-environment/index';
import { Events, MessageBus } from 'protocol';
import { TabUpdate } from './tab-update/index';
import {Events, MessageBus} from 'protocol';
import {ApplicationEnvironment} from '../application-environment/index';
import {DevToolsTabsComponent} from './devtools-tabs.component';
import {TabUpdate} from './tab-update/index';
describe('DevtoolsTabsComponent', () => {
let messageBusMock: MessageBus<Events>;
@@ -15,11 +17,7 @@ describe('DevtoolsTabsComponent', () => {
mockThemeService = {};
comp = new DevToolsTabsComponent(
new TabUpdate(),
mockThemeService as any,
messageBusMock,
applicationEnvironmentMock
);
new TabUpdate(), mockThemeService as any, messageBusMock, applicationEnvironmentMock);
});
it('should create instance from class', () => {
@@ -1,90 +1,84 @@
import { DefaultIterableDiffer } from '@angular/core';
// tslint:disable-next-line:deprecation
import {DefaultIterableDiffer} from '@angular/core';
export interface MovedRecord {
currentIndex: number;
previousIndex: number;
}
export const diff = <T>(
differ: DefaultIterableDiffer<T>,
a: T[],
b: T[]
): {
newItems: T[];
removedItems: T[];
movedItems: T[];
} => {
differ.diff(a);
differ.diff(b);
export const diff = <T>(differ: DefaultIterableDiffer<T>, a: T[], b: T[]):
{newItems: T[]; removedItems: T[]; movedItems: T[];} => {
differ.diff(a);
differ.diff(b);
const alreadySet: boolean[] = [];
const movedItems: T[] = [];
const alreadySet: boolean[] = [];
const movedItems: T[] = [];
// We first have to set the moved items to their correct positions.
// Keep in mind that the track by function may not guarantee
// that we haven't changed any of the items' props.
differ.forEachMovedItem(record => {
if (record.currentIndex === null) {
return;
}
if (record.previousIndex === null) {
return;
}
// We want to preserve the reference so that a default
// track by function used by the CDK, for instance, can
// recognize that this item's identity hasn't changed.
// At the same time, since we don't have the guarantee
// that we haven't already set the previousIndex while
// iterating, we need to check that. If we have, we assign
// this array item to a new object. We don't want to risk
// changing the properties of an object we'll use in the future.
if (!alreadySet[record.previousIndex]) {
a[record.currentIndex] = a[record.previousIndex];
} else {
a[record.currentIndex] = {} as T;
}
Object.keys(b[record.currentIndex]).forEach(prop => {
// TypeScript's type inference didn't follow the check from above.
if (record.currentIndex === null) {
return;
// We first have to set the moved items to their correct positions.
// Keep in mind that the track by function may not guarantee
// that we haven't changed any of the items' props.
differ.forEachMovedItem(record => {
if (record.currentIndex === null) {
return;
}
if (record.previousIndex === null) {
return;
}
// We want to preserve the reference so that a default
// track by function used by the CDK, for instance, can
// recognize that this item's identity hasn't changed.
// At the same time, since we don't have the guarantee
// that we haven't already set the previousIndex while
// iterating, we need to check that. If we have, we assign
// this array item to a new object. We don't want to risk
// changing the properties of an object we'll use in the future.
if (!alreadySet[record.previousIndex]) {
a[record.currentIndex] = a[record.previousIndex];
} else {
a[record.currentIndex] = {} as T;
}
Object.keys(b[record.currentIndex]).forEach(prop => {
// TypeScript's type inference didn't follow the check from above.
if (record.currentIndex === null) {
return;
}
a[record.currentIndex][prop] = b[record.currentIndex][prop];
});
if (!alreadySet[record.previousIndex]) {
// tslint:disable-next-line: no-non-null-assertion
a[record.previousIndex] = null!;
}
alreadySet[record.currentIndex] = true;
movedItems.push(a[record.currentIndex]);
});
// Now we can set the new items and remove the deleted ones.
const newItems: T[] = [];
const removedItems: T[] = [];
differ.forEachAddedItem(record => {
if (record.currentIndex !== null && record.previousIndex === null) {
a[record.currentIndex] = record.item;
alreadySet[record.currentIndex] = true;
newItems.push(record.item);
}
});
differ.forEachRemovedItem(record => {
if (record.previousIndex === null) {
return;
}
if (record.currentIndex === null && !alreadySet[record.previousIndex]) {
// tslint:disable-next-line: no-non-null-assertion
a[record.previousIndex] = null!;
}
removedItems.push(record.item);
});
for (let i = a.length - 1; i >= 0; i--) {
if (a[i] === null) {
a.splice(i, 1);
}
}
a[record.currentIndex][prop] = b[record.currentIndex][prop];
});
if (!alreadySet[record.previousIndex]) {
// tslint:disable-next-line: no-non-null-assertion
a[record.previousIndex] = null!;
}
alreadySet[record.currentIndex] = true;
movedItems.push(a[record.currentIndex]);
});
// Now we can set the new items and remove the deleted ones.
const newItems: T[] = [];
const removedItems: T[] = [];
differ.forEachAddedItem(record => {
if (record.currentIndex !== null && record.previousIndex === null) {
a[record.currentIndex] = record.item;
alreadySet[record.currentIndex] = true;
newItems.push(record.item);
}
});
differ.forEachRemovedItem(record => {
if (record.previousIndex === null) {
return;
}
if (record.currentIndex === null && !alreadySet[record.previousIndex]) {
// tslint:disable-next-line: no-non-null-assertion
a[record.previousIndex] = null!;
}
removedItems.push(record.item);
});
for (let i = a.length - 1; i >= 0; i--) {
if (a[i] === null) {
a.splice(i, 1);
}
}
return { newItems, removedItems, movedItems };
};
return {newItems, removedItems, movedItems};
};
@@ -1,35 +1,15 @@
import {
ChangeDetectorRef,
Component,
ElementRef,
EventEmitter,
Input,
NgZone,
OnDestroy,
OnInit,
Output,
ViewChild,
} from '@angular/core';
import {
MessageBus,
Events,
DevToolsNode,
ComponentExplorerViewQuery,
ComponentExplorerView,
ElementPosition,
PropertyQuery,
PropertyQueryTypes,
DirectivePosition,
} from 'protocol';
import { IndexedNode } from './directive-forest/index-forest';
import { ApplicationOperations } from '../../application-operations/index';
import { ElementPropertyResolver } from './property-resolver/element-property-resolver';
import { FlatNode } from './directive-forest/component-data-source';
import { FlatNode as PropertyFlatNode } from './property-resolver/element-property-resolver';
import { DirectiveForestComponent } from './directive-forest/directive-forest.component';
import { constructPathOfKeysToPropertyValue } from './property-resolver/directive-property-resolver';
import { BreadcrumbsComponent } from './directive-forest/breadcrumbs/breadcrumbs.component';
import { SplitComponent } from '../../../lib/vendor/angular-split/public_api';
import {ChangeDetectorRef, Component, ElementRef, EventEmitter, Input, NgZone, OnDestroy, OnInit, Output, ViewChild,} from '@angular/core';
import {ComponentExplorerView, ComponentExplorerViewQuery, DevToolsNode, DirectivePosition, ElementPosition, Events, MessageBus, PropertyQuery, PropertyQueryTypes,} from 'protocol';
import {SplitComponent} from '../../../lib/vendor/angular-split/public_api';
import {ApplicationOperations} from '../../application-operations/index';
import {BreadcrumbsComponent} from './directive-forest/breadcrumbs/breadcrumbs.component';
import {FlatNode} from './directive-forest/component-data-source';
import {DirectiveForestComponent} from './directive-forest/directive-forest.component';
import {IndexedNode} from './directive-forest/index-forest';
import {constructPathOfKeysToPropertyValue} from './property-resolver/directive-property-resolver';
import {ElementPropertyResolver, FlatNode as PropertyFlatNode} from './property-resolver/element-property-resolver';
const sameDirectives = (a: IndexedNode, b: IndexedNode) => {
if ((a.component && !b.component) || (!a.component && b.component)) {
@@ -64,40 +44,36 @@ export class DirectiveExplorerComponent implements OnInit, OnDestroy {
@ViewChild(DirectiveForestComponent) directiveForest: DirectiveForestComponent;
@ViewChild(BreadcrumbsComponent) breadcrumbs: BreadcrumbsComponent;
@ViewChild(SplitComponent, { static: true, read: ElementRef }) splitElementRef: ElementRef;
@ViewChild('directiveForestSplitArea', { static: true, read: ElementRef }) directiveForestSplitArea: ElementRef;
@ViewChild(SplitComponent, {static: true, read: ElementRef}) splitElementRef: ElementRef;
@ViewChild('directiveForestSplitArea', {static: true, read: ElementRef})
directiveForestSplitArea: ElementRef;
currentSelectedElement: IndexedNode | null = null;
currentSelectedElement: IndexedNode|null = null;
forest: DevToolsNode[];
splitDirection: 'horizontal' | 'vertical' = 'horizontal';
parents: FlatNode[] | null = null;
splitDirection: 'horizontal'|'vertical' = 'horizontal';
parents: FlatNode[]|null = null;
private _resizeObserver = new ResizeObserver((entries) =>
this._ngZone.run(() => {
const resizedEntry = entries[0];
private _resizeObserver = new ResizeObserver((entries) => this._ngZone.run(() => {
const resizedEntry = entries[0];
if (resizedEntry.target === this.splitElementRef.nativeElement) {
this.splitDirection = resizedEntry.contentRect.width <= 500 ? 'vertical' : 'horizontal';
}
if (resizedEntry.target === this.splitElementRef.nativeElement) {
this.splitDirection = resizedEntry.contentRect.width <= 500 ? 'vertical' : 'horizontal';
}
if (!this.breadcrumbs) {
return;
}
if (!this.breadcrumbs) {
return;
}
this.breadcrumbs.updateScrollButtonVisibility();
})
);
this.breadcrumbs.updateScrollButtonVisibility();
}));
private _clickedElement: IndexedNode | null = null;
private _clickedElement: IndexedNode|null = null;
private _refreshRetryTimeout: any = null;
constructor(
private _appOperations: ApplicationOperations,
private _messageBus: MessageBus<Events>,
private _propResolver: ElementPropertyResolver,
private _cdr: ChangeDetectorRef,
private _ngZone: NgZone
) {}
private _appOperations: ApplicationOperations, private _messageBus: MessageBus<Events>,
private _propResolver: ElementPropertyResolver, private _cdr: ChangeDetectorRef,
private _ngZone: NgZone) {}
ngOnInit(): void {
this.subscribeToBackendEvents();
@@ -111,7 +87,7 @@ export class DirectiveExplorerComponent implements OnInit, OnDestroy {
this._resizeObserver.unobserve(this.directiveForestSplitArea.nativeElement);
}
handleNodeSelection(node: IndexedNode | null): void {
handleNodeSelection(node: IndexedNode|null): void {
if (node) {
// We want to guarantee that we're not reusing any of the previous properties.
// That's possible if the user has selected an NgForOf and after that
@@ -141,7 +117,8 @@ export class DirectiveExplorerComponent implements OnInit, OnDestroy {
}
refresh(): void {
const success = this._messageBus.emit('getLatestComponentExplorerView', [this._constructViewQuery()]);
const success =
this._messageBus.emit('getLatestComponentExplorerView', [this._constructViewQuery()]);
// If the event was not throttled, we no longer need to retry.
if (success) {
clearTimeout(this._refreshRetryTimeout);
@@ -176,7 +153,7 @@ export class DirectiveExplorerComponent implements OnInit, OnDestroy {
this._messageBus.emit('removeHighlightOverlay');
}
private _constructViewQuery(): ComponentExplorerViewQuery | undefined {
private _constructViewQuery(): ComponentExplorerViewQuery|undefined {
if (!this._clickedElement) {
return;
}
@@ -191,11 +168,8 @@ export class DirectiveExplorerComponent implements OnInit, OnDestroy {
// We check if we're dealing with the same instance (i.e., if we have the same
// set of directives and component on it), if we do, we want to get the same
// set of properties which are already expanded.
if (
!this._clickedElement ||
!this.currentSelectedElement ||
!sameDirectives(this._clickedElement, this.currentSelectedElement)
) {
if (!this._clickedElement || !this.currentSelectedElement ||
!sameDirectives(this._clickedElement, this.currentSelectedElement)) {
return {
type: PropertyQueryTypes.All,
};
@@ -218,12 +192,13 @@ export class DirectiveExplorerComponent implements OnInit, OnDestroy {
this.directiveForest.handleSelect(node);
}
handleSetParents(parents: FlatNode[] | null): void {
handleSetParents(parents: FlatNode[]|null): void {
this.parents = parents;
this._cdr.detectChanges();
}
inspect({ node, directivePosition }: { node: PropertyFlatNode; directivePosition: DirectivePosition }): void {
inspect({node, directivePosition}:
{node: PropertyFlatNode; directivePosition: DirectivePosition}): void {
const objectPath = constructPathOfKeysToPropertyValue(node.prop);
this._appOperations.inspect(directivePosition, objectPath);
}
@@ -1,17 +1,18 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatIconModule } from '@angular/material/icon';
import {ScrollingModule} from '@angular/cdk/scrolling';
import {CommonModule} from '@angular/common';
import {NgModule} from '@angular/core';
import {MatButtonModule} from '@angular/material/button';
import {MatCardModule} from '@angular/material/card';
import {MatIconModule} from '@angular/material/icon';
import {MatSnackBarModule} from '@angular/material/snack-bar';
import {MatTooltipModule} from '@angular/material/tooltip';
import {MatTreeModule} from '@angular/material/tree';
import { DirectiveExplorerComponent } from './directive-explorer.component';
import { MatTreeModule } from '@angular/material/tree';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatSnackBarModule } from '@angular/material/snack-bar';
import { PropertyTabModule } from './property-tab/property-tab.module';
import { DirectiveForestModule } from './directive-forest/directive-forest.module';
import { ScrollingModule } from '@angular/cdk/scrolling';
import { AngularSplitModule } from '../../vendor/angular-split/public_api';
import { MatTooltipModule } from '@angular/material/tooltip';
import {AngularSplitModule} from '../../vendor/angular-split/public_api';
import {DirectiveExplorerComponent} from './directive-explorer.component';
import {DirectiveForestModule} from './directive-forest/directive-forest.module';
import {PropertyTabModule} from './property-tab/property-tab.module';
@NgModule({
declarations: [DirectiveExplorerComponent],
@@ -30,4 +31,5 @@ import { MatTooltipModule } from '@angular/material/tooltip';
MatTooltipModule,
],
})
export class DirectiveExplorerModule {}
export class DirectiveExplorerModule {
}
@@ -1,8 +1,10 @@
import { DirectiveExplorerComponent } from './directive-explorer.component';
import { PropertyQueryTypes } from 'protocol';
import { IndexedNode } from './directive-forest/index-forest';
import {PropertyQueryTypes} from 'protocol';
import {DirectiveExplorerComponent} from './directive-explorer.component';
import {IndexedNode} from './directive-forest/index-forest';
import SpyObj = jasmine.SpyObj;
import { ElementPropertyResolver } from './property-resolver/element-property-resolver';
import {ElementPropertyResolver} from './property-resolver/element-property-resolver';
describe('DirectiveExplorerComponent', () => {
let messageBusMock: any;
@@ -12,17 +14,14 @@ describe('DirectiveExplorerComponent', () => {
let ngZone: any;
beforeEach(() => {
applicationOperationsSpy = jasmine.createSpyObj('_appOperations', ['viewSource', 'selectDomElement']);
applicationOperationsSpy =
jasmine.createSpyObj('_appOperations', ['viewSource', 'selectDomElement']);
messageBusMock = jasmine.createSpyObj('messageBus', ['on', 'once', 'emit', 'destroy']);
cdr = jasmine.createSpyObj('_cdr', ['detectChanges']);
ngZone = jasmine.createSpyObj('_ngZone', ['run']);
comp = new DirectiveExplorerComponent(
applicationOperationsSpy,
messageBusMock,
new ElementPropertyResolver(messageBusMock),
cdr,
ngZone
);
applicationOperationsSpy, messageBusMock, new ElementPropertyResolver(messageBusMock), cdr,
ngZone);
});
it('should create instance from class', () => {
@@ -32,7 +31,8 @@ describe('DirectiveExplorerComponent', () => {
it('subscribe to backend events', () => {
comp.subscribeToBackendEvents();
expect(messageBusMock.on).toHaveBeenCalledTimes(2);
expect(messageBusMock.on).toHaveBeenCalledWith('latestComponentExplorerView', jasmine.any(Function));
expect(messageBusMock.on)
.toHaveBeenCalledWith('latestComponentExplorerView', jasmine.any(Function));
expect(messageBusMock.on).toHaveBeenCalledWith('componentTreeDirty', jasmine.any(Function));
});
@@ -44,18 +44,24 @@ describe('DirectiveExplorerComponent', () => {
it('should emit getLatestComponentExplorerView event with null view query', () => {
comp.refresh();
expect(messageBusMock.emit).toHaveBeenCalledWith('getLatestComponentExplorerView', [undefined]);
expect(messageBusMock.emit).toHaveBeenCalledWith('getLatestComponentExplorerView', [
undefined
]);
});
it('should emit getLatestComponentExplorerView event on refresh with view query no properties', () => {
const currentSelectedElement = jasmine.createSpyObj('currentSelectedElement', ['position', 'children']);
currentSelectedElement.position = [0];
currentSelectedElement.children = [];
comp.currentSelectedElement = currentSelectedElement;
comp.refresh();
expect(comp.currentSelectedElement).toBeTruthy();
expect(messageBusMock.emit).toHaveBeenCalledWith('getLatestComponentExplorerView', [undefined]);
});
it('should emit getLatestComponentExplorerView event on refresh with view query no properties',
() => {
const currentSelectedElement =
jasmine.createSpyObj('currentSelectedElement', ['position', 'children']);
currentSelectedElement.position = [0];
currentSelectedElement.children = [];
comp.currentSelectedElement = currentSelectedElement;
comp.refresh();
expect(comp.currentSelectedElement).toBeTruthy();
expect(messageBusMock.emit).toHaveBeenCalledWith('getLatestComponentExplorerView', [
undefined
]);
});
});
describe('node selection event', () => {
@@ -1,18 +1,8 @@
import {
AfterViewInit,
Component,
ElementRef,
EventEmitter,
HostListener,
Input,
OnChanges,
OnInit,
Output,
ViewChild,
} from '@angular/core';
import { Subject } from 'rxjs';
import { debounceTime } from 'rxjs/operators';
import { FlatNode } from '../component-data-source';
import {AfterViewInit, Component, ElementRef, EventEmitter, HostListener, Input, OnChanges, OnInit, Output, ViewChild,} from '@angular/core';
import {Subject} from 'rxjs';
import {debounceTime} from 'rxjs/operators';
import {FlatNode} from '../component-data-source';
@Component({
selector: 'ng-breadcrumbs',
@@ -33,7 +23,8 @@ export class BreadcrumbsComponent implements OnInit, AfterViewInit, OnChanges {
updateScrollButtonVisibility$ = new Subject<void>();
ngOnInit(): void {
this.updateScrollButtonVisibility$.pipe(debounceTime(100)).subscribe(() => this.updateScrollButtonVisibility());
this.updateScrollButtonVisibility$.pipe(debounceTime(100))
.subscribe(() => this.updateScrollButtonVisibility());
}
ngAfterViewInit(): void {
@@ -55,7 +46,7 @@ export class BreadcrumbsComponent implements OnInit, AfterViewInit, OnChanges {
}
updateScrollButtonVisibility(): void {
const { clientWidth, scrollWidth, scrollLeft } = this.breadcrumbsScrollContent.nativeElement;
const {clientWidth, scrollWidth, scrollLeft} = this.breadcrumbsScrollContent.nativeElement;
this.showScrollLeftButton = scrollLeft > 0;
this.showScrollRightButton = scrollLeft + clientWidth < scrollWidth;
}
@@ -1,13 +1,15 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { BreadcrumbsComponent } from './breadcrumbs.component';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatIconModule } from '@angular/material/icon';
import {CommonModule} from '@angular/common';
import {NgModule} from '@angular/core';
import {MatButtonModule} from '@angular/material/button';
import {MatCardModule} from '@angular/material/card';
import {MatIconModule} from '@angular/material/icon';
import {BreadcrumbsComponent} from './breadcrumbs.component';
@NgModule({
declarations: [BreadcrumbsComponent],
imports: [CommonModule, MatCardModule, MatButtonModule, MatIconModule],
exports: [BreadcrumbsComponent],
})
export class BreadcrumbsModule {}
export class BreadcrumbsModule {
}
@@ -1,6 +1,7 @@
import { FlatTreeControl } from '@angular/cdk/tree';
import { ComponentDataSource, FlatNode } from '.';
import { DevToolsNode } from 'protocol';
import {FlatTreeControl} from '@angular/cdk/tree';
import {DevToolsNode} from 'protocol';
import {ComponentDataSource, FlatNode} from '.';
const tree1: DevToolsNode = {
element: 'app',
@@ -174,10 +175,8 @@ const tree4: DevToolsNode = {
describe('ComponentDataSource', () => {
let dataSource: ComponentDataSource;
const treeControl = new FlatTreeControl<FlatNode>(
(node) => node.level,
(node) => node.expandable
);
const treeControl =
new FlatTreeControl<FlatNode>((node) => node.level, (node) => node.expandable);
beforeEach(() => (dataSource = new ComponentDataSource(treeControl)));
@@ -1,12 +1,13 @@
import { DevToolsNode } from 'protocol';
import { CollectionViewer, DataSource } from '@angular/cdk/collections';
import { BehaviorSubject, merge, Observable } from 'rxjs';
import { MatTreeFlattener } from '@angular/material/tree';
import { FlatTreeControl } from '@angular/cdk/tree';
import { map } from 'rxjs/operators';
import { DefaultIterableDiffer, TrackByFunction } from '@angular/core';
import { IndexedNode, indexForest } from '../index-forest';
import { diff } from '../../../diffing';
import {CollectionViewer, DataSource} from '@angular/cdk/collections';
import {FlatTreeControl} from '@angular/cdk/tree';
import {DefaultIterableDiffer, TrackByFunction} from '@angular/core';
import {MatTreeFlattener} from '@angular/material/tree';
import {DevToolsNode} from 'protocol';
import {BehaviorSubject, merge, Observable} from 'rxjs';
import {map} from 'rxjs/operators';
import {diff} from '../../../diffing';
import {IndexedNode, indexForest} from '../index-forest';
/** Flat node with expandable and level information */
export interface FlatNode {
@@ -22,18 +23,17 @@ export interface FlatNode {
const expandable = (node: IndexedNode) => !!node.children && node.children.length > 0;
const trackBy: TrackByFunction<FlatNode> = (_: number, item: FlatNode) => `${item.id}#${item.expandable}`;
const trackBy: TrackByFunction<FlatNode> = (_: number, item: FlatNode) =>
`${item.id}#${item.expandable}`;
const getId = (node: IndexedNode) => {
let prefix = '';
if (node.component) {
prefix = node.component.id.toString();
}
const dirIds = node.directives
.map((d) => d.id)
.sort((a, b) => {
return a - b;
});
const dirIds = node.directives.map((d) => d.id).sort((a, b) => {
return a - b;
});
return prefix + '-' + dirIds.join('-');
};
@@ -67,29 +67,27 @@ export class ComponentDataSource extends DataSource<FlatNode> {
private _nodeToFlat = new WeakMap<IndexedNode, FlatNode>();
private _treeFlattener = new MatTreeFlattener(
(node: IndexedNode, level: number) => {
if (this._nodeToFlat.has(node)) {
return this._nodeToFlat.get(node);
}
const flatNode: FlatNode = {
expandable: expandable(node),
id: getId(node),
// We can compare the nodes in the navigation functions above
// based on this identifier directly, since it's a reference type
// and the reference is preserved after transformation.
position: node.position,
name: node.component ? node.component.name : node.element,
directives: node.directives.map((d) => d.name).join(', '),
original: node,
level,
};
this._nodeToFlat.set(node, flatNode);
return flatNode;
},
(node) => (node ? node.level : -1),
(node) => (node ? node.expandable : false),
(node) => (node ? node.children : [])
);
(node: IndexedNode, level: number) => {
if (this._nodeToFlat.has(node)) {
return this._nodeToFlat.get(node);
}
const flatNode: FlatNode = {
expandable: expandable(node),
id: getId(node),
// We can compare the nodes in the navigation functions above
// based on this identifier directly, since it's a reference type
// and the reference is preserved after transformation.
position: node.position,
name: node.component ? node.component.name : node.element,
directives: node.directives.map((d) => d.name).join(', '),
original: node,
level,
};
this._nodeToFlat.set(node, flatNode);
return flatNode;
},
(node) => (node ? node.level : -1), (node) => (node ? node.expandable : false),
(node) => (node ? node.children : []));
constructor(private _treeControl: FlatTreeControl<FlatNode>) {
super();
@@ -103,21 +101,20 @@ export class ComponentDataSource extends DataSource<FlatNode> {
return this._expandedData.value;
}
getFlatNodeFromIndexedNode(indexedNode: IndexedNode): FlatNode | undefined {
getFlatNodeFromIndexedNode(indexedNode: IndexedNode): FlatNode|undefined {
return this._nodeToFlat.get(indexedNode);
}
update(
forest: DevToolsNode[],
showCommentNodes: boolean
): { newItems: FlatNode[]; movedItems: FlatNode[]; removedItems: FlatNode[] } {
update(forest: DevToolsNode[], showCommentNodes: boolean):
{newItems: FlatNode[]; movedItems: FlatNode[]; removedItems: FlatNode[]} {
if (!forest) {
return { newItems: [], movedItems: [], removedItems: [] };
return {newItems: [], movedItems: [], removedItems: []};
}
let indexedForest = indexForest(forest);
// We filter comment nodes here because we need to preserve the positions within the component tree.
// We filter comment nodes here because we need to preserve the positions within the component
// tree.
//
// For example:
// ```
@@ -126,8 +123,8 @@ export class ComponentDataSource extends DataSource<FlatNode> {
// ```
//
// #comment's position will be [0] and bar's will be [0, 0]. If we trim #comment nodes earlier
// before indexing, bar's position will be [0] which will be inaccurate and will make the backend
// enable to find the corresponding node when we request its properties.
// before indexing, bar's position will be [0] which will be inaccurate and will make the
// backend enable to find the corresponding node when we request its properties.
if (!showCommentNodes) {
indexedForest = filterCommentNodes(indexedForest);
}
@@ -141,7 +138,8 @@ export class ComponentDataSource extends DataSource<FlatNode> {
expandedNodes[item.id] = this._treeControl.isExpanded(item);
});
const { newItems, movedItems, removedItems } = diff<FlatNode>(this._differ, this.data, flattenedCollection);
const {newItems, movedItems, removedItems} =
diff<FlatNode>(this._differ, this.data, flattenedCollection);
this._treeControl.dataNodes = this.data;
this._flattenedData.next(this.data);
@@ -154,17 +152,18 @@ export class ComponentDataSource extends DataSource<FlatNode> {
newItems.forEach((i) => (i.newItem = true));
removedItems.forEach((i) => this._nodeToFlat.delete(i.original));
return { newItems, movedItems, removedItems };
return {newItems, movedItems, removedItems};
}
connect(collectionViewer: CollectionViewer): Observable<FlatNode[]> {
const changes = [collectionViewer.viewChange, this._treeControl.expansionModel.changed, this._flattenedData];
return merge(...changes).pipe(
map(() => {
this._expandedData.next(this._treeFlattener.expandFlattenedNodes(this.data, this._treeControl) as FlatNode[]);
return this._expandedData.value;
})
);
const changes = [
collectionViewer.viewChange, this._treeControl.expansionModel.changed, this._flattenedData
];
return merge(...changes).pipe(map(() => {
this._expandedData.next(
this._treeFlattener.expandFlattenedNodes(this.data, this._treeControl) as FlatNode[]);
return this._expandedData.value;
}));
}
disconnect(): void {}
@@ -1,5 +1,6 @@
import { FlatNode } from './component-data-source';
import { FlatTreeControl } from '@angular/cdk/tree';
import {FlatTreeControl} from '@angular/cdk/tree';
import {FlatNode} from './component-data-source';
export const isChildOf = (childPosition: number[], parentPosition: number[]) => {
if (childPosition.length <= parentPosition.length) {
@@ -13,12 +14,13 @@ export const isChildOf = (childPosition: number[], parentPosition: number[]) =>
return true;
};
export const parentCollapsed = (nodeIdx: number, all: FlatNode[], treeControl: FlatTreeControl<FlatNode>) => {
const node = all[nodeIdx];
for (let i = nodeIdx - 1; i >= 0; i--) {
if (isChildOf(node.position, all[i].position) && !treeControl.isExpanded(all[i])) {
return true;
}
}
return false;
};
export const parentCollapsed =
(nodeIdx: number, all: FlatNode[], treeControl: FlatTreeControl<FlatNode>) => {
const node = all[nodeIdx];
for (let i = nodeIdx - 1; i >= 0; i--) {
if (isChildOf(node.position, all[i].position) && !treeControl.isExpanded(all[i])) {
return true;
}
}
return false;
};
@@ -1,23 +1,14 @@
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
EventEmitter,
HostListener,
Input,
OnInit,
Output,
ViewChild,
OnDestroy,
} from '@angular/core';
import { DevToolsNode, ElementPosition, Events, MessageBus } from 'protocol';
import { FlatTreeControl } from '@angular/cdk/tree';
import { ComponentDataSource, FlatNode } from './component-data-source';
import { isChildOf, parentCollapsed } from './directive-forest-utils';
import { IndexedNode } from './index-forest';
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
import { TabUpdate } from '../../tab-update/index';
import { Subscription } from 'rxjs';
import {CdkVirtualScrollViewport} from '@angular/cdk/scrolling';
import {FlatTreeControl} from '@angular/cdk/tree';
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, HostListener, Input, OnDestroy, OnInit, Output, ViewChild,} from '@angular/core';
import {DevToolsNode, ElementPosition, Events, MessageBus} from 'protocol';
import {Subscription} from 'rxjs';
import {TabUpdate} from '../../tab-update/index';
import {ComponentDataSource, FlatNode} from './component-data-source';
import {isChildOf, parentCollapsed} from './directive-forest-utils';
import {IndexedNode} from './index-forest';
@Component({
selector: 'ng-directive-forest',
@@ -26,23 +17,26 @@ import { Subscription } from 'rxjs';
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DirectiveForestComponent implements OnInit, OnDestroy {
@Input() set forest(forest: DevToolsNode[]) {
@Input()
set forest(forest: DevToolsNode[]) {
this._latestForest = forest;
const result = this._updateForest(forest);
const changed = result.movedItems.length || result.newItems.length || result.removedItems.length;
const changed =
result.movedItems.length || result.newItems.length || result.removedItems.length;
if (this.currentSelectedElement && changed) {
this._reselectNodeOnUpdate();
}
}
@Input() currentSelectedElement: IndexedNode;
@Input() set showCommentNodes(show: boolean) {
@Input()
set showCommentNodes(show: boolean) {
this._showCommentNodes = show;
this.forest = this._latestForest;
}
@Output() selectNode = new EventEmitter<IndexedNode | null>();
@Output() selectNode = new EventEmitter<IndexedNode|null>();
@Output() selectDomElement = new EventEmitter<IndexedNode>();
@Output() setParents = new EventEmitter<FlatNode[] | null>();
@Output() setParents = new EventEmitter<FlatNode[]|null>();
@Output() highlightComponent = new EventEmitter<ElementPosition>();
@Output() removeComponentHighlight = new EventEmitter<void>();
@Output() toggleInspector = new EventEmitter<void>();
@@ -52,33 +46,29 @@ export class DirectiveForestComponent implements OnInit, OnDestroy {
filterRegex = new RegExp('.^');
currentlyMatchedIndex = -1;
selectedNode: FlatNode | null = null;
selectedNode: FlatNode|null = null;
parents: FlatNode[];
private _highlightIDinTreeFromElement: number | null = null;
private _highlightIDinTreeFromElement: number|null = null;
private _tabUpdateSubscription: Subscription;
private _showCommentNodes = false;
private _latestForest: DevToolsNode[];
set highlightIDinTreeFromElement(id: number | null) {
set highlightIDinTreeFromElement(id: number|null) {
this._highlightIDinTreeFromElement = id;
this._cdr.markForCheck();
}
readonly treeControl = new FlatTreeControl<FlatNode>(
(node) => node.level,
(node) => node.expandable
);
readonly treeControl =
new FlatTreeControl<FlatNode>((node) => node.level, (node) => node.expandable);
readonly dataSource = new ComponentDataSource(this.treeControl);
readonly itemHeight = 18;
private _initialized = false;
constructor(
private _tabUpdate: TabUpdate,
private _messageBus: MessageBus<Events>,
private _cdr: ChangeDetectorRef
) {}
private _tabUpdate: TabUpdate, private _messageBus: MessageBus<Events>,
private _cdr: ChangeDetectorRef) {}
ngOnInit(): void {
this.subscribeToInspectorEvents();
@@ -122,7 +112,8 @@ export class DirectiveForestComponent implements OnInit, OnDestroy {
}
handleSelect(node: FlatNode): void {
this.currentlyMatchedIndex = this.dataSource.data.findIndex((matchedNode) => matchedNode.id === node.id);
this.currentlyMatchedIndex =
this.dataSource.data.findIndex((matchedNode) => matchedNode.id === node.id);
this.selectAndEnsureVisible(node);
}
@@ -146,9 +137,9 @@ export class DirectiveForestComponent implements OnInit, OnDestroy {
}
const itemTop = idx * this.itemHeight;
if (itemTop < top) {
scrollParent.scrollTo({ top: itemTop });
scrollParent.scrollTo({top: itemTop});
} else if (bottom < itemTop + this.itemHeight) {
scrollParent.scrollTo({ top: itemTop - parentHeight + this.itemHeight });
scrollParent.scrollTo({top: itemTop - parentHeight + this.itemHeight});
}
}
@@ -166,7 +157,8 @@ export class DirectiveForestComponent implements OnInit, OnDestroy {
}
private _reselectNodeOnUpdate(): void {
const nodeThatStillExists = this.dataSource.getFlatNodeFromIndexedNode(this.currentSelectedElement);
const nodeThatStillExists =
this.dataSource.getFlatNodeFromIndexedNode(this.currentSelectedElement);
if (nodeThatStillExists) {
this.select(nodeThatStillExists);
} else {
@@ -174,11 +166,8 @@ export class DirectiveForestComponent implements OnInit, OnDestroy {
}
}
private _updateForest(forest: DevToolsNode[]): {
newItems: FlatNode[];
movedItems: FlatNode[];
removedItems: FlatNode[];
} {
private _updateForest(forest: DevToolsNode[]):
{newItems: FlatNode[]; movedItems: FlatNode[]; removedItems: FlatNode[];} {
const result = this.dataSource.update(forest, this._showCommentNodes);
if (!this._initialized && forest && forest.length) {
this.treeControl.expandAll();
@@ -196,7 +185,8 @@ export class DirectiveForestComponent implements OnInit, OnDestroy {
this.parents = [];
for (let i = 1; i <= position.length; i++) {
const current = position.slice(0, i);
const selectedNode = this.dataSource.data.find((item) => item.position.toString() === current.toString());
const selectedNode =
this.dataSource.data.find((item) => item.position.toString() === current.toString());
// We might not be able to find the parent if the user has hidden the comment nodes.
if (selectedNode) {
@@ -290,7 +280,8 @@ export class DirectiveForestComponent implements OnInit, OnDestroy {
}
isMatched(node: FlatNode): boolean {
return this.filterRegex.test(node.name.toLowerCase()) || this.filterRegex.test(node.directives.toLowerCase());
return this.filterRegex.test(node.name.toLowerCase()) ||
this.filterRegex.test(node.directives.toLowerCase());
}
handleFilter(filterText: string): void {
@@ -334,8 +325,8 @@ export class DirectiveForestComponent implements OnInit, OnDestroy {
prevMatched(): void {
const indexesOfMatchedNodes = this._findMatchedNodes();
this.currentlyMatchedIndex =
(this.currentlyMatchedIndex - 1 + indexesOfMatchedNodes.length) % indexesOfMatchedNodes.length;
this.currentlyMatchedIndex = (this.currentlyMatchedIndex - 1 + indexesOfMatchedNodes.length) %
indexesOfMatchedNodes.length;
const indexToSelect = indexesOfMatchedNodes[this.currentlyMatchedIndex];
const nodeToSelect = this.dataSource.data[indexToSelect];
if (indexToSelect !== undefined) {
@@ -362,10 +353,11 @@ export class DirectiveForestComponent implements OnInit, OnDestroy {
}
isHighlighted(node: FlatNode): boolean {
return !!this._highlightIDinTreeFromElement && this._highlightIDinTreeFromElement === node.original.component?.id;
return !!this._highlightIDinTreeFromElement &&
this._highlightIDinTreeFromElement === node.original.component?.id;
}
isElement(node: FlatNode): boolean | null {
isElement(node: FlatNode): boolean|null {
return node.original.component && node.original.component.isElement;
}
}
@@ -1,12 +1,14 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatIconModule } from '@angular/material/icon';
import { BreadcrumbsModule } from './breadcrumbs/breadcrumbs.module';
import { FilterModule } from './filter/filter.module';
import { ScrollingModule } from '@angular/cdk/scrolling';
import { DirectiveForestComponent } from './directive-forest.component';
import {ScrollingModule} from '@angular/cdk/scrolling';
import {CommonModule} from '@angular/common';
import {NgModule} from '@angular/core';
import {MatButtonModule} from '@angular/material/button';
import {MatCardModule} from '@angular/material/card';
import {MatIconModule} from '@angular/material/icon';
import {BreadcrumbsModule} from './breadcrumbs/breadcrumbs.module';
import {DirectiveForestComponent} from './directive-forest.component';
import {FilterModule} from './filter/filter.module';
@NgModule({
declarations: [DirectiveForestComponent],
imports: [
@@ -20,4 +22,5 @@ import { DirectiveForestComponent } from './directive-forest.component';
],
exports: [DirectiveForestComponent, BreadcrumbsModule],
})
export class DirectiveForestModule {}
export class DirectiveForestModule {
}
@@ -1,4 +1,4 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
import {Component, EventEmitter, Input, Output} from '@angular/core';
@Component({
selector: 'ng-filter',
@@ -1,13 +1,15 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FilterComponent } from './filter.component';
import { MatCardModule } from '@angular/material/card';
import { MatIconModule } from '@angular/material/icon';
import { MatButtonModule } from '@angular/material/button';
import {CommonModule} from '@angular/common';
import {NgModule} from '@angular/core';
import {MatButtonModule} from '@angular/material/button';
import {MatCardModule} from '@angular/material/card';
import {MatIconModule} from '@angular/material/icon';
import {FilterComponent} from './filter.component';
@NgModule({
declarations: [FilterComponent],
imports: [CommonModule, MatCardModule, MatIconModule, MatButtonModule],
exports: [FilterComponent],
})
export class FilterModule {}
export class FilterModule {
}
@@ -1,4 +1,4 @@
import { indexForest } from './';
import {indexForest} from './';
describe('indexForest', () => {
it('should work with an empty forest', () => {
@@ -6,83 +6,10 @@ describe('indexForest', () => {
});
it('should index a forest', () => {
expect(
indexForest([
{
element: 'Parent1',
directives: [],
component: {
isElement: false,
name: 'Cmp1',
id: 1,
},
children: [
{
element: 'Child1_1',
directives: [
{
name: 'Dir1',
id: 1,
},
{
name: 'Dir2',
id: 1,
},
],
component: null,
children: [],
},
{
element: 'Child1_2',
directives: [],
component: {
isElement: false,
name: 'Cmp2',
id: 1,
},
children: [],
},
],
},
{
element: 'Parent2',
directives: [],
component: null,
children: [
{
element: 'Child2_1',
directives: [
{
name: 'Dir3',
id: 1,
},
],
component: null,
children: [],
},
{
element: 'Child2_2',
directives: [
{
name: 'Dir4',
id: 1,
},
{
name: 'Dir5',
id: 1,
},
],
component: null,
children: [],
},
],
},
])
).toEqual([
expect(indexForest([
{
element: 'Parent1',
directives: [],
position: [0],
component: {
isElement: false,
name: 'Cmp1',
@@ -91,7 +18,6 @@ describe('indexForest', () => {
children: [
{
element: 'Child1_1',
position: [0, 0],
directives: [
{
name: 'Dir1',
@@ -108,7 +34,6 @@ describe('indexForest', () => {
{
element: 'Child1_2',
directives: [],
position: [0, 1],
component: {
isElement: false,
name: 'Cmp2',
@@ -122,11 +47,9 @@ describe('indexForest', () => {
element: 'Parent2',
directives: [],
component: null,
position: [1],
children: [
{
element: 'Child2_1',
position: [1, 0],
directives: [
{
name: 'Dir3',
@@ -138,7 +61,6 @@ describe('indexForest', () => {
},
{
element: 'Child2_2',
position: [1, 1],
directives: [
{
name: 'Dir4',
@@ -154,6 +76,83 @@ describe('indexForest', () => {
},
],
},
]);
]))
.toEqual([
{
element: 'Parent1',
directives: [],
position: [0],
component: {
isElement: false,
name: 'Cmp1',
id: 1,
},
children: [
{
element: 'Child1_1',
position: [0, 0],
directives: [
{
name: 'Dir1',
id: 1,
},
{
name: 'Dir2',
id: 1,
},
],
component: null,
children: [],
},
{
element: 'Child1_2',
directives: [],
position: [0, 1],
component: {
isElement: false,
name: 'Cmp2',
id: 1,
},
children: [],
},
],
},
{
element: 'Parent2',
directives: [],
component: null,
position: [1],
children: [
{
element: 'Child2_1',
position: [1, 0],
directives: [
{
name: 'Dir3',
id: 1,
},
],
component: null,
children: [],
},
{
element: 'Child2_2',
position: [1, 1],
directives: [
{
name: 'Dir4',
id: 1,
},
{
name: 'Dir5',
id: 1,
},
],
component: null,
children: [],
},
],
},
]);
});
});
@@ -1,19 +1,20 @@
import { DevToolsNode, ElementPosition } from 'protocol';
import {DevToolsNode, ElementPosition} from 'protocol';
export interface IndexedNode extends DevToolsNode {
position: ElementPosition;
children: IndexedNode[];
}
const indexTree = (node: DevToolsNode, idx: number, parentPosition: ElementPosition = []): IndexedNode => {
const position = parentPosition.concat([idx]);
return {
position,
element: node.element,
component: node.component,
directives: node.directives.map((d, i) => ({ name: d.name, id: d.id })),
children: node.children.map((n, i) => indexTree(n, i, position)),
} as IndexedNode;
};
const indexTree =
(node: DevToolsNode, idx: number, parentPosition: ElementPosition = []): IndexedNode => {
const position = parentPosition.concat([idx]);
return {
position,
element: node.element,
component: node.component,
directives: node.directives.map((d, i) => ({name: d.name, id: d.id})),
children: node.children.map((n, i) => indexTree(n, i, position)),
} as IndexedNode;
};
export const indexForest = (forest: DevToolsNode[]) => forest.map((n, i) => indexTree(n, i));
@@ -1,5 +1,6 @@
import { PropType } from 'protocol';
import { arrayifyProps } from './arrayify-props';
import {PropType} from 'protocol';
import {arrayifyProps} from './arrayify-props';
describe('arrayify', () => {
it('should return an array from prop object', () => {
@@ -1,19 +1,16 @@
import { Descriptor } from 'protocol';
import { Property } from './element-property-resolver';
import {Descriptor} from 'protocol';
export const arrayifyProps = (
props: { [prop: string]: Descriptor } | Descriptor[],
parent: Property | null = null
): Property[] =>
Object.keys(props)
.map((name) => ({ name, descriptor: props[name], parent }))
.sort((a, b) => {
const parsedA = parseInt(a.name, 10);
const parsedB = parseInt(b.name, 10);
import {Property} from './element-property-resolver';
if (isNaN(parsedA) || isNaN(parsedB)) {
return a.name > b.name ? 1 : -1;
}
export const arrayifyProps =
(props: {[prop: string]: Descriptor}|Descriptor[], parent: Property|null = null): Property[] =>
Object.keys(props).map((name) => ({name, descriptor: props[name], parent})).sort((a, b) => {
const parsedA = parseInt(a.name, 10);
const parsedB = parseInt(b.name, 10);
return parsedA - parsedB;
});
if (isNaN(parsedA) || isNaN(parsedB)) {
return a.name > b.name ? 1 : -1;
}
return parsedA - parsedB;
});
@@ -1,5 +1,6 @@
import { Properties, PropType } from 'protocol';
import { DirectivePropertyResolver } from './directive-property-resolver';
import {Properties, PropType} from 'protocol';
import {DirectivePropertyResolver} from './directive-property-resolver';
const properties: Properties = {
props: {
@@ -1,52 +1,49 @@
import { Descriptor, MessageBus, Events, Properties, DirectivePosition, NestedProp } from 'protocol';
import { PropertyDataSource } from './property-data-source';
import { FlatTreeControl } from '@angular/cdk/tree';
import { getExpandedDirectiveProperties } from './property-expanded-directive-properties';
import { Property, FlatNode } from './element-property-resolver';
import { ViewEncapsulation } from '@angular/core';
import { getTreeFlattener } from './flatten';
import {FlatTreeControl} from '@angular/cdk/tree';
import {ViewEncapsulation} from '@angular/core';
import {Descriptor, DirectivePosition, Events, MessageBus, NestedProp, Properties} from 'protocol';
import {FlatNode, Property} from './element-property-resolver';
import {getTreeFlattener} from './flatten';
import {PropertyDataSource} from './property-data-source';
import {getExpandedDirectiveProperties} from './property-expanded-directive-properties';
export interface DirectiveTreeData {
dataSource: PropertyDataSource;
treeControl: FlatTreeControl<FlatNode>;
}
const getDirectiveControls = (
dataSource: PropertyDataSource
): { dataSource: PropertyDataSource; treeControl: FlatTreeControl<FlatNode> } => {
const treeControl = dataSource.treeControl;
return {
dataSource,
treeControl,
};
};
const getDirectiveControls = (dataSource: PropertyDataSource):
{dataSource: PropertyDataSource; treeControl: FlatTreeControl<FlatNode>} => {
const treeControl = dataSource.treeControl;
return {
dataSource,
treeControl,
};
};
export const constructPathOfKeysToPropertyValue = (nodePropToGetKeysFor: Property, keys: string[] = []): string[] => {
keys.unshift(nodePropToGetKeysFor.name);
const parentNodeProp = nodePropToGetKeysFor.parent;
if (parentNodeProp) {
constructPathOfKeysToPropertyValue(parentNodeProp, keys);
}
return keys;
};
export const constructPathOfKeysToPropertyValue =
(nodePropToGetKeysFor: Property, keys: string[] = []): string[] => {
keys.unshift(nodePropToGetKeysFor.name);
const parentNodeProp = nodePropToGetKeysFor.parent;
if (parentNodeProp) {
constructPathOfKeysToPropertyValue(parentNodeProp, keys);
}
return keys;
};
export class DirectivePropertyResolver {
private _treeFlattener = getTreeFlattener();
private _treeControl = new FlatTreeControl<FlatNode>(
(node) => node.level,
(node) => node.expandable
);
private _treeControl =
new FlatTreeControl<FlatNode>((node) => node.level, (node) => node.expandable);
private _inputsDataSource: PropertyDataSource;
private _outputsDataSource: PropertyDataSource;
private _stateDataSource: PropertyDataSource;
constructor(
private _messageBus: MessageBus<Events>,
private _props: Properties,
private _directivePosition: DirectivePosition
) {
private _messageBus: MessageBus<Events>, private _props: Properties,
private _directivePosition: DirectivePosition) {
this._initDataSources();
}
@@ -62,7 +59,7 @@ export class DirectivePropertyResolver {
return getDirectiveControls(this._stateDataSource);
}
get directiveProperties(): { [name: string]: Descriptor } {
get directiveProperties(): {[name: string]: Descriptor} {
return this._props.props;
}
@@ -70,11 +67,11 @@ export class DirectivePropertyResolver {
return this._directivePosition;
}
get directiveViewEncapsulation(): ViewEncapsulation | undefined {
get directiveViewEncapsulation(): ViewEncapsulation|undefined {
return this._props.metadata?.encapsulation;
}
get directiveHasOnPushStrategy(): boolean | undefined {
get directiveHasOnPushStrategy(): boolean|undefined {
return this._props.metadata?.onPush;
}
@@ -88,7 +85,7 @@ export class DirectivePropertyResolver {
updateProperties(newProps: Properties): void {
this._props = newProps;
const { inputProps, outputProps, stateProps } = this._classifyProperties();
const {inputProps, outputProps, stateProps} = this._classifyProperties();
this._inputsDataSource.update(inputProps);
this._outputsDataSource.update(outputProps);
@@ -98,32 +95,26 @@ export class DirectivePropertyResolver {
updateValue(node: FlatNode, newValue: any): void {
const directiveId = this._directivePosition;
const keyPath = constructPathOfKeysToPropertyValue(node.prop);
this._messageBus.emit('updateState', [{ directiveId, keyPath, newValue }]);
this._messageBus.emit('updateState', [{directiveId, keyPath, newValue}]);
node.prop.descriptor.value = newValue;
}
private _initDataSources(): void {
const { inputProps, outputProps, stateProps } = this._classifyProperties();
const {inputProps, outputProps, stateProps} = this._classifyProperties();
this._inputsDataSource = this._createDataSourceFromProps(inputProps);
this._outputsDataSource = this._createDataSourceFromProps(outputProps);
this._stateDataSource = this._createDataSourceFromProps(stateProps);
}
private _createDataSourceFromProps(props: { [name: string]: Descriptor }): PropertyDataSource {
private _createDataSourceFromProps(props: {[name: string]: Descriptor}): PropertyDataSource {
return new PropertyDataSource(
props,
this._treeFlattener,
this._treeControl,
this._directivePosition,
this._messageBus
);
props, this._treeFlattener, this._treeControl, this._directivePosition, this._messageBus);
}
private _classifyProperties(): {
inputProps: { [name: string]: Descriptor };
outputProps: { [name: string]: Descriptor };
stateProps: { [name: string]: Descriptor };
inputProps: {[name: string]: Descriptor}; outputProps: {[name: string]: Descriptor};
stateProps: {[name: string]: Descriptor};
} {
const inputLabels: Set<string> = new Set(Object.values(this._props.metadata?.inputs || {}));
const outputLabels: Set<string> = new Set(Object.values(this._props.metadata?.outputs || {}));
@@ -131,10 +122,12 @@ export class DirectivePropertyResolver {
const inputProps = {};
const outputProps = {};
const stateProps = {};
let propPointer: { [name: string]: Descriptor };
let propPointer: {[name: string]: Descriptor};
Object.keys(this.directiveProperties).forEach((propName) => {
propPointer = inputLabels.has(propName) ? inputProps : outputLabels.has(propName) ? outputProps : stateProps;
propPointer = inputLabels.has(propName) ? inputProps :
outputLabels.has(propName) ? outputProps :
stateProps;
propPointer[propName] = this.directiveProperties[propName];
});
@@ -1,5 +1,6 @@
import { ElementPropertyResolver } from './element-property-resolver';
import { Properties, PropType } from 'protocol';
import {Properties, PropType} from 'protocol';
import {ElementPropertyResolver} from './element-property-resolver';
const mockIndexedNode = {
component: {
@@ -1,14 +1,9 @@
import { Injectable } from '@angular/core';
import {
DirectivesProperties,
ComponentExplorerViewProperties,
Descriptor,
MessageBus,
Events,
DirectivePosition,
} from 'protocol';
import { IndexedNode } from '../directive-forest/index-forest';
import { DirectivePropertyResolver } from './directive-property-resolver';
import {Injectable} from '@angular/core';
import {ComponentExplorerViewProperties, Descriptor, DirectivePosition, DirectivesProperties, Events, MessageBus,} from 'protocol';
import {IndexedNode} from '../directive-forest/index-forest';
import {DirectivePropertyResolver} from './directive-property-resolver';
export interface FlatNode {
expandable: boolean;
@@ -19,7 +14,7 @@ export interface FlatNode {
export interface Property {
name: string;
descriptor: Descriptor;
parent: Property | null;
parent: Property|null;
}
@Injectable()
@@ -49,9 +44,7 @@ export class ElementPropertyResolver {
position.directive = indexedNode.directives.findIndex((d) => d.name === key);
}
this._directivePropertiesController.set(
key,
new DirectivePropertyResolver(this._messageBus, data[key], position)
);
key, new DirectivePropertyResolver(this._messageBus, data[key], position));
});
}
@@ -78,7 +71,7 @@ export class ElementPropertyResolver {
return result;
}
getDirectiveController(directive: string): DirectivePropertyResolver | undefined {
getDirectiveController(directive: string): DirectivePropertyResolver|undefined {
return this._directivePropertiesController.get(directive);
}
}
@@ -1,22 +1,18 @@
import { MatTreeFlattener } from '@angular/material/tree';
import { Descriptor, PropType } from 'protocol';
import { Observable } from 'rxjs';
import { arrayifyProps } from './arrayify-props';
import { FlatNode, Property } from './element-property-resolver';
import {MatTreeFlattener} from '@angular/material/tree';
import {Descriptor, PropType} from 'protocol';
import {Observable} from 'rxjs';
import {arrayifyProps} from './arrayify-props';
import {FlatNode, Property} from './element-property-resolver';
export const getTreeFlattener = () =>
new MatTreeFlattener(
(node: Property, level: number): FlatNode => {
new MatTreeFlattener((node: Property, level: number): FlatNode => {
return {
expandable: expandable(node.descriptor),
prop: node,
level,
};
},
(node) => node.level,
(node) => node.expandable,
(node) => getChildren(node)
);
}, (node) => node.level, (node) => node.expandable, (node) => getChildren(node));
export const expandable = (prop: Descriptor) => {
if (!prop) {
@@ -28,12 +24,10 @@ export const expandable = (prop: Descriptor) => {
return !(prop.type !== PropType.Object && prop.type !== PropType.Array);
};
const getChildren = (prop: Property): Property[] | undefined => {
const getChildren = (prop: Property): Property[]|undefined => {
const descriptor = prop.descriptor;
if (
(descriptor.type === PropType.Object || descriptor.type === PropType.Array) &&
!(descriptor.value instanceof Observable)
) {
if ((descriptor.type === PropType.Object || descriptor.type === PropType.Array) &&
!(descriptor.value instanceof Observable)) {
return arrayifyProps(descriptor.value || {}, prop);
} else {
console.error('Unexpected data type', descriptor, 'in property', prop);
@@ -1,31 +1,26 @@
import { FlatTreeControl } from '@angular/cdk/tree';
import { PropType } from 'protocol';
import { FlatNode } from './element-property-resolver';
import { getTreeFlattener } from './flatten';
import { PropertyDataSource } from './property-data-source';
import {FlatTreeControl} from '@angular/cdk/tree';
import {PropType} from 'protocol';
const flatTreeControl = new FlatTreeControl<FlatNode>(
(node) => node.level,
(node) => node.expandable
);
import {FlatNode} from './element-property-resolver';
import {getTreeFlattener} from './flatten';
import {PropertyDataSource} from './property-data-source';
const flatTreeControl =
new FlatTreeControl<FlatNode>((node) => node.level, (node) => node.expandable);
describe('PropertyDataSource', () => {
it('should detect changes in the collection', () => {
const source = new PropertyDataSource(
{
foo: {
editable: true,
expandable: false,
preview: '42',
type: PropType.Number,
value: 42,
{
foo: {
editable: true,
expandable: false,
preview: '42',
type: PropType.Number,
value: 42,
},
},
},
getTreeFlattener(),
flatTreeControl,
{ element: [1, 2, 3] },
null as any
);
getTreeFlattener(), flatTreeControl, {element: [1, 2, 3]}, null as any);
source.update({
foo: {
@@ -1,16 +1,18 @@
import { Descriptor, DirectivePosition, Events, MessageBus, Properties } from 'protocol';
import { CollectionViewer, DataSource, SelectionChange } from '@angular/cdk/collections';
import { BehaviorSubject, merge, Observable, Subscription } from 'rxjs';
import { MatTreeFlattener } from '@angular/material/tree';
import { FlatTreeControl } from '@angular/cdk/tree';
import { map } from 'rxjs/operators';
import { DefaultIterableDiffer, TrackByFunction } from '@angular/core';
import { diff } from '../../diffing';
import { FlatNode, Property } from './element-property-resolver';
import { arrayifyProps } from './arrayify-props';
import {CollectionViewer, DataSource, SelectionChange} from '@angular/cdk/collections';
import {FlatTreeControl} from '@angular/cdk/tree';
import {DefaultIterableDiffer, TrackByFunction} from '@angular/core';
import {MatTreeFlattener} from '@angular/material/tree';
import {Descriptor, DirectivePosition, Events, MessageBus, Properties} from 'protocol';
import {BehaviorSubject, merge, Observable, Subscription} from 'rxjs';
import {map} from 'rxjs/operators';
import {diff} from '../../diffing';
import {arrayifyProps} from './arrayify-props';
import {FlatNode, Property} from './element-property-resolver';
const trackBy: TrackByFunction<FlatNode> = (_: number, item: FlatNode) =>
`#${item.prop.name}#${item.prop.descriptor.preview}#${item.level}`;
`#${item.prop.name}#${item.prop.descriptor.preview}#${item.level}`;
export class PropertyDataSource extends DataSource<FlatNode> {
private _data = new BehaviorSubject<FlatNode[]>([]);
@@ -19,12 +21,10 @@ export class PropertyDataSource extends DataSource<FlatNode> {
private _differ = new DefaultIterableDiffer<FlatNode>(trackBy);
constructor(
props: { [prop: string]: Descriptor },
private _treeFlattener: MatTreeFlattener<Property, FlatNode>,
private _treeControl: FlatTreeControl<FlatNode>,
private _entityPosition: DirectivePosition,
private _messageBus: MessageBus<Events>
) {
props: {[prop: string]: Descriptor},
private _treeFlattener: MatTreeFlattener<Property, FlatNode>,
private _treeControl: FlatTreeControl<FlatNode>, private _entityPosition: DirectivePosition,
private _messageBus: MessageBus<Events>) {
super();
this._data.next(this._treeFlattener.flattenNodes(arrayifyProps(props)));
}
@@ -37,7 +37,7 @@ export class PropertyDataSource extends DataSource<FlatNode> {
return this._treeControl;
}
update(props: { [prop: string]: Descriptor }): void {
update(props: {[prop: string]: Descriptor}): void {
const newData = this._treeFlattener.flattenNodes(arrayifyProps(props));
diff(this._differ, this.data, newData);
this._data.next(this.data);
@@ -58,14 +58,14 @@ export class PropertyDataSource extends DataSource<FlatNode> {
});
this._subscriptions.push(s);
const changes = [collectionViewer.viewChange, this._treeControl.expansionModel.changed, this._data];
const changes =
[collectionViewer.viewChange, this._treeControl.expansionModel.changed, this._data];
return merge(...changes).pipe(
map(() => {
this._expandedData.next(this._treeFlattener.expandFlattenedNodes(this.data, this._treeControl));
return this._expandedData.value;
})
);
return merge(...changes).pipe(map(() => {
this._expandedData.next(
this._treeFlattener.expandFlattenedNodes(this.data, this._treeControl));
return this._expandedData.value;
}));
}
disconnect(): void {
@@ -95,14 +95,15 @@ export class PropertyDataSource extends DataSource<FlatNode> {
this._messageBus.emit('getNestedProperties', [this._entityPosition, parentPath]);
this._messageBus.once('nestedProperties', (position: DirectivePosition, data: Properties, _path: string[]) => {
node.prop.descriptor.value = data.props;
this._treeControl.expand(node);
const props = arrayifyProps(data.props, node.prop);
const flatNodes = this._treeFlattener.flattenNodes(props);
flatNodes.forEach((f) => (f.level += node.level + 1));
this.data.splice(index + 1, 0, ...flatNodes);
this._data.next(this.data);
});
this._messageBus.once(
'nestedProperties', (position: DirectivePosition, data: Properties, _path: string[]) => {
node.prop.descriptor.value = data.props;
this._treeControl.expand(node);
const props = arrayifyProps(data.props, node.prop);
const flatNodes = this._treeFlattener.flattenNodes(props);
flatNodes.forEach((f) => (f.level += node.level + 1));
this.data.splice(index + 1, 0, ...flatNodes);
this._data.next(this.data);
});
}
}
@@ -1,5 +1,6 @@
import { Descriptor, NestedProp, PropType } from 'protocol';
import { FlatNode } from './element-property-resolver';
import {Descriptor, NestedProp, PropType} from 'protocol';
import {FlatNode} from './element-property-resolver';
export const getExpandedDirectiveProperties = (data: FlatNode[]): NestedProp[] => {
const getChildren = (prop: Descriptor) => {
@@ -14,7 +15,7 @@ export const getExpandedDirectiveProperties = (data: FlatNode[]): NestedProp[] =
return [];
};
const getExpandedProperties = (props: { [name: string]: Descriptor }) => {
const getExpandedProperties = (props: {[name: string]: Descriptor}) => {
return Object.keys(props).map(name => {
return {
name,
@@ -23,7 +24,7 @@ export const getExpandedDirectiveProperties = (data: FlatNode[]): NestedProp[] =
});
};
const parents: { [name: string]: Descriptor } = {};
const parents: {[name: string]: Descriptor} = {};
for (const node of data) {
let prop = node.prop;
@@ -1,7 +1,8 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { DirectivePropertyResolver } from '../property-resolver/directive-property-resolver';
import { ElementPropertyResolver } from '../property-resolver/element-property-resolver';
import { ComponentType } from 'protocol';
import {ChangeDetectionStrategy, Component, Input} from '@angular/core';
import {ComponentType} from 'protocol';
import {DirectivePropertyResolver} from '../property-resolver/directive-property-resolver';
import {ElementPropertyResolver} from '../property-resolver/element-property-resolver';
@Component({
selector: 'ng-component-metadata',
@@ -16,21 +17,21 @@ export class ComponentMetadataComponent {
viewEncapsulationModes = ['Emulated', 'Native', 'None', 'ShadowDom'];
get controller(): DirectivePropertyResolver | undefined {
get controller(): DirectivePropertyResolver|undefined {
if (!this.currentSelectedComponent) {
return;
}
return this._nestedProps.getDirectiveController(this.currentSelectedComponent.name);
}
get viewEncapsulation(): string | undefined {
get viewEncapsulation(): string|undefined {
const encapsulationIndex = this?.controller?.directiveViewEncapsulation;
if (encapsulationIndex !== undefined) {
return this.viewEncapsulationModes[encapsulationIndex];
}
}
get changeDetectionStrategy(): string | undefined {
get changeDetectionStrategy(): string|undefined {
const onPush = this?.controller?.directiveHasOnPushStrategy;
return onPush ? 'OnPush' : onPush !== undefined ? 'Default' : undefined;
}
@@ -1,5 +1,6 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { IndexedNode } from '../directive-forest/index-forest';
import {ChangeDetectionStrategy, Component, EventEmitter, Input, Output} from '@angular/core';
import {IndexedNode} from '../directive-forest/index-forest';
@Component({
templateUrl: './property-tab-header.component.html',
@@ -9,7 +10,7 @@ import { IndexedNode } from '../directive-forest/index-forest';
})
export class PropertyTabHeaderComponent {
@Input() currentSelectedElement: IndexedNode;
@Input() currentDirectives: string[] | undefined;
@Input() currentDirectives: string[]|undefined;
@Output() viewSource = new EventEmitter<void>();
handleViewSource(event: MouseEvent): void {
@@ -1,7 +1,8 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { IndexedNode } from '../directive-forest/index-forest';
import { FlatNode } from '../property-resolver/element-property-resolver';
import { DirectivePosition } from 'protocol';
import {Component, EventEmitter, Input, Output} from '@angular/core';
import {DirectivePosition} from 'protocol';
import {IndexedNode} from '../directive-forest/index-forest';
import {FlatNode} from '../property-resolver/element-property-resolver';
@Component({
templateUrl: './property-tab.component.html',
@@ -10,5 +11,5 @@ import { DirectivePosition } from 'protocol';
export class PropertyTabComponent {
@Input() currentSelectedElement: IndexedNode;
@Output() viewSource = new EventEmitter<void>();
@Output() inspect = new EventEmitter<{ node: FlatNode; directivePosition: DirectivePosition }>();
@Output() inspect = new EventEmitter<{node: FlatNode; directivePosition: DirectivePosition}>();
}
@@ -1,17 +1,22 @@
import { NgModule } from '@angular/core';
import { PropertyTabComponent } from './property-tab.component';
import { CommonModule } from '@angular/common';
import { MatButtonModule } from '@angular/material/button';
import { MatExpansionModule } from '@angular/material/expansion';
import { MatIconModule } from '@angular/material/icon';
import { MatTooltipModule } from '@angular/material/tooltip';
import { PropertyViewModule } from './property-view/property-view.module';
import { ComponentMetadataComponent } from './component-metadata.component';
import { PropertyTabHeaderComponent } from './property-tab-header.component';
import {CommonModule} from '@angular/common';
import {NgModule} from '@angular/core';
import {MatButtonModule} from '@angular/material/button';
import {MatExpansionModule} from '@angular/material/expansion';
import {MatIconModule} from '@angular/material/icon';
import {MatTooltipModule} from '@angular/material/tooltip';
import {ComponentMetadataComponent} from './component-metadata.component';
import {PropertyTabHeaderComponent} from './property-tab-header.component';
import {PropertyTabComponent} from './property-tab.component';
import {PropertyViewModule} from './property-view/property-view.module';
@NgModule({
declarations: [PropertyTabComponent, PropertyTabHeaderComponent, ComponentMetadataComponent],
imports: [PropertyViewModule, CommonModule, MatButtonModule, MatExpansionModule, MatIconModule, MatTooltipModule],
imports: [
PropertyViewModule, CommonModule, MatButtonModule, MatExpansionModule, MatIconModule,
MatTooltipModule
],
exports: [PropertyTabComponent],
})
export class PropertyTabModule {}
export class PropertyTabModule {
}
@@ -1,16 +1,7 @@
import {
AfterViewChecked,
ChangeDetectorRef,
Component,
ElementRef,
EventEmitter,
Input,
OnInit,
Output,
} from '@angular/core';
import {AfterViewChecked, ChangeDetectorRef, Component, ElementRef, EventEmitter, Input, OnInit, Output,} from '@angular/core';
type EditorType = string | number | boolean;
type EditorResult = EditorType | Array<EditorType>;
type EditorType = string|number|boolean;
type EditorResult = EditorType|Array<EditorType>;
enum PropertyEditorState {
Read,
@@ -1,6 +1,7 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { PropType } from 'protocol';
import { FlatNode } from '../../property-resolver/element-property-resolver';
import {Component, EventEmitter, Input, Output} from '@angular/core';
import {PropType} from 'protocol';
import {FlatNode} from '../../property-resolver/element-property-resolver';
@Component({
selector: 'ng-property-preview',
@@ -12,6 +13,7 @@ export class PropertyPreviewComponent {
@Output() inspect = new EventEmitter<void>();
get isClickableProp(): boolean {
return this.node.prop.descriptor.type === PropType.Function || this.node.prop.descriptor.type === PropType.HTMLNode;
return this.node.prop.descriptor.type === PropType.Function ||
this.node.prop.descriptor.type === PropType.HTMLNode;
}
}
@@ -1,7 +1,8 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { IndexedNode } from '../../directive-forest/index-forest';
import { FlatNode } from '../../property-resolver/element-property-resolver';
import { DirectivePosition } from 'protocol';
import {Component, EventEmitter, Input, Output} from '@angular/core';
import {DirectivePosition} from 'protocol';
import {IndexedNode} from '../../directive-forest/index-forest';
import {FlatNode} from '../../property-resolver/element-property-resolver';
@Component({
templateUrl: './property-tab-body.component.html',
@@ -9,10 +10,10 @@ import { DirectivePosition } from 'protocol';
styleUrls: ['./property-tab-body.component.scss'],
})
export class PropertyTabBodyComponent {
@Input() currentSelectedElement: IndexedNode | null;
@Output() inspect = new EventEmitter<{ node: FlatNode; directivePosition: DirectivePosition }>();
@Input() currentSelectedElement: IndexedNode|null;
@Output() inspect = new EventEmitter<{node: FlatNode; directivePosition: DirectivePosition}>();
getCurrentDirectives(): string[] | undefined {
getCurrentDirectives(): string[]|undefined {
if (!this.currentSelectedElement) {
return;
}
@@ -1,8 +1,9 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { DirectivePropertyResolver, DirectiveTreeData } from '../../property-resolver/directive-property-resolver';
import { FlatNode } from '../../property-resolver/element-property-resolver';
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
import { DirectivePosition } from 'protocol';
import {CdkDragDrop, moveItemInArray} from '@angular/cdk/drag-drop';
import {Component, EventEmitter, Input, Output} from '@angular/core';
import {DirectivePosition} from 'protocol';
import {DirectivePropertyResolver, DirectiveTreeData} from '../../property-resolver/directive-property-resolver';
import {FlatNode} from '../../property-resolver/element-property-resolver';
@Component({
selector: 'ng-property-view-body',
@@ -15,11 +16,14 @@ export class PropertyViewBodyComponent {
@Input() directiveOutputControls: DirectiveTreeData;
@Input() directiveStateControls: DirectiveTreeData;
@Output() inspect = new EventEmitter<{ node: FlatNode; directivePosition: DirectivePosition }>();
@Output() inspect = new EventEmitter<{node: FlatNode; directivePosition: DirectivePosition}>();
categoryOrder = [0, 1, 2];
get panels(): { title: string; hidden: boolean; controls: DirectiveTreeData; documentation: string, class: string }[] {
get panels(): {
title: string; hidden: boolean; controls: DirectiveTreeData; documentation: string,
class: string
}[] {
return [
{
title: '@Inputs',
@@ -46,10 +50,11 @@ export class PropertyViewBodyComponent {
}
get controlsLoaded(): boolean {
return !!this.directiveStateControls && !!this.directiveOutputControls && !!this.directiveInputControls;
return !!this.directiveStateControls && !!this.directiveOutputControls &&
!!this.directiveInputControls;
}
updateValue({ node, newValue }: { node: FlatNode; newValue: any }): void {
updateValue({node, newValue}: {node: FlatNode; newValue: any}): void {
this.controller.updateValue(node, newValue);
}
@@ -1,4 +1,4 @@
import { Component, Input } from '@angular/core';
import {Component, Input} from '@angular/core';
@Component({
selector: 'ng-property-view-header',
@@ -1,7 +1,8 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { PropertyDataSource } from '../../property-resolver/property-data-source';
import { FlatNode } from '../../property-resolver/element-property-resolver';
import { FlatTreeControl } from '@angular/cdk/tree';
import {FlatTreeControl} from '@angular/cdk/tree';
import {Component, EventEmitter, Input, Output} from '@angular/core';
import {FlatNode} from '../../property-resolver/element-property-resolver';
import {PropertyDataSource} from '../../property-resolver/property-data-source';
@Component({
selector: 'ng-property-view-tree',
@@ -25,7 +26,7 @@ export class PropertyViewTreeComponent {
}
expand(node: FlatNode): void {
const { prop } = node;
const {prop} = node;
if (!prop.descriptor.expandable) {
return;
}
@@ -1,7 +1,8 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { DirectivePropertyResolver, DirectiveTreeData } from '../../property-resolver/directive-property-resolver';
import { ElementPropertyResolver, FlatNode } from '../../property-resolver/element-property-resolver';
import { DirectivePosition } from 'protocol';
import {Component, EventEmitter, Input, Output} from '@angular/core';
import {DirectivePosition} from 'protocol';
import {DirectivePropertyResolver, DirectiveTreeData} from '../../property-resolver/directive-property-resolver';
import {ElementPropertyResolver, FlatNode} from '../../property-resolver/element-property-resolver';
@Component({
selector: 'ng-property-view',
@@ -10,23 +11,23 @@ import { DirectivePosition } from 'protocol';
})
export class PropertyViewComponent {
@Input() directive: string;
@Output() inspect = new EventEmitter<{ node: FlatNode; directivePosition: DirectivePosition }>();
@Output() inspect = new EventEmitter<{node: FlatNode; directivePosition: DirectivePosition}>();
constructor(private _nestedProps: ElementPropertyResolver) {}
get controller(): DirectivePropertyResolver | undefined {
get controller(): DirectivePropertyResolver|undefined {
return this._nestedProps.getDirectiveController(this.directive);
}
get directiveInputControls(): DirectiveTreeData | void {
get directiveInputControls(): DirectiveTreeData|void {
return this.controller?.directiveInputControls;
}
get directiveOutputControls(): DirectiveTreeData | void {
get directiveOutputControls(): DirectiveTreeData|void {
return this.controller?.directiveOutputControls;
}
get directiveStateControls(): DirectiveTreeData | void {
get directiveStateControls(): DirectiveTreeData|void {
return this.controller?.directiveStateControls;
}
}
@@ -1,20 +1,20 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { PropertyViewBodyComponent } from './property-view-body.component';
import { PropertyViewHeaderComponent } from './property-view-header.component';
import { PropertyViewTreeComponent } from './property-view-tree.component';
import { PropertyViewComponent } from './property-view.component';
import { PropertyTabBodyComponent } from './property-tab-body.component';
import { PropertyPreviewComponent } from './property-preview.component';
import { PropertyEditorComponent } from './property-editor.component';
import {DragDropModule} from '@angular/cdk/drag-drop';
import {CommonModule} from '@angular/common';
import {NgModule} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {MatExpansionModule} from '@angular/material/expansion';
import {MatIconModule} from '@angular/material/icon';
import {MatToolbarModule} from '@angular/material/toolbar';
import {MatTooltipModule} from '@angular/material/tooltip';
import {MatTreeModule} from '@angular/material/tree';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatIconModule } from '@angular/material/icon';
import { MatTooltipModule } from '@angular/material/tooltip';
import { MatExpansionModule } from '@angular/material/expansion';
import { MatTreeModule } from '@angular/material/tree';
import { DragDropModule } from '@angular/cdk/drag-drop';
import {PropertyEditorComponent} from './property-editor.component';
import {PropertyPreviewComponent} from './property-preview.component';
import {PropertyTabBodyComponent} from './property-tab-body.component';
import {PropertyViewBodyComponent} from './property-view-body.component';
import {PropertyViewHeaderComponent} from './property-view-header.component';
import {PropertyViewTreeComponent} from './property-view-tree.component';
import {PropertyViewComponent} from './property-view.component';
@NgModule({
declarations: [
@@ -46,4 +46,5 @@ import { DragDropModule } from '@angular/cdk/drag-drop';
PropertyEditorComponent,
],
})
export class PropertyViewModule {}
export class PropertyViewModule {
}
@@ -1,6 +1,7 @@
import { Subject } from 'rxjs';
import { Injectable } from '@angular/core';
import { toISO8601Compact } from '../../vendor/chromium/date-utilities';
import {Injectable} from '@angular/core';
import {Subject} from 'rxjs';
import {toISO8601Compact} from '../../vendor/chromium/date-utilities';
@Injectable({
providedIn: 'root',
@@ -14,7 +15,7 @@ export class FileApiService {
try {
this.uploadedData.next(JSON.parse((event.target as any).result));
} catch (e) {
this.uploadedData.next({ error: e });
this.uploadedData.next({error: e});
}
(parentEvent.target as any).value = '';
};
@@ -24,7 +25,8 @@ export class FileApiService {
saveObjectAsJSON(object: object): void {
const downloadLink = document.createElement('a');
downloadLink.download = `NgDevTools-Profile-${toISO8601Compact(new Date())}.json`;
downloadLink.href = URL.createObjectURL(new Blob([JSON.stringify(object)], { type: 'application/json' }));
downloadLink.href =
URL.createObjectURL(new Blob([JSON.stringify(object)], {type: 'application/json'}));
downloadLink.click();
setTimeout(() => URL.revokeObjectURL(downloadLink.href));
}
@@ -1,11 +1,11 @@
import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import {Component, Inject} from '@angular/core';
import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog';
interface DialogData {
profilerVersion?: number;
importedVersion?: number;
errorMessage?: string;
status: 'ERROR' | 'INVALID_VERSION';
status: 'ERROR'|'INVALID_VERSION';
}
@Component({
@@ -15,7 +15,6 @@ interface DialogData {
})
export class ProfilerImportDialogComponent {
constructor(
public dialogRef: MatDialogRef<ProfilerImportDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: DialogData
) {}
public dialogRef: MatDialogRef<ProfilerImportDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: DialogData) {}
}
@@ -1,11 +1,12 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { MessageBus, Events, ProfilerFrame } from 'protocol';
import { FileApiService } from './file-api-service';
import { MatDialog } from '@angular/material/dialog';
import { ProfilerImportDialogComponent } from './profiler-import-dialog.component';
import { Subject, Subscription } from 'rxjs';
import {Component, OnDestroy, OnInit} from '@angular/core';
import {MatDialog} from '@angular/material/dialog';
import {Events, MessageBus, ProfilerFrame} from 'protocol';
import {Subject, Subscription} from 'rxjs';
type State = 'idle' | 'recording' | 'visualizing';
import {FileApiService} from './file-api-service';
import {ProfilerImportDialogComponent} from './profiler-import-dialog.component';
type State = 'idle'|'recording'|'visualizing';
const SUPPORTED_VERSIONS = [1];
const PROFILER_VERSION = 1;
@@ -25,10 +26,8 @@ export class ProfilerComponent implements OnInit, OnDestroy {
private _buffer: ProfilerFrame[] = [];
constructor(
private _fileApiService: FileApiService,
private _messageBus: MessageBus<Events>,
public dialog: MatDialog
) {}
private _fileApiService: FileApiService, private _messageBus: MessageBus<Events>,
public dialog: MatDialog) {}
startRecording(): void {
this.state = 'recording';
@@ -60,7 +59,7 @@ export class ProfilerComponent implements OnInit, OnDestroy {
console.error(importedFile.error);
this.dialog.open(ProfilerImportDialogComponent, {
width: '600px',
data: { status: 'ERROR', errorMessage: importedFile.error },
data: {status: 'ERROR', errorMessage: importedFile.error},
});
return;
@@ -69,7 +68,11 @@ export class ProfilerComponent implements OnInit, OnDestroy {
if (!SUPPORTED_VERSIONS.includes(importedFile.version)) {
const processDataDialog = this.dialog.open(ProfilerImportDialogComponent, {
width: '600px',
data: { importedVersion: importedFile.version, profilerVersion: PROFILER_VERSION, status: 'INVALID_VERSION' },
data: {
importedVersion: importedFile.version,
profilerVersion: PROFILER_VERSION,
status: 'INVALID_VERSION'
},
});
processDataDialog.afterClosed().subscribe((result) => {
@@ -1,16 +1,16 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatSelectModule } from '@angular/material/select';
import { MatDialogModule } from '@angular/material/dialog';
import { FormsModule } from '@angular/forms';
import {CommonModule} from '@angular/common';
import {NgModule} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {MatButtonModule} from '@angular/material/button';
import {MatCardModule} from '@angular/material/card';
import {MatDialogModule} from '@angular/material/dialog';
import {MatIconModule} from '@angular/material/icon';
import {MatSelectModule} from '@angular/material/select';
import {MatTooltipModule} from '@angular/material/tooltip';
import { ProfilerComponent } from './profiler.component';
import { TimelineModule } from './timeline/timeline.module';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { ProfilerImportDialogComponent } from './profiler-import-dialog.component';
import { MatIconModule } from '@angular/material/icon';
import { MatTooltipModule } from '@angular/material/tooltip';
import {ProfilerImportDialogComponent} from './profiler-import-dialog.component';
import {ProfilerComponent} from './profiler.component';
import {TimelineModule} from './timeline/timeline.module';
@NgModule({
declarations: [ProfilerComponent, ProfilerImportDialogComponent],
@@ -28,4 +28,5 @@ import { MatTooltipModule } from '@angular/material/tooltip';
exports: [ProfilerComponent],
entryComponents: [ProfilerImportDialogComponent],
})
export class ProfilerModule {}
export class ProfilerModule {
}

Some files were not shown because too many files have changed in this diff Show More