Skip to content

Commit 914c3d0

Browse files
feat: Integrate Locations tree as description-tab section with TOC entry
- Move LocationsTree into the description-tab as a proper section with divider header and TOC entry ("Locations in the Pathway Browser") - Add species filtering, expand/collapse all, tree connector lines, type icons, and arrow expand icons matching site design language - Add pathway-browser SVG icon assets to WebsiteAngular build config so entity icons (protein, pathway, etc.) render correctly - Clean up detail component to pass showLocations flag instead of rendering LocationsTree standalone Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 23bf408 commit 914c3d0

8 files changed

Lines changed: 291 additions & 125 deletions

File tree

angular.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,11 @@
152152
"glob": "**/*",
153153
"input": "projects/website-angular/src/assets",
154154
"output": "/assets"
155+
},
156+
{
157+
"glob": "**/*",
158+
"input": "projects/pathway-browser/src/assets",
159+
"output": "/assets"
155160
}
156161
],
157162
"styles": ["projects/website-angular/src/styles.scss"],

projects/pathway-browser/src/app/details/tabs/description-tab/description-tab.component.html

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,3 +281,9 @@
281281
</div>
282282
</ng-template>
283283

284+
<ng-template #locationsTemplate>
285+
<div class="element-container">
286+
<app-locations-tree [id]="obj().stId" />
287+
</div>
288+
</ng-template>
289+

projects/pathway-browser/src/app/details/tabs/description-tab/description-tab.component.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ import {CellMarkerComponent} from "../../common/cell-marker/cell-marker.componen
7474
import {IconComponent} from "./icon/icon.component";
7575
import {RheaComponent} from "../../common/rhea/rhea.component";
7676
import {InteractorsTableComponent} from "../../common/interactors-table/interactors-table.component";
77+
import {
78+
LocationsTreeComponent
79+
} from "../../../../../../website-angular/src/app/content/detail/locations-tree/locations-tree.component";
7780

7881

