mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
fix(devtools): router tree details table data
Introduce layout fixes and use dedicated buttons for the view source and navigate actions.
This commit is contained in:
parent
44435ea97b
commit
cd0e96c1d0
7 changed files with 223 additions and 159 deletions
|
|
@ -1,38 +1,56 @@
|
|||
<th>{{label()}}</th>
|
||||
<td>
|
||||
@switch (type()) {
|
||||
@case ('chip') {
|
||||
<button
|
||||
ng-button
|
||||
size="compact"
|
||||
(click)="btnClick.emit('')"
|
||||
[disabled]="rowValue()?.toString().startsWith('Lazy ')"
|
||||
>
|
||||
{{ rowValue() }}
|
||||
</button>
|
||||
}
|
||||
@case ('flag') {
|
||||
<span [class]="rowValue() ? 'tag-active' : 'tag-inactive'">
|
||||
{{ rowValue() }}
|
||||
{{ rowValue() || 'false' }}
|
||||
</span>
|
||||
}
|
||||
@case ('list') {
|
||||
<div class="chips-container">
|
||||
@for (provider of dataArray(); track $index) {
|
||||
<button ng-button size="compact" (click)="btnClick.emit(provider)">
|
||||
{{ provider }}
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
@for (provider of dataArray(); track $index) {
|
||||
<div class="value-container">
|
||||
<span>{{ provider || '[empty string] ' }}</span
|
||||
><ng-container
|
||||
[ngTemplateOutlet]="actionBtn"
|
||||
[ngTemplateOutletContext]="{ value: provider }"
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@default {
|
||||
<span class="row-data">
|
||||
@if (renderValueAsJson()) {
|
||||
{{ rowValue() | json }}
|
||||
} @else {
|
||||
{{ rowValue() }}
|
||||
}
|
||||
</span>
|
||||
<div class="value-container">
|
||||
<span>
|
||||
@if (rowValue() && renderValueAsJson()) {
|
||||
{{ rowValue() | json }}
|
||||
} @else {
|
||||
{{ rowValue() || '[empty string] ' }}
|
||||
}</span
|
||||
><ng-container
|
||||
[ngTemplateOutlet]="actionBtn"
|
||||
[ngTemplateOutletContext]="{ value: rowValue() }"
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</td>
|
||||
|
||||
<ng-template #actionBtn let-value="value">
|
||||
@if (actionBtnType() !== 'none') {
|
||||
<button
|
||||
ng-button
|
||||
btnType="icon"
|
||||
(click)="actionBtnClick.emit(value)"
|
||||
[matTooltip]="!actionBtnDisabled() ? actionBtnTooltip() || value : null"
|
||||
[disabled]="actionBtnDisabled()"
|
||||
>
|
||||
@switch (actionBtnType()) {
|
||||
@case ('view-source') {
|
||||
<mat-icon class="view-source">code</mat-icon>
|
||||
}
|
||||
@case ('navigate') {
|
||||
<mat-icon class="navigate">output</mat-icon>
|
||||
}
|
||||
}
|
||||
</button>
|
||||
}
|
||||
</ng-template>
|
||||
|
|
|
|||
|
|
@ -13,11 +13,35 @@
|
|||
border-color: var(--red-06);
|
||||
}
|
||||
|
||||
.chips-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.375rem;
|
||||
overflow-wrap: normal;
|
||||
button {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
vertical-align: middle;
|
||||
color: var(--quaternary-contrast);
|
||||
|
||||
&:not([disabled]):hover {
|
||||
color: var(--primary-contrast);
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
cursor: default;
|
||||
color: var(--quinary-contrast);
|
||||
}
|
||||
|
||||
mat-icon {
|
||||
font-size: 14px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
|
||||
&.view-source {
|
||||
font-size: 16px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
|
|
|
|||
|
|
@ -74,10 +74,11 @@ describe('RouteDetailsRowComponent', () => {
|
|||
expect(dataElements[0].nativeElement.innerText).toEqual('false');
|
||||
});
|
||||
|
||||
it('should render a label and chip data', () => {
|
||||
it('should render a label with an action button', () => {
|
||||
fixture.componentRef.setInput('type', 'chip');
|
||||
fixture.componentRef.setInput('data', {name: 'Component Name'});
|
||||
fixture.componentRef.setInput('dataKey', 'name');
|
||||
fixture.componentRef.setInput('actionBtnType', 'view-source');
|
||||
fixture.detectChanges();
|
||||
|
||||
const labelElement = fixture.debugElement.query(By.css('th'));
|
||||
|
|
@ -85,13 +86,14 @@ describe('RouteDetailsRowComponent', () => {
|
|||
|
||||
const dataElements = fixture.debugElement.queryAll(By.css('button'));
|
||||
expect(dataElements.length).toEqual(1);
|
||||
expect(dataElements[0].nativeElement.innerText).toEqual('Component Name');
|
||||
});
|
||||
|
||||
it('should render a label and chip data disabled', () => {
|
||||
it('should render a label with a disabled action button', () => {
|
||||
fixture.componentRef.setInput('type', 'chip');
|
||||
fixture.componentRef.setInput('data', {name: 'Lazy Component Name'});
|
||||
fixture.componentRef.setInput('dataKey', 'name');
|
||||
fixture.componentRef.setInput('actionBtnType', 'view-source');
|
||||
fixture.componentRef.setInput('actionBtnDisabled', true);
|
||||
fixture.detectChanges();
|
||||
|
||||
const labelElement = fixture.debugElement.query(By.css('th'));
|
||||
|
|
@ -99,7 +101,6 @@ describe('RouteDetailsRowComponent', () => {
|
|||
|
||||
const dataElements = fixture.debugElement.queryAll(By.css('button'));
|
||||
expect(dataElements.length).toEqual(1);
|
||||
expect(dataElements[0].nativeElement.innerText).toEqual('Lazy Component Name');
|
||||
expect(dataElements[0].nativeElement.disabled).toEqual(true);
|
||||
});
|
||||
|
||||
|
|
@ -107,6 +108,7 @@ describe('RouteDetailsRowComponent', () => {
|
|||
fixture.componentRef.setInput('type', 'list');
|
||||
fixture.componentRef.setInput('data', {providers: ['Guard 1', 'Guard 2']});
|
||||
fixture.componentRef.setInput('dataKey', 'providers');
|
||||
fixture.componentRef.setInput('actionBtnType', 'view-source');
|
||||
fixture.detectChanges();
|
||||
|
||||
const labelElement = fixture.debugElement.query(By.css('th'));
|
||||
|
|
@ -114,7 +116,5 @@ describe('RouteDetailsRowComponent', () => {
|
|||
|
||||
const dataElements = fixture.debugElement.queryAll(By.css('button'));
|
||||
expect(dataElements.length).toEqual(2);
|
||||
expect(dataElements[0].nativeElement.innerText).toEqual('Guard 1');
|
||||
expect(dataElements[1].nativeElement.innerText).toEqual('Guard 2');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,17 +7,20 @@
|
|||
*/
|
||||
|
||||
import {Component, computed, input, output, ChangeDetectionStrategy} from '@angular/core';
|
||||
import {JsonPipe, NgTemplateOutlet} from '@angular/common';
|
||||
import {MatIcon} from '@angular/material/icon';
|
||||
import {ButtonComponent} from '../../shared/button/button.component';
|
||||
import {JsonPipe} from '@angular/common';
|
||||
import {RouterTreeNode} from './router-tree-fns';
|
||||
import {MatTooltip} from '@angular/material/tooltip';
|
||||
|
||||
export type RowType = 'text' | 'chip' | 'flag' | 'list';
|
||||
export type RowType = 'text' | 'flag' | 'list';
|
||||
export type ActionBtnType = 'none' | 'view-source' | 'navigate';
|
||||
|
||||
@Component({
|
||||
selector: '[ng-route-details-row]',
|
||||
templateUrl: './route-details-row.component.html',
|
||||
styleUrls: ['./route-details-row.component.scss'],
|
||||
imports: [ButtonComponent, JsonPipe],
|
||||
imports: [NgTemplateOutlet, ButtonComponent, JsonPipe, MatIcon, MatTooltip],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class RouteDetailsRowComponent {
|
||||
|
|
@ -26,8 +29,11 @@ export class RouteDetailsRowComponent {
|
|||
readonly dataKey = input.required<string>();
|
||||
readonly renderValueAsJson = input<boolean>(false);
|
||||
readonly type = input<RowType>('text');
|
||||
readonly actionBtnType = input<ActionBtnType>('none');
|
||||
readonly actionBtnTooltip = input<string>('');
|
||||
readonly actionBtnDisabled = input<boolean>(false);
|
||||
|
||||
readonly btnClick = output<string>();
|
||||
readonly actionBtnClick = output<string>();
|
||||
|
||||
readonly rowValue = computed(() => {
|
||||
return this.data()[this.dataKey() as keyof RouterTreeNode];
|
||||
|
|
|
|||
|
|
@ -47,134 +47,147 @@
|
|||
</button>
|
||||
|
||||
<!-- TODO: Convert to a description list (<dl>) -->
|
||||
<table class="ng-table">
|
||||
<tr
|
||||
ng-route-details-row
|
||||
label="Path"
|
||||
type="chip"
|
||||
dataKey="path"
|
||||
[data]="data"
|
||||
(btnClick)="navigateRoute(route)"
|
||||
></tr>
|
||||
<div class="scrollable-wrapper">
|
||||
<table class="ng-table">
|
||||
<tr
|
||||
ng-route-details-row
|
||||
label="Path"
|
||||
dataKey="path"
|
||||
[data]="data"
|
||||
actionBtnType="navigate"
|
||||
[actionBtnTooltip]="'Navigate to ' + data.path"
|
||||
(actionBtnClick)="navigateRoute(route)"
|
||||
></tr>
|
||||
|
||||
@if (!data.isRedirect) {
|
||||
<tr
|
||||
ng-route-details-row
|
||||
label="Component"
|
||||
type="chip"
|
||||
dataKey="component"
|
||||
[data]="data"
|
||||
(btnClick)="viewComponentSource(data.component)"
|
||||
></tr>
|
||||
}
|
||||
@if (!data.redirectTo) {
|
||||
<tr
|
||||
ng-route-details-row
|
||||
label="Component"
|
||||
dataKey="component"
|
||||
[data]="data"
|
||||
actionBtnType="view-source"
|
||||
actionBtnTooltip="View source"
|
||||
[actionBtnDisabled]="data.component.includes('Lazy')"
|
||||
(actionBtnClick)="viewComponentSource(data.component)"
|
||||
></tr>
|
||||
} @else {
|
||||
<tr
|
||||
ng-route-details-row
|
||||
label="Redirect to"
|
||||
dataKey="redirectTo"
|
||||
[data]="data"
|
||||
actionBtnType="view-source"
|
||||
actionBtnTooltip="View source"
|
||||
(actionBtnClick)="viewFunctionSource(data.redirectTo, 'redirectTo')"
|
||||
></tr>
|
||||
}
|
||||
|
||||
@if (data.pathMatch) {
|
||||
<tr ng-route-details-row label="Path Match" dataKey="pathMatch" [data]="data"></tr>
|
||||
}
|
||||
@if (route?.data?.data?.length > 0) {
|
||||
<tr
|
||||
ng-route-details-row
|
||||
label="Data"
|
||||
[renderValueAsJson]="true"
|
||||
dataKey="data"
|
||||
[data]="data"
|
||||
></tr>
|
||||
}
|
||||
@if (data.canActivateGuards && data.canActivateGuards.length > 0) {
|
||||
<tr
|
||||
ng-route-details-row
|
||||
label="Can Activate Guards"
|
||||
type="list"
|
||||
dataKey="canActivateGuards"
|
||||
[data]="data"
|
||||
(btnClick)="viewSourceFromRouter($event, 'canActivate')"
|
||||
></tr>
|
||||
}
|
||||
@if (data.canActivateChildGuards && data.canActivateChildGuards.length > 0) {
|
||||
<tr
|
||||
ng-route-details-row
|
||||
label="Can Activate Child Guards"
|
||||
type="list"
|
||||
dataKey="canActivateChildGuards"
|
||||
[data]="data"
|
||||
(btnClick)="viewSourceFromRouter($event, 'canActivateChild')"
|
||||
></tr>
|
||||
}
|
||||
@if (data.canDeactivateGuards && data.canDeactivateGuards.length > 0) {
|
||||
<tr
|
||||
ng-route-details-row
|
||||
label="Can DeActivate Guards"
|
||||
type="list"
|
||||
dataKey="canDeactivateGuards"
|
||||
[data]="data"
|
||||
(btnClick)="viewSourceFromRouter($event, 'canDeactivate')"
|
||||
></tr>
|
||||
}
|
||||
@if (data.canMatchGuards && data.canMatchGuards.length > 0) {
|
||||
<tr
|
||||
ng-route-details-row
|
||||
label="Can Match Guards"
|
||||
type="list"
|
||||
dataKey="canMatchGuards"
|
||||
[data]="data"
|
||||
(btnClick)="viewSourceFromRouter($event, 'canMatch')"
|
||||
></tr>
|
||||
}
|
||||
@if (data.pathMatch) {
|
||||
<tr ng-route-details-row label="Path Match" dataKey="pathMatch" [data]="data"></tr>
|
||||
}
|
||||
@if (route?.data?.data?.length > 0) {
|
||||
<tr
|
||||
ng-route-details-row
|
||||
label="Data"
|
||||
[renderValueAsJson]="true"
|
||||
dataKey="data"
|
||||
[data]="data"
|
||||
></tr>
|
||||
}
|
||||
@if (data.canActivateGuards && data.canActivateGuards.length > 0) {
|
||||
<tr
|
||||
ng-route-details-row
|
||||
label="Can Activate Guards"
|
||||
type="list"
|
||||
dataKey="canActivateGuards"
|
||||
[data]="data"
|
||||
actionBtnType="view-source"
|
||||
actionBtnTooltip="View source"
|
||||
(actionBtnClick)="viewSourceFromRouter($event, 'canActivate')"
|
||||
></tr>
|
||||
}
|
||||
@if (data.canActivateChildGuards && data.canActivateChildGuards.length > 0) {
|
||||
<tr
|
||||
ng-route-details-row
|
||||
label="Can Activate Child Guards"
|
||||
type="list"
|
||||
dataKey="canActivateChildGuards"
|
||||
[data]="data"
|
||||
(actionBtnClick)="viewSourceFromRouter($event, 'canActivateChild')"
|
||||
></tr>
|
||||
}
|
||||
@if (data.canDeactivateGuards && data.canDeactivateGuards.length > 0) {
|
||||
<tr
|
||||
ng-route-details-row
|
||||
label="Can DeActivate Guards"
|
||||
type="list"
|
||||
dataKey="canDeactivateGuards"
|
||||
[data]="data"
|
||||
actionBtnType="view-source"
|
||||
actionBtnTooltip="View source"
|
||||
(actionBtnClick)="viewSourceFromRouter($event, 'canDeactivate')"
|
||||
></tr>
|
||||
}
|
||||
@if (data.canMatchGuards && data.canMatchGuards.length > 0) {
|
||||
<tr
|
||||
ng-route-details-row
|
||||
label="Can Match Guards"
|
||||
type="list"
|
||||
dataKey="canMatchGuards"
|
||||
[data]="data"
|
||||
actionBtnType="view-source"
|
||||
actionBtnTooltip="View source"
|
||||
(actionBtnClick)="viewSourceFromRouter($event, 'canMatch')"
|
||||
></tr>
|
||||
}
|
||||
|
||||
@if (data.providers && data.providers.length > 0) {
|
||||
@if (data.providers && data.providers.length > 0) {
|
||||
<tr
|
||||
ng-route-details-row
|
||||
label="Providers"
|
||||
type="list"
|
||||
dataKey="providers"
|
||||
[data]="data"
|
||||
actionBtnType="view-source"
|
||||
actionBtnTooltip="View source"
|
||||
(actionBtnClick)="viewSourceFromRouter($event, 'providers')"
|
||||
></tr>
|
||||
}
|
||||
@if (data.title) {
|
||||
<tr
|
||||
ng-route-details-row
|
||||
label="Title"
|
||||
dataKey="title"
|
||||
[data]="data"
|
||||
actionBtnType="view-source"
|
||||
actionBtnTooltip="View source"
|
||||
(actionBtnClick)="viewFunctionSource(data.title, 'title')"
|
||||
></tr>
|
||||
}
|
||||
<tr
|
||||
ng-route-details-row
|
||||
label="Providers"
|
||||
type="list"
|
||||
dataKey="providers"
|
||||
label="Active"
|
||||
type="flag"
|
||||
dataKey="isActive"
|
||||
[data]="data"
|
||||
(btnClick)="viewSourceFromRouter($event, 'providers')"
|
||||
></tr>
|
||||
}
|
||||
@if (data.title) {
|
||||
<tr
|
||||
ng-route-details-row
|
||||
type="chip"
|
||||
label="Title"
|
||||
dataKey="title"
|
||||
label="Auxiliary"
|
||||
type="flag"
|
||||
dataKey="isAux"
|
||||
[data]="data"
|
||||
(btnClick)="viewFunctionSource(data.title, 'title')"
|
||||
></tr>
|
||||
}
|
||||
<tr
|
||||
ng-route-details-row
|
||||
label="Active"
|
||||
type="flag"
|
||||
dataKey="isActive"
|
||||
[data]="data"
|
||||
></tr>
|
||||
<tr
|
||||
ng-route-details-row
|
||||
label="Auxiliary"
|
||||
type="flag"
|
||||
dataKey="isAux"
|
||||
[data]="data"
|
||||
></tr>
|
||||
<tr ng-route-details-row label="Lazy" type="flag" dataKey="isLazy" [data]="data"></tr>
|
||||
<tr
|
||||
ng-route-details-row
|
||||
label="Redirecting"
|
||||
type="flag"
|
||||
dataKey="isRedirect"
|
||||
[data]="data"
|
||||
></tr>
|
||||
|
||||
@if (data.redirectTo) {
|
||||
<tr ng-route-details-row label="Lazy" type="flag" dataKey="isLazy" [data]="data"></tr>
|
||||
<tr
|
||||
ng-route-details-row
|
||||
label="Redirect to"
|
||||
type="chip"
|
||||
dataKey="redirectTo"
|
||||
label="Redirecting"
|
||||
type="flag"
|
||||
dataKey="isRedirect"
|
||||
[data]="data"
|
||||
(btnClick)="viewFunctionSource(data.redirectTo, 'redirectTo')"
|
||||
></tr>
|
||||
}
|
||||
</table>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</as-split-area>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,9 @@
|
|||
|
||||
.side-pane {
|
||||
position: relative;
|
||||
padding: 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
|
||||
.close-btn {
|
||||
position: absolute;
|
||||
|
|
@ -47,10 +49,13 @@
|
|||
@extend %heading-400;
|
||||
margin: 0;
|
||||
padding-top: 0;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
table {
|
||||
margin-top: 1rem;
|
||||
.scrollable-wrapper {
|
||||
padding: 0 1rem 1rem 1rem;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,8 +7,6 @@ table.ng-table {
|
|||
width: 100%;
|
||||
|
||||
tr {
|
||||
height: 38px;
|
||||
|
||||
th {
|
||||
@extend %body-bold-01;
|
||||
margin: 0;
|
||||
|
|
@ -37,6 +35,6 @@ table.ng-table {
|
|||
border-bottom: 1px solid var(--senary-contrast);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
padding: 0 0.375rem;
|
||||
padding: 0.625rem 0.375rem;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue