mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
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:
@@ -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,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}}));
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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,4 +1,4 @@
|
||||
import { browser, by, element } from 'protractor';
|
||||
import {browser, by, element} from 'protractor';
|
||||
|
||||
export class AppPage {
|
||||
navigateTo(): Promise<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: 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
@@ -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> {
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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
-1
@@ -1,4 +1,4 @@
|
||||
import { ComponentInspector } from './component-inspector';
|
||||
import {ComponentInspector} from './component-inspector';
|
||||
|
||||
describe('ComponentInspector', () => {
|
||||
it('should create instance from class', () => {
|
||||
|
||||
+18
-17
@@ -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}`;
|
||||
|
||||
+111
-120
@@ -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) {
|
||||
|
||||
+78
-81
@@ -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,
|
||||
|
||||
+74
-89
@@ -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;
|
||||
|
||||
|
||||
@@ -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};
|
||||
};
|
||||
|
||||
+41
-66
@@ -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);
|
||||
}
|
||||
|
||||
+16
-14
@@ -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 {
|
||||
}
|
||||
|
||||
+28
-22
@@ -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', () => {
|
||||
|
||||
+8
-17
@@ -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;
|
||||
}
|
||||
|
||||
+9
-7
@@ -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 {
|
||||
}
|
||||
|
||||
+6
-7
@@ -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)));
|
||||
|
||||
|
||||
+55
-56
@@ -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 {}
|
||||
|
||||
+13
-11
@@ -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;
|
||||
};
|
||||
|
||||
+43
-51
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+13
-10
@@ -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
-1
@@ -1,4 +1,4 @@
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
import {Component, EventEmitter, Input, Output} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'ng-filter',
|
||||
|
||||
+9
-7
@@ -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 {
|
||||
}
|
||||
|
||||
+80
-81
@@ -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: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
+12
-11
@@ -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));
|
||||
|
||||
+3
-2
@@ -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', () => {
|
||||
|
||||
+13
-16
@@ -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;
|
||||
});
|
||||
|
||||
+3
-2
@@ -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: {
|
||||
|
||||
+43
-50
@@ -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];
|
||||
});
|
||||
|
||||
|
||||
+3
-2
@@ -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: {
|
||||
|
||||
+9
-16
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+11
-17
@@ -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);
|
||||
|
||||
+17
-22
@@ -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: {
|
||||
|
||||
+35
-34
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+5
-4
@@ -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;
|
||||
|
||||
+8
-7
@@ -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;
|
||||
}
|
||||
|
||||
+4
-3
@@ -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 {
|
||||
|
||||
+6
-5
@@ -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}>();
|
||||
}
|
||||
|
||||
+17
-12
@@ -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 {
|
||||
}
|
||||
|
||||
+3
-12
@@ -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,
|
||||
|
||||
+6
-4
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+8
-7
@@ -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;
|
||||
}
|
||||
|
||||
+14
-9
@@ -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
-1
@@ -1,4 +1,4 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import {Component, Input} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'ng-property-view-header',
|
||||
|
||||
+6
-5
@@ -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;
|
||||
}
|
||||
|
||||
+10
-9
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+18
-17
@@ -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));
|
||||
}
|
||||
|
||||
+5
-6
@@ -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) {}
|
||||
}
|
||||
|
||||
+16
-13
@@ -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
Reference in New Issue
Block a user