7982
@Component({
@@ -106,7 +109,8 @@ import {InteractorsTableComponent} from "../../common/interactors-table/interact
106109
CellMarkerComponent,
107110
IconComponent,
108111
RheaComponent,
109-
InteractorsTableComponent
112+
InteractorsTableComponent,
113+
LocationsTreeComponent
110114
]
111115
})
112116
export class DescriptionTabComponent implements OnDestroy {
@@ -138,6 +142,7 @@ export class DescriptionTabComponent implements OnDestroy {
138142

139143
readonly obj = input.required<SelectableObject>();
140144
readonly analysisResult = input<Analysis.Result>();
145+
readonly showLocations = input(false);
141146

142147
static referenceTypeToNameSuffix = new Map<string, string>([
143148
["ReferenceMolecule", ""],
@@ -274,6 +279,7 @@ export class DescriptionTabComponent implements OnDestroy {
274279
authorsTemplate$ = viewChild.required<TemplateRef<any>>('authorsTemplate');
275280
interactorsTemplate$ = viewChild.required<TemplateRef<any>>('interactorsTemplate');
276281
rheaTemplate$ = viewChild.required<TemplateRef<any>>('rheaTemplate');
282+
locationsTemplate$ = viewChild<TemplateRef<any>>('locationsTemplate');
277283

278284
protected readonly Labels = Labels;
279285
protected readonly DataKeys = DataKeys;
@@ -301,6 +307,13 @@ export class DescriptionTabComponent implements OnDestroy {
301307
template: this.overviewTemplate$,
302308
isPresent: signal(true)
303309
},
310+
{
311+
key: 'locationsInPWB',
312+
label: 'Locations in the Pathway Browser',
313+
manual: true,
314+
template: this.locationsTemplate$ as Signal<TemplateRef<any>>,
315+
isPresent: computed(() => this.showLocations())
316+
},
304317
{key: DataKeys.REFERENCE_ENTITY, label: Labels.EXTERNAL_REFERENCE, manual: true, template: this.referenceTemplate$},
305318
{key: DataKeys.SUMMARISED_ENTITIES, label: Labels.SUMMARISED_ENTITIES},
306319
{
@@ -451,6 +464,8 @@ export class DescriptionTabComponent implements OnDestroy {
451464
switch (key) {
452465
case DataKeys.OVERVIEW:
453466
return obj;
467+
case 'locationsInPWB':
468+
return this.showLocations();
454469
case DataKeys.PROTEIN_MARKER:
455470
return this.proteinMarkers().length + this.rnaMarkers().length > 0;
456471
case DataKeys.CATALYST_ACTIVITY:

projects/website-angular/src/app/content/detail/detail.component.html

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,7 @@ <h2>Entity not found</h2>
1010
</div>
1111
} @else if (obj()) {
1212
<div class="detail-container">
13-
@if (entityId()) {
14-
<app-locations-tree [id]="entityId()!" />
15-
}
16-
<cr-description-tab [obj]="obj()!" />
13+
<cr-description-tab [obj]="obj()!" [showLocations]="true" />
1714
</div>
1815
}
1916
</app-page-layout>

projects/website-angular/src/app/content/detail/detail.component.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ import {DiagramService} from '../../../../../pathway-browser/src/app/services/di
2020
import {ParticipantService} from '../../../../../pathway-browser/src/app/services/participant.service';
2121
import {IconService} from '../../../../../pathway-browser/src/app/services/icon.service';
2222

23-
import {LocationsTreeComponent} from './locations-tree/locations-tree.component';
2423
import {DetailDataService} from '../../../services/detail-data.service';
2524
import {DetailUrlState} from './providers/detail-url-state.provider';
2625
import {DetailDataState} from './providers/detail-data-state.provider';
@@ -32,7 +31,7 @@ import {DetailSpeciesService} from './providers/detail-species.provider';
3231
@Component({
3332
selector: 'app-detail',
3433
standalone: true,
35-
imports: [PageLayoutComponent, DescriptionTabComponent, LocationsTreeComponent, MatProgressSpinner],
34+
imports: [PageLayoutComponent, DescriptionTabComponent, MatProgressSpinner],
3635
providers: [
3736
{provide: UrlStateService, useClass: DetailUrlState},
3837
{provide: DataStateService, useClass: DetailDataState},
@@ -54,7 +53,6 @@ export class DetailComponent implements OnInit {
5453
private domSanitizer = inject(DomSanitizer);
5554
private iconService = inject(IconService);
5655

57-
entityId = signal<string | null>(null);
5856
obj = signal<SelectableObject | undefined>(undefined);
5957
loading = signal(true);
6058
error = signal(false);
@@ -85,7 +83,6 @@ export class DetailComponent implements OnInit {
8583

8684
ngOnInit() {
8785
const id = this.route.snapshot.paramMap.get('id');
88-
this.entityId.set(id);
8986
if (!id) {
9087
this.loading.set(false);
9188
this.error.set(true);
Lines changed: 72 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,85 @@
11
@if (trees().length) {
2-
<div class="locations-section">
3-
<div class="header">
4-
<div class="label">
5-
<span class="name">Locations in the PathwayBrowser</span>
2+
<!-- Toolbar: species selector + expand/collapse -->
3+
<div class="toolbar">
4+
@if (availableSpecies().length > 1) {
5+
<div class="species-filter">
6+
<span class="species-label">Species:</span>
7+
<select
8+
class="species-select"
9+
[value]="selectedSpecies()"
10+
(change)="onSpeciesChange($event)"
11+
>
12+
@for (species of availableSpecies(); track species) {
13+
<option [value]="species">{{ species }}</option>
14+
}
15+
</select>
616
</div>
7-
<button class="expand-all-btn" (click)="toggleAll()">
8-
{{ allExpanded() ? 'Collapse all' : 'Expand all' }}
9-
</button>
10-
</div>
17+
}
18+
<button class="expand-all-btn" (click)="toggleAll()">
19+
{{ allExpanded() ? 'Collapse all' : 'Expand all' }}
20+
</button>
21+
</div>
1122

12-
<div class="tree-list">
13-
@for (root of trees(); track nodeKey(root)) {
14-
<ng-container
15-
*ngTemplateOutlet="treeNode; context: { $implicit: root, level: 0 }"
16-
/>
17-
}
18-
</div>
23+
<!-- Tree roots -->
24+
<div class="tree-roots">
25+
@for (root of filteredTrees(); track root.stId) {
26+
<div class="root-entry">
27+
@if (hasChildren(root)) {
28+
<!-- Expandable root -->
29+
<button class="root-toggle" (click)="toggleRoot(root.stId)">
30+
<mat-icon class="arrow-icon" [svgIcon]="isExpanded(root.stId) ? 'arrow-down' : 'arrow-right'"></mat-icon>
31+
<mat-icon class="type-icon" [svgIcon]="getIconName(root.type)"></mat-icon>
32+
<span class="root-name">{{ root.name }}</span>
33+
@if (root.species && availableSpecies().length <= 1) {
34+
<span class="node-species">({{ root.species }})</span>
35+
}
36+
</button>
37+
<!-- Subtree -->
38+
@if (isExpanded(root.stId)) {
39+
<div class="subtree-container">
40+
<ul class="tree">
41+
@for (child of root.children; track child.url || child.stId; let last = $last) {
42+
<ng-container
43+
*ngTemplateOutlet="nodeTemplate; context: { $implicit: child, siblings: root.children!, isLast: last }"
44+
/>
45+
}
46+
</ul>
47+
</div>
48+
}
49+
} @else {
50+
<!-- Leaf root (no children) — direct link -->
51+
<div class="root-leaf">
52+
<mat-icon class="type-icon" [svgIcon]="getIconName(root.type)"></mat-icon>
53+
<a class="leaf-link" [href]="root.url">{{ root.name }}</a>
54+
@if (root.species && availableSpecies().length <= 1) {
55+
<span class="node-species">({{ root.species }})</span>
56+
}
57+
</div>
58+
}
59+
</div>
60+
}
1961
</div>
2062
}
2163

22-
<ng-template #treeNode let-node let-level="level">
23-
<div class="tree-node" [style.padding-left.px]="level * 20">
64+
<!-- Recursive node template -->
65+
<ng-template #nodeTemplate let-node let-siblings="siblings" let-isLast="isLast">
66+
<li [class.last]="isLast">
2467
<div class="node-row">
25-
@if (hasChildren(node)) {
26-
<button class="toggle-btn" (click)="toggleNode(nodeKey(node))">
27-
<mat-icon class="expand-icon" fontSet="symbols">
28-
{{ isExpanded(nodeKey(node)) ? 'expand_more' : 'chevron_right' }}
29-
</mat-icon>
30-
</button>
31-
} @else {
32-
<span class="toggle-placeholder"></span>
33-
}
3468
<mat-icon class="type-icon" [svgIcon]="getIconName(node.type)"></mat-icon>
35-
@if (hasChildren(node)) {
36-
<span class="node-name">{{ node.name }}</span>
69+
@if (node.url && !hasChildren(node)) {
70+
<a class="leaf-link" [href]="node.url">{{ node.name }}</a>
3771
} @else {
38-
<a class="node-name leaf-link" [href]="node.url">{{ node.name }}</a>
39-
}
40-
@if (node.species) {
41-
<span class="node-species">({{ node.species }})</span>
72+
<span class="node-name">{{ node.name }}</span>
4273
}
4374
</div>
44-
@if (hasChildren(node) && isExpanded(nodeKey(node))) {
45-
@for (child of node.children; track nodeKey(child)) {
46-
<ng-container
47-
*ngTemplateOutlet="treeNode; context: { $implicit: child, level: level + 1 }"
48-
/>
49-
}
75+
@if (hasChildren(node)) {
76+
<ul class="tree">
77+
@for (child of node.children; track child.url || child.stId; let childLast = $last) {
78+
<ng-container
79+
*ngTemplateOutlet="nodeTemplate; context: { $implicit: child, siblings: node.children!, isLast: childLast }"
80+
/>
81+
}
82+
</ul>
5083
}
51-
</div>
84+
</li>
5285
</ng-template>

0 commit comments

Comments
 (0)