diff --git a/projects/ng-devtools-backend/src/lib/component-tree.ts b/projects/ng-devtools-backend/src/lib/component-tree.ts index 582eea47fd9..deb32f30019 100644 --- a/projects/ng-devtools-backend/src/lib/component-tree.ts +++ b/projects/ng-devtools-backend/src/lib/component-tree.ts @@ -3,6 +3,7 @@ import { deeplySerializeSelectedProperties, serializeDirectiveState } from './st import { ComponentExplorerViewQuery, DevToolsNode, + DirectiveMetadata, DirectivesProperties, ElementPosition, PropertyQueryTypes, @@ -10,6 +11,7 @@ import { } from 'protocol'; import { IndexedNode } from './observer/identity-tracker'; import { buildDirectiveTree, getLViewFromDirectiveOrElementInstance } from './lview-transform'; +import { ViewEncapsulation } from '@angular/core'; const ngDebug = () => (window as any).ng; @@ -40,15 +42,13 @@ export const getLatestComponentState = (query: ComponentExplorerViewQuery): Dire if (query.propertyQuery.type === PropertyQueryTypes.All) { result[dir.name] = { props: serializeDirectiveState(dir.instance), - inputs: getDirectiveMetaData(dir.instance).inputs(), - outputs: getDirectiveMetaData(dir.instance).outputs(), + metadata: getDirectiveMetadata(dir.instance), }; } if (query.propertyQuery.type === PropertyQueryTypes.Specified) { result[dir.name] = { props: deeplySerializeSelectedProperties(dir.instance, query.propertyQuery.properties[dir.name] || []), - inputs: getDirectiveMetaData(dir.instance).inputs(), - outputs: getDirectiveMetaData(dir.instance).outputs(), + metadata: getDirectiveMetadata(dir.instance), }; } }; @@ -61,19 +61,28 @@ export const getLatestComponentState = (query: ComponentExplorerViewQuery): Dire return result; }; -export const getDirectiveMetaData = (dir: any) => { - const getDirInputOrOutput = (inputOrOutputKey: 'inputs' | 'outputs') => { +const enum DirectiveMetadataKey { + INPUTS = 'inputs', + OUTPUTS = 'outputs', + ENCAPSULATION = 'encapsulation', + ON_PUSH = 'onPush', +} + +export const getDirectiveMetadata = (dir: any): DirectiveMetadata => { + const safelyGrabMetadata = (key: DirectiveMetadataKey) => { try { - return dir.constructor.ɵcmp ? dir.constructor.ɵcmp[inputOrOutputKey] : dir.constructor.ɵdir[inputOrOutputKey]; + return dir.constructor.ɵcmp ? dir.constructor.ɵcmp[key] : dir.constructor.ɵdir[key]; } catch { - console.warn('Could not find metadata for: ', dir); - return {}; + console.warn(`Could not find metadata for key: ${key} in directive:`, dir); + return undefined; } }; return { - inputs: () => getDirInputOrOutput('inputs'), - outputs: () => getDirInputOrOutput('outputs'), + inputs: safelyGrabMetadata(DirectiveMetadataKey.INPUTS), + outputs: safelyGrabMetadata(DirectiveMetadataKey.OUTPUTS), + encapsulation: safelyGrabMetadata(DirectiveMetadataKey.ENCAPSULATION), + onPush: safelyGrabMetadata(DirectiveMetadataKey.ON_PUSH), }; }; diff --git a/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-resolver/directive-property-resolver.ts b/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-resolver/directive-property-resolver.ts index 336674aa0c9..277a3b0e667 100644 --- a/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-resolver/directive-property-resolver.ts +++ b/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-resolver/directive-property-resolver.ts @@ -5,6 +5,7 @@ import { FlatTreeControl } from '@angular/cdk/tree'; import { getExpandedDirectiveProperties } from './property-expanded-directive-properties'; import { Observable } from 'rxjs'; import { Property, FlatNode } from './element-property-resolver'; +import { ViewEncapsulation } from '@angular/core'; export interface DirectiveTreeData { dataSource: PropertyDataSource; @@ -91,6 +92,14 @@ export class DirectivePropertyResolver { return this._directivePosition; } + get directiveViewEncapsulation(): ViewEncapsulation | undefined { + return this._props.metadata?.encapsulation; + } + + get directiveHasOnPushStrategy(): boolean | undefined { + return this._props.metadata?.onPush; + } + getExpandedProperties(): NestedProp[] { return [ ...getExpandedDirectiveProperties(this._inputsDataSource.data), @@ -156,8 +165,8 @@ export class DirectivePropertyResolver { outputProps: { [name: string]: Descriptor }; stateProps: { [name: string]: Descriptor }; } { - const inputLabels: Set = new Set(Object.keys(this._props.inputs || {})); - const outputLabels: Set = new Set(Object.keys(this._props.outputs || {})); + const inputLabels: Set = new Set(Object.keys(this._props.metadata?.inputs || {})); + const outputLabels: Set = new Set(Object.keys(this._props.metadata?.outputs || {})); const inputProps = {}; const outputProps = {}; diff --git a/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-resolver/element-property-resolver.ts b/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-resolver/element-property-resolver.ts index 519e0b451ad..83a4f39a673 100644 --- a/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-resolver/element-property-resolver.ts +++ b/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-resolver/element-property-resolver.ts @@ -33,14 +33,7 @@ export class ElementPropertyResolver { } setProperties(indexedNode: IndexedNode, data: DirectivesProperties): void { - // To prevent memory leaks when a directive no longer exists on an element - const currentProps = [...this._directivePropertiesController.keys()]; - const incomingProps = new Set(Object.keys(data)); - for (const prop of currentProps) { - if (!incomingProps.has(prop)) { - this._directivePropertiesController.delete(prop); - } - } + this._flushDeletedProperties(data); Object.keys(data).forEach((key) => { const controller = this._directivePropertiesController.get(key); @@ -62,6 +55,16 @@ export class ElementPropertyResolver { }); } + private _flushDeletedProperties(data: DirectivesProperties): void { + const currentProps = [...this._directivePropertiesController.keys()]; + const incomingProps = new Set(Object.keys(data)); + for (const prop of currentProps) { + if (!incomingProps.has(prop)) { + this._directivePropertiesController.delete(prop); + } + } + } + getExpandedProperties(): ComponentExplorerViewProperties { const result: ComponentExplorerViewProperties = {}; for (const [directive] of this._directivePropertiesController) { diff --git a/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-tab-header/component-metadata/component-metadata.component.html b/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-tab-header/component-metadata/component-metadata.component.html new file mode 100644 index 00000000000..d68aeddf092 --- /dev/null +++ b/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-tab-header/component-metadata/component-metadata.component.html @@ -0,0 +1,16 @@ + + View Encapsulation: + + + Change Detection Strategy: + diff --git a/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-tab-header/component-metadata/component-metadata.component.scss b/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-tab-header/component-metadata/component-metadata.component.scss new file mode 100644 index 00000000000..274693b8f7b --- /dev/null +++ b/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-tab-header/component-metadata/component-metadata.component.scss @@ -0,0 +1,24 @@ +.view-link { + display: block; + font-size: 11px; + padding: 2px; + color: unset; + text-decoration: unset; + + &:focus { + outline: none; + } + + &:hover { + background-color: #eee; + cursor: pointer; + } + + &:active { + background-color: #bcbcbc; + } +} + +.meta-data { + font-weight: 400; +} diff --git a/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-tab-header/component-metadata/component-metadata.component.ts b/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-tab-header/component-metadata/component-metadata.component.ts new file mode 100644 index 00000000000..a05bbdf8b56 --- /dev/null +++ b/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-tab-header/component-metadata/component-metadata.component.ts @@ -0,0 +1,37 @@ +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'; + +@Component({ + selector: 'ng-component-metadata', + templateUrl: './component-metadata.component.html', + styleUrls: ['./component-metadata.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ComponentMetadataComponent { + @Input() currentSelectedComponent: ComponentType; + + constructor(private _nestedProps: ElementPropertyResolver) {} + + viewEncapsulationModes = ['Emulated', 'Native', 'None', 'ShadowDom']; + + get controller(): DirectivePropertyResolver | undefined { + if (!this.currentSelectedComponent) { + return; + } + return this._nestedProps.getDirectiveController(this.currentSelectedComponent.name); + } + + get viewEncapsulation(): string | undefined { + const encapsulationIndex = this?.controller?.directiveViewEncapsulation; + if (encapsulationIndex !== undefined) { + return this.viewEncapsulationModes[encapsulationIndex]; + } + } + + get changeDetectionStrategy(): string | undefined { + const onPush = this?.controller?.directiveHasOnPushStrategy; + return onPush ? 'On Push' : onPush !== undefined ? 'Default' : undefined; + } +} diff --git a/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-tab-header/property-tab-header.component.html b/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-tab-header/property-tab-header.component.html index cf41b7165ef..fc5d8fa6b8d 100644 --- a/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-tab-header/property-tab-header.component.html +++ b/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-tab-header/property-tab-header.component.html @@ -1,19 +1,35 @@ -
- -
{{ currentSelectedElement.component.name }}
- -
- + + +
+ + + +
+
{{ currentSelectedElement.component.name }}
+ +
+
+
+ +
+
+
+
+ +
{{ currentSelectedElement.element }}
- -
+
+
+
No selected Element
diff --git a/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-tab-header/property-tab-header.component.scss b/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-tab-header/property-tab-header.component.scss index 11653bf2575..e70e1001b64 100644 --- a/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-tab-header/property-tab-header.component.scss +++ b/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-tab-header/property-tab-header.component.scss @@ -1,9 +1,16 @@ .element-header { display: flex; justify-content: space-between; + width: 100%; align-items: center; font-size: 12px; + .mat-icon-button { + height: 25px; + width: 25px; + line-height: 25px; + } + .component-name { margin-left: 10px; font-weight: 700; @@ -18,17 +25,31 @@ } ::ng-deep { - .mat-button { - height: 25px; - width: 25px; - line-height: 25px; - margin-right: 5px; + .property-tab-header { + &.mat-accordion { + .mat-expansion-panel { + &:not(.mat-expanded) { + .mat-expansion-panel-header { + &:hover:not([aria-disabled='true']) { + background: #fff; + } + } + } + + .mat-expansion-panel-header { + padding: 0; + + .mat-expansion-panel-header-title { + margin-right: 0; + } + } + + .mat-expansion-panel-content { + .mat-expansion-panel-body { + padding: 0; + } + } + } + } } } - -.mat-icon-button { - height: 25px; - width: 25px; - line-height: 25px; - margin-right: 5px; -} diff --git a/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-tab-header/property-tab-header.component.ts b/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-tab-header/property-tab-header.component.ts index 4b1dc6afba9..0b5de973ea1 100644 --- a/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-tab-header/property-tab-header.component.ts +++ b/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-tab-header/property-tab-header.component.ts @@ -1,12 +1,19 @@ -import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; import { IndexedNode } from '../../directive-forest/index-forest'; @Component({ templateUrl: './property-tab-header.component.html', selector: 'ng-property-tab-header', styleUrls: ['./property-tab-header.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, }) export class PropertyTabHeaderComponent { @Input() currentSelectedElement: IndexedNode; + @Input() currentDirectives: string[] | undefined; @Output() viewSource = new EventEmitter(); + + handleViewSource(event: MouseEvent): void { + event.stopPropagation(); + this.viewSource.emit(); + } } diff --git a/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-tab.component.ts b/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-tab.component.ts index cae9b1e29e3..b766b0ffafc 100644 --- a/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-tab.component.ts +++ b/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-tab.component.ts @@ -1,6 +1,5 @@ -import { Component, EventEmitter, Input, Output, ViewChild } from '@angular/core'; +import { Component, EventEmitter, Input, Output } from '@angular/core'; import { IndexedNode } from '../directive-forest/index-forest'; -import { PropertyTabBodyComponent } from './property-tab-body/property-tab-body.component'; import { FlatNode } from '../property-resolver/element-property-resolver'; import { DirectivePosition } from 'protocol'; @@ -12,6 +11,4 @@ export class PropertyTabComponent { @Input() currentSelectedElement: IndexedNode; @Output() viewSource = new EventEmitter(); @Output() inspectFunction = new EventEmitter<{ node: FlatNode; directivePosition: DirectivePosition }>(); - - @ViewChild(PropertyTabBodyComponent) propertyTabBody: PropertyTabBodyComponent; } diff --git a/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-tab.module.ts b/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-tab.module.ts index e249012f015..8e19d9e952a 100644 --- a/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-tab.module.ts +++ b/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-tab.module.ts @@ -5,10 +5,17 @@ import { PropertyTabHeaderComponent } from './property-tab-header/property-tab-h import { CommonModule } from '@angular/common'; import { MatButtonModule } from '@angular/material/button'; import { PropertyTabBodyComponent } from './property-tab-body/property-tab-body.component'; +import { MatExpansionModule } from '@angular/material/expansion'; +import { ComponentMetadataComponent } from './property-tab-header/component-metadata/component-metadata.component'; @NgModule({ - declarations: [PropertyTabComponent, PropertyTabHeaderComponent, PropertyTabBodyComponent], - imports: [PropertyViewModule, CommonModule, MatButtonModule], + declarations: [ + PropertyTabComponent, + PropertyTabHeaderComponent, + PropertyTabBodyComponent, + ComponentMetadataComponent, + ], + imports: [PropertyViewModule, CommonModule, MatButtonModule, MatExpansionModule], exports: [PropertyTabComponent], }) export class PropertyTabModule {} diff --git a/projects/protocol/src/lib/messages.ts b/projects/protocol/src/lib/messages.ts index 646dec4e2e7..1250969ecf7 100644 --- a/projects/protocol/src/lib/messages.ts +++ b/projects/protocol/src/lib/messages.ts @@ -1,3 +1,5 @@ +import { ViewEncapsulation } from '@angular/core'; + export interface DirectiveType { name: string; id: number; @@ -45,10 +47,16 @@ export interface DirectivesProperties { [name: string]: Properties; } +export interface DirectiveMetadata { + inputs: { [name: string]: string }; + outputs: { [name: string]: string }; + encapsulation: ViewEncapsulation; + onPush: boolean; +} + export interface Properties { props: { [name: string]: Descriptor }; - inputs?: { [name: string]: string }; - outputs?: { [name: string]: string }; + metadata?: DirectiveMetadata; } export type ElementPosition = number[];