Skip to content

Commit 6ac86fd

Browse files
committed
front: adds global nodes management panel
This commit addresses issue: OpenRailAssociation/osrd#13292 Details: - Adds new component GlobalNodesManagementComponent in app/view/editor-edit-tools-view-component - The new component controls the isCollapsed state of the nodes - Adds EN and FR translations for the new component - Adds the new component to the existing EditorEditToolsViewComponent panel
1 parent 5422f1a commit 6ac86fd

File tree

7 files changed

+249
-2
lines changed

7 files changed

+249
-2
lines changed

src/app/app.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ import {I18nModule} from "./core/i18n/i18n.module";
107107
import {OriginDestinationComponent} from "./services/analytics/origin-destination/components/origin-destination.component";
108108
import {SbbToggleModule} from "@sbb-esta/angular/toggle";
109109
import {ToggleSwitchButtonComponent} from "./view/toggle-switch-button/toggle-switch-button.component";
110+
import {GlobalNodesManagementComponent} from "./view/editor-edit-tools-view-component/global-nodes-management/global-nodes-management.component";
110111

111112
@NgModule({
112113
declarations: [
@@ -152,6 +153,7 @@ import {ToggleSwitchButtonComponent} from "./view/toggle-switch-button/toggle-sw
152153
NavigationBarComponent,
153154
EditorPropertiesViewComponent,
154155
EditorEditToolsViewComponent,
156+
GlobalNodesManagementComponent,
155157
FilterableLabelDialogComponent,
156158
FilterableLabelFormComponent,
157159
NoteDialogComponent,

src/app/view/editor-edit-tools-view-component/editor-edit-tools-view.component.html

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@ <h2 class="SummaryTitle">{{ "app.view.editor-edit-tools-view-component.edit" | t
2929
</sbb-accordion>
3030
</sbb-expansion-panel>
3131

32+
<sbb-expansion-panel [expanded]="false" [hideToggle]="false" [disabled]="!getVariantIsWritable()">
33+
<sbb-expansion-panel-header>{{
34+
"app.view.editor-edit-tools-view-component.nodes" | translate
35+
}}</sbb-expansion-panel-header>
36+
<sbb-global-nodes-management />
37+
</sbb-expansion-panel>
38+
3239
<sbb-expansion-panel
3340
[expanded]="getVariantIsWritable() && !getAreMultiObjectSelected()"
3441
[disabled]="!getVariantIsWritable()"
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<section class="global-nodes-management">
2+
<div class="search">
3+
<input
4+
#globalNodesManagementQuery
5+
sbbInput
6+
type="search"
7+
[value]="query"
8+
(input)="updateState({query: globalNodesManagementQuery.value})"
9+
[placeholder]="
10+
'app.view.editor-edit-tools-view-component.nodes-search-placeholder' | translate
11+
"
12+
/>
13+
</div>
14+
<div class="nodes-list">
15+
@if (matchingNodes.length) {
16+
<table>
17+
<thead>
18+
<tr>
19+
@for (column of ["nodes-expanded", "nodes"]; track column) {
20+
<th scope="col">
21+
{{ "app.view.editor-edit-tools-view-component." + column | translate }}
22+
</th>
23+
}
24+
</tr>
25+
@if (matchingNodes.length > 1) {
26+
<tr>
27+
<td>
28+
<sbb-checkbox
29+
[checked]="!!getGlobalCheckboxStatus()"
30+
[indeterminate]="getGlobalCheckboxStatus() === undefined"
31+
(click)="onClickGlobalCheckbox($event)"
32+
/>
33+
</td>
34+
</tr>
35+
}
36+
</thead>
37+
<tbody>
38+
@for (node of matchingNodes; track node.getId()) {
39+
<tr>
40+
<td class="offset">
41+
<sbb-checkbox
42+
[checked]="!node.getIsCollapsed()"
43+
(change)="toggleIsCollapsed(node, !$event.checked)"
44+
/>
45+
</td>
46+
<td>{{ node.getBetriebspunktName() }}</td>
47+
<td>{{ node.getFullName() }}</td>
48+
</tr>
49+
}
50+
</tbody>
51+
</table>
52+
} @else {
53+
<p>{{ "app.view.editor-edit-tools-view-component.nodes-no-result" | translate }}</p>
54+
}
55+
</div>
56+
</section>
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
.global-nodes-management {
2+
width: 100%;
3+
display: flex;
4+
flex-direction: column;
5+
gap: 0.5rem;
6+
align-items: stretch;
7+
8+
.search {
9+
position: relative;
10+
11+
input {
12+
width: 100%;
13+
}
14+
.sbb-icon {
15+
position: absolute;
16+
right: 5px;
17+
top: 50%;
18+
transform: translateY(-50%);
19+
}
20+
}
21+
22+
.nodes-list {
23+
overflow-x: auto;
24+
25+
table {
26+
width: 100%;
27+
border-collapse: collapse;
28+
29+
th,
30+
td {
31+
font-family: var(--sbb-font-light);
32+
font-weight: var(--sbb-font-weight-normal);
33+
padding: 8px 0;
34+
white-space: nowrap;
35+
vertical-align: middle;
36+
}
37+
38+
th {
39+
text-align: start;
40+
}
41+
tbody tr {
42+
border-top: var(--sbb-border-width-thin) solid var(--sbb-expansion-panel-border-color-open);
43+
}
44+
td.offset {
45+
padding-left: 10px;
46+
}
47+
tbody td:last-child {
48+
width: 99%;
49+
}
50+
thead th:not(:last-child),
51+
tbody td:not(:last-child) {
52+
padding-right: 13px;
53+
}
54+
55+
.sbb-checkbox {
56+
display: block;
57+
}
58+
}
59+
}
60+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import {Component} from "@angular/core";
2+
3+
import {NodeService} from "../../../services/data/node.service";
4+
import {Node} from "../../../models/node.model";
5+
import {DataService} from "../../../services/data/data.service";
6+
import {UiInteractionService} from "../../../services/ui/ui.interaction.service";
7+
import {ConfirmationDialogParameter} from "../../dialogs/confirmation-dialog/confirmation-dialog.component";
8+
9+
function normalizeStr(str: string): string {
10+
return str
11+
.toLowerCase()
12+
.trim()
13+
.normalize("NFD")
14+
.replace(/[\u0300-\u036f]/g, "");
15+
}
16+
17+
@Component({
18+
selector: "sbb-global-nodes-management",
19+
templateUrl: "./global-nodes-management.component.html",
20+
styleUrl: "./global-nodes-management.component.scss",
21+
})
22+
export class GlobalNodesManagementComponent {
23+
query: string;
24+
allNodes: Node[];
25+
matchingNodes: Node[];
26+
27+
constructor(
28+
private dataService: DataService,
29+
private nodeService: NodeService,
30+
private uiInteractionService: UiInteractionService,
31+
) {
32+
this.query = "";
33+
this.allNodes = this.nodeService.getNodes();
34+
this.nodeService.nodes.subscribe((nodes) => this.updateState({nodes}));
35+
}
36+
37+
updateState({nodes = this.allNodes, query = this.query}: {nodes?: Node[]; query?: string}) {
38+
// Save state locally
39+
this.query = query;
40+
this.allNodes = nodes;
41+
42+
const normalizedQuery = normalizeStr(this.query);
43+
44+
this.matchingNodes = normalizedQuery
45+
? this.allNodes.filter(
46+
(node) =>
47+
normalizeStr(node.getFullName()).includes(normalizedQuery) ||
48+
normalizeStr(node.getBetriebspunktName()).includes(normalizedQuery),
49+
)
50+
: this.allNodes;
51+
}
52+
53+
getGlobalCheckboxStatus(): boolean | undefined {
54+
let allCollapsed = true;
55+
let noneCollapsed = true;
56+
this.matchingNodes.every((node) => {
57+
const isCollapsed = node.getIsCollapsed();
58+
allCollapsed = allCollapsed && isCollapsed;
59+
noneCollapsed = noneCollapsed && !isCollapsed;
60+
61+
// If both allCollapsed and noneCollapsed fail, stop iterating
62+
return allCollapsed || noneCollapsed;
63+
});
64+
65+
if (allCollapsed) return false;
66+
if (noneCollapsed) return true;
67+
return undefined;
68+
}
69+
70+
toggleIsCollapsed(node: Node, isCollapsed: boolean) {
71+
node.setIsCollapsed(isCollapsed);
72+
this.dataService.triggerViewUpdate();
73+
}
74+
onClickGlobalCheckbox(event: MouseEvent) {
75+
event.preventDefault();
76+
event.stopPropagation();
77+
78+
const currentGlobalCheckboxStatus = this.getGlobalCheckboxStatus();
79+
const newCheckboxStatus = !currentGlobalCheckboxStatus;
80+
const newIsCollapsed = !newCheckboxStatus;
81+
82+
const allNodesImpacted = this.allNodes.length === this.matchingNodes.length;
83+
const impactedNodesCount = this.matchingNodes.length;
84+
85+
const dialogTitle = $localize`:@@app.view.editor-edit-tools-view-component.global-nodes-management:Global nodes management`;
86+
const dialogContent = newIsCollapsed
87+
? allNodesImpacted
88+
? $localize`:@@app.view.editor-edit-tools-view-component.confirm-collapse-all:Are you sure you want to collapse all nodes?`
89+
: $localize`:@@app.view.editor-edit-tools-view-component.confirm-collapse-matching:Are you sure you want to collapse the ${impactedNodesCount}:count: nodes matching "${this.query}:query:"?`
90+
: allNodesImpacted
91+
? $localize`:@@app.view.editor-edit-tools-view-component.confirm-expand-all:Are you sure you want to expand all nodes?`
92+
: $localize`:@@app.view.editor-edit-tools-view-component.confirm-expand-matching:Are you sure you want to expand the ${impactedNodesCount}:count: nodes matching "${this.query}:query:"?`;
93+
const confirmationDialogParameter = new ConfirmationDialogParameter(dialogTitle, dialogContent);
94+
95+
this.uiInteractionService
96+
.showConfirmationDiagramDialog(confirmationDialogParameter)
97+
.subscribe((confirmed: boolean) => {
98+
if (confirmed) {
99+
this.matchingNodes.forEach((node) => {
100+
node.setIsCollapsed(newIsCollapsed);
101+
});
102+
this.dataService.triggerViewUpdate();
103+
}
104+
});
105+
}
106+
}

src/assets/i18n/en.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,15 @@
337337
"trainruns": "Trainruns",
338338
"notes": "Notes",
339339
"nodes": "Nodes"
340-
}
340+
},
341+
"nodes-search-placeholder": "Search for names or short names",
342+
"nodes-expanded": "Expanded",
343+
"nodes-no-result": "There is no node matching the query.",
344+
"global-nodes-management": "Global nodes management",
345+
"confirm-expand-all": "Are you sure you want to expand all nodes?",
346+
"confirm-expand-matching": "Are you sure you want to expand the {$count} nodes matching \"{$query}\"?",
347+
"confirm-collapse-all": "Are you sure you want to collapse all nodes?",
348+
"confirm-collapse-matching": "Are you sure you want to collapse the {$count} nodes matching \"{$query}\"?"
341349
},
342350
"editor-filter-view": {
343351
"filter": "Filter",

src/assets/i18n/fr.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,15 @@
336336
"trainruns": "Trajets de train",
337337
"notes": "Notes",
338338
"nodes": "Noeuds"
339-
}
339+
},
340+
"nodes-search-placeholder": "Rechercher par nom ou trigramme",
341+
"nodes-expanded": "Développés",
342+
"nodes-no-result": "Aucun noeud ne correspond à cette recherche.",
343+
"global-nodes-management": "Gestion globale des noeuds",
344+
"confirm-expand-all": "Êtes-vous sûr(e) de vouloir développer tous les noeuds ?",
345+
"confirm-expand-matching": "Êtes-vous sûr(e) de vouloir développer les {$count} noeuds qui contiennent «{$query}» ?",
346+
"confirm-collapse-all": "Êtes-vous sûr(e) de vouloir réduire tous les noeuds ?",
347+
"confirm-collapse-matching": "Êtes-vous sûr(e) de vouloir réduire les {$count} noeuds qui contiennent «{$query}» ?"
340348
},
341349
"editor-filter-view": {
342350
"filter": "Filtres",

0 commit comments

Comments
 (0)