Skip to content

Commit b4a6c99

Browse files
authored
Merge pull request #66 from reactome/details-page-ui
Details page UI
2 parents 4f8fd67 + a0b545a commit b4a6c99

5 files changed

Lines changed: 301 additions & 190 deletions

File tree

Lines changed: 116 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,117 +1,144 @@
11
<app-page-layout [showSidebar]="false" [showBreadcrumb]="false">
22
@if (loading()) {
3-
<div class="loading-container">
4-
<mat-spinner diameter="48"></mat-spinner>
5-
</div>
3+
<div class="loading-container">
4+
<mat-spinner diameter="48"></mat-spinner>
5+
</div>
66
} @else if (error()) {
7-
<div class="error-container">
8-
<h2>Icon not found</h2>
9-
<p>The requested icon could not be found.</p>
10-
</div>
7+
<div class="error-container">
8+
<h2>Icon not found</h2>
9+
<p>The requested icon could not be found.</p>
10+
</div>
1111
} @else if (icon()) {
12-
<div class="icon-detail">
13-
<app-tile variant="light">
14-
<div class="icon-header">
15-
<div class="icon-info">
16-
<h1>{{ icon()!.name }}</h1>
12+
<div class="icon-detail">
13+
<app-tile variant="light">
14+
<div class="icon-summary">
15+
<h1>{{ icon()!.name }}</h1>
16+
<dl class="summary-fields">
17+
<div class="summary-row">
18+
<dt>Preview</dt>
19+
<dd>
20+
<a class="download-link" [href]="getPngUrl()" download="{{ icon()!.name }}.png" title="Click to download PNG">
21+
<span class="material-symbols-rounded">download</span>
22+
PNG
23+
</a>
24+
<a class="download-link" [href]="getSvgUrl()" download="{{ icon()!.name }}.svg" title="Click to download SVG">
25+
<span class="material-symbols-rounded">download</span>
26+
SVG
27+
</a>
28+
</dd>
29+
<div class="icon-preview">
30+
<img [src]="getSvgUrl()" [alt]="icon()!.name" />
31+
</div>
32+
</div>
33+
<div class="summary-row">
1734
@if (icon()!.iconCategories?.length) {
18-
<div class="icon-categories">
19-
@for (cat of icon()!.iconCategories; track cat) {
20-
<span class="category-tag">{{ cat }}</span>
21-
}
22-
</div>
35+
<dt>Categories</dt>
36+
<dd>
37+
@for (cat of icon()!.iconCategories; track cat) {
38+
<span>{{ cat }}</span>
39+
}
40+
</dd>
2341
}
2442
</div>
25-
<div class="icon-preview">
26-
<img [src]="getSvgUrl()" [alt]="icon()!.name" />
43+
<div class="summary-row">
44+
@if (icon()!.summation) {
45+
<dt>Description</dt>
46+
<dd [innerHTML]="icon()!.summation"></dd>
47+
}
2748
</div>
28-
</div>
29-
</app-tile>
30-
31-
@if (icon()!.summation) {
32-
<app-tile variant="light">
33-
<h2>Description</h2>
34-
<p [innerHTML]="icon()!.summation"></p>
35-
</app-tile>
36-
}
37-
38-
@if (icon()!.iconCuratorName || icon()!.iconDesignerName) {
39-
<app-tile variant="light">
40-
<h2>Credits</h2>
41-
<div class="credits">
49+
<div class="summary-row">
4250
@if (icon()!.iconCuratorName) {
43-
<div class="credit-item">
44-
<span class="credit-label">Curator:</span>
45-
@if (icon()!.iconCuratorOrcidId) {
46-
<a [href]="'https://orcid.org/' + icon()!.iconCuratorOrcidId" target="_blank" rel="noopener">
47-
{{ icon()!.iconCuratorName }}
48-
<span class="material-symbols-rounded">open_in_new</span>
49-
</a>
50-
} @else {
51-
<span>{{ icon()!.iconCuratorName }}</span>
52-
}
53-
</div>
51+
<div class="credit-item">
52+
<span class="credit-label">Curator</span>
53+
@if (icon()!.iconCuratorOrcidId) {
54+
<a
55+
[href]="'https://orcid.org/' + icon()!.iconCuratorOrcidId"
56+
target="_blank"
57+
rel="noopener"
58+
>
59+
{{ icon()!.iconCuratorName }}
60+
<span class="material-symbols-rounded">open_in_new</span>
61+
</a>
62+
} @else {
63+
<span>{{ icon()!.iconCuratorName }}</span>
64+
}
65+
</div>
5466
}
67+
</div>
68+
<div class="summary-row">
5569
@if (icon()!.iconDesignerName) {
56-
<div class="credit-item">
57-
<span class="credit-label">Designer:</span>
58-
@if (icon()!.iconDesignerUrl) {
59-
<a [href]="icon()!.iconDesignerUrl" target="_blank" rel="noopener">
60-
{{ icon()!.iconDesignerName }}
61-
<span class="material-symbols-rounded">open_in_new</span>
62-
</a>
63-
} @else {
64-
<span>{{ icon()!.iconDesignerName }}</span>
65-
}
66-
</div>
70+
<div class="credit-item">
71+
<span class="credit-label">Designer</span>
72+
@if (icon()!.iconDesignerUrl) {
73+
<a
74+
[href]="icon()!.iconDesignerUrl"
75+
target="_blank"
76+
rel="noopener"
77+
>
78+
{{ icon()!.iconDesignerName }}
79+
<span class="material-symbols-rounded">open_in_new</span>
80+
</a>
81+
} @else {
82+
<span>{{ icon()!.iconDesignerName }}</span>
83+
}
84+
</div>
6785
}
6886
</div>
69-
</app-tile>
70-
}
71-
72-
@if (getUniProtReferences().length) {
73-
<app-tile variant="light">
74-
<h2>External References</h2>
75-
<div class="references">
76-
@for (ref of getUniProtReferences(); track ref) {
77-
<a [href]="'https://www.uniprot.org/uniprot/' + ref" target="_blank" rel="noopener" class="ref-link">
87+
<div class="summary-row">
88+
@if (getUniProtReferences().length) {
89+
<dt>External References</dt>
90+
<div class="references">
91+
@for (ref of getUniProtReferences(); track ref) {
92+
<a
93+
[href]="'https://www.uniprot.org/uniprot/' + ref"
94+
target="_blank"
95+
rel="noopener"
96+
class="ref-link"
97+
>
7898
UniProt: {{ ref }}
7999
<span class="material-symbols-rounded">open_in_new</span>
80100
</a>
101+
}
102+
</div>
81103
}
82104
</div>
83-
</app-tile>
84-
}
85-
86-
@if (icon()!.iconPhysicalEntities?.length) {
87-
<app-tile variant="light">
88-
<h2>Physical Entities ({{ icon()!.iconPhysicalEntities.length }})</h2>
89-
<div class="table-wrapper">
90-
<table class="entities-table">
91-
<thead>
92-
<tr>
93-
<th>Name</th>
94-
<th>Identifier</th>
95-
<th>Type</th>
96-
<th>Compartment</th>
97-
</tr>
98-
</thead>
99-
<tbody>
100-
@for (entity of icon()!.iconPhysicalEntities; track entity.stId) {
105+
<div class="summary-row">
106+
@if (icon()!.iconPhysicalEntities?.length) {
107+
<dt>
108+
Physical Entities ({{ icon()!.iconPhysicalEntities.length }})
109+
</dt>
110+
<div class="table-wrapper">
111+
<table class="entities-table">
112+
<thead>
113+
<tr>
114+
<th>Name</th>
115+
<th>Identifier</th>
116+
<th>Type</th>
117+
<th>Compartment</th>
118+
</tr>
119+
</thead>
120+
<tbody>
121+
@for (entity of icon()!.iconPhysicalEntities; track
122+
entity.stId) {
101123
<tr>
102124
<td>
103-
<a [routerLink]="'/content/detail/' + entity.stId">{{ entity.displayName || entity.name }}</a>
125+
<a [routerLink]="'/content/detail/' + entity.stId">{{
126+
entity.displayName || entity.name
127+
}}</a>
104128
</td>
105129
<td>{{ entity.stId }}</td>
106130
<td>{{ entity.type }}</td>
107131
<td>{{ entity.compartments }}</td>
108132
</tr>
109-
}
110-
</tbody>
111-
</table>
133+
}
134+
</tbody>
135+
</table>
136+
</div>
137+
}
112138
</div>
113-
</app-tile>
114-
}
115-
</div>
139+
</dl>
140+
</div>
141+
</app-tile>
142+
</div>
116143
}
117144
</app-page-layout>

projects/website-angular/src/app/content/detail/icon-detail/icon-detail.component.scss

Lines changed: 80 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,84 @@
3232
gap: 16px;
3333
}
3434

35+
.icon-summary {
36+
width: 100%;
37+
margin: 16px;
38+
39+
h1 {
40+
border-bottom: 1px solid var(--primary);
41+
padding: 0 0 16px;
42+
font-size: 1.5rem;
43+
44+
a {
45+
color: var(--primary);
46+
text-decoration: none;
47+
48+
&:hover {
49+
text-decoration: underline;
50+
}
51+
}
52+
}
53+
54+
.summary-fields {
55+
margin: 0;
56+
57+
.summary-row {
58+
display: flex;
59+
gap: 12px;
60+
padding: 6px 0;
61+
border-bottom: 1px solid var(--outline, #eee);
62+
63+
.download-link {
64+
display: inline-flex;
65+
align-items: center;
66+
gap: 4px;
67+
color: var(--primary);
68+
text-decoration: none;
69+
margin-right: 16px;
70+
71+
&:hover {
72+
text-decoration: underline;
73+
}
74+
75+
.material-symbols-rounded {
76+
font-size: 16px;
77+
}
78+
}
79+
80+
.icon-preview {
81+
display: flex;
82+
flex: 1;
83+
align-items: center;
84+
justify-content: center;
85+
padding: 12px;
86+
87+
img {
88+
height: 150px;
89+
width: auto;
90+
max-width: 100%;
91+
max-height: 100%;
92+
object-fit: contain;
93+
}
94+
}
95+
96+
&:last-child {
97+
border-bottom: none;
98+
}
99+
100+
dt {
101+
font-weight: 600;
102+
min-width: 100px;
103+
flex-shrink: 0;
104+
}
105+
106+
dd {
107+
margin: 0;
108+
}
109+
}
110+
}
111+
}
112+
35113
.icon-header {
36114
display: flex;
37115
align-items: flex-start;
@@ -48,25 +126,6 @@
48126
font-size: 1.5rem;
49127
}
50128
}
51-
52-
.icon-preview {
53-
flex-shrink: 0;
54-
width: 120px;
55-
height: 120px;
56-
display: flex;
57-
align-items: center;
58-
justify-content: center;
59-
border: 1px solid var(--outline, #ddd);
60-
border-radius: 8px;
61-
padding: 12px;
62-
background: white;
63-
64-
img {
65-
max-width: 100%;
66-
max-height: 100%;
67-
object-fit: contain;
68-
}
69-
}
70129
}
71130

72131
.icon-categories {
@@ -151,7 +210,8 @@ h2 {
151210
width: 100%;
152211
border-collapse: collapse;
153212

154-
th, td {
213+
th,
214+
td {
155215
padding: 10px 14px;
156216
text-align: left;
157217
border-bottom: 1px solid var(--outline, #ddd);

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

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import {Component, inject, OnInit, signal} from '@angular/core';
2-
import {ActivatedRoute, RouterLink} from '@angular/router';
3-
import {MatProgressSpinner} from '@angular/material/progress-spinner';
4-
import {PageLayoutComponent} from '../../../page-layout/page-layout.component';
5-
import {TileComponent} from '../../../reactome-components/tile/tile.component';
6-
import {IconService, IconEntry} from '../../../../services/icon.service';
1+
import { Component, inject, OnInit, signal } from '@angular/core';
2+
import { ActivatedRoute, RouterLink } from '@angular/router';
3+
import { MatProgressSpinner } from '@angular/material/progress-spinner';
4+
import { PageLayoutComponent } from '../../../page-layout/page-layout.component';
5+
import { TileComponent } from '../../../reactome-components/tile/tile.component';
6+
import { IconService, IconEntry } from '../../../../services/icon.service';
77

88
@Component({
99
selector: 'app-icon-detail',
@@ -45,8 +45,13 @@ export class IconDetailComponent implements OnInit {
4545
return stId ? `https://reactome.org/icon/${stId}.svg` : '';
4646
}
4747

48+
getPngUrl(): string {
49+
const stId = this.icon()?.stId;
50+
return stId ? `https://reactome.org/icon/${stId}.png` : '';
51+
}
52+
4853
getUniProtReferences(): string[] {
4954
const refs = this.icon()?.iconReferences ?? [];
50-
return refs.filter(r => !r.includes(':'));
55+
return refs.filter((r) => !r.includes(':'));
5156
}
5257
}

0 commit comments

Comments
 (0)