diff --git a/src/app/app.module.ts b/src/app/app.module.ts index aae6d957c..c1f4c46aa 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -107,6 +107,7 @@ import {I18nModule} from "./core/i18n/i18n.module"; import {OriginDestinationComponent} from "./services/analytics/origin-destination/components/origin-destination.component"; import {SbbToggleModule} from "@sbb-esta/angular/toggle"; import {ToggleSwitchButtonComponent} from "./view/toggle-switch-button/toggle-switch-button.component"; +import {GlobalNodesManagementComponent} from "./view/editor-edit-tools-view-component/global-nodes-management/global-nodes-management.component"; @NgModule({ declarations: [ @@ -152,6 +153,7 @@ import {ToggleSwitchButtonComponent} from "./view/toggle-switch-button/toggle-sw NavigationBarComponent, EditorPropertiesViewComponent, EditorEditToolsViewComponent, + GlobalNodesManagementComponent, FilterableLabelDialogComponent, FilterableLabelFormComponent, NoteDialogComponent, diff --git a/src/app/view/editor-edit-tools-view-component/editor-edit-tools-view.component.html b/src/app/view/editor-edit-tools-view-component/editor-edit-tools-view.component.html index b69a0ab74..2eff04cda 100644 --- a/src/app/view/editor-edit-tools-view-component/editor-edit-tools-view.component.html +++ b/src/app/view/editor-edit-tools-view-component/editor-edit-tools-view.component.html @@ -29,6 +29,13 @@

{{ "app.view.editor-edit-tools-view-component.edit" | t + + {{ + "app.view.editor-edit-tools-view-component.nodes" | translate + }} + + + + +
+ @if (matchingNodes.length) { + + + + @for (column of ["nodes-expanded", "nodes"]; track column) { + + } + + @if (matchingNodes.length > 1) { + + + + } + + + @for (node of matchingNodes; track node.getId()) { + + + + + + } + +
+ {{ "app.view.editor-edit-tools-view-component." + column | translate }} +
+ +
+ + {{ node.getBetriebspunktName() }}{{ node.getFullName() }}
+ } @else { +

{{ "app.view.editor-edit-tools-view-component.nodes-no-result" | translate }}

+ } +
+ diff --git a/src/app/view/editor-edit-tools-view-component/global-nodes-management/global-nodes-management.component.scss b/src/app/view/editor-edit-tools-view-component/global-nodes-management/global-nodes-management.component.scss new file mode 100644 index 000000000..202cd1940 --- /dev/null +++ b/src/app/view/editor-edit-tools-view-component/global-nodes-management/global-nodes-management.component.scss @@ -0,0 +1,60 @@ +.global-nodes-management { + width: 100%; + display: flex; + flex-direction: column; + gap: 0.5rem; + align-items: stretch; + + .search { + position: relative; + + input { + width: 100%; + } + .sbb-icon { + position: absolute; + right: 5px; + top: 50%; + transform: translateY(-50%); + } + } + + .nodes-list { + overflow-x: auto; + + table { + width: 100%; + border-collapse: collapse; + + th, + td { + font-family: var(--sbb-font-light); + font-weight: var(--sbb-font-weight-normal); + padding: 8px 0; + white-space: nowrap; + vertical-align: middle; + } + + th { + text-align: start; + } + tbody tr { + border-top: var(--sbb-border-width-thin) solid var(--sbb-expansion-panel-border-color-open); + } + td.offset { + padding-left: 10px; + } + tbody td:last-child { + width: 99%; + } + thead th:not(:last-child), + tbody td:not(:last-child) { + padding-right: 13px; + } + + .sbb-checkbox { + display: block; + } + } + } +} diff --git a/src/app/view/editor-edit-tools-view-component/global-nodes-management/global-nodes-management.component.ts b/src/app/view/editor-edit-tools-view-component/global-nodes-management/global-nodes-management.component.ts new file mode 100644 index 000000000..72e01f8b2 --- /dev/null +++ b/src/app/view/editor-edit-tools-view-component/global-nodes-management/global-nodes-management.component.ts @@ -0,0 +1,106 @@ +import {Component} from "@angular/core"; + +import {NodeService} from "../../../services/data/node.service"; +import {Node} from "../../../models/node.model"; +import {DataService} from "../../../services/data/data.service"; +import {UiInteractionService} from "../../../services/ui/ui.interaction.service"; +import {ConfirmationDialogParameter} from "../../dialogs/confirmation-dialog/confirmation-dialog.component"; + +function normalizeStr(str: string): string { + return str + .toLowerCase() + .trim() + .normalize("NFD") + .replace(/[\u0300-\u036f]/g, ""); +} + +@Component({ + selector: "sbb-global-nodes-management", + templateUrl: "./global-nodes-management.component.html", + styleUrl: "./global-nodes-management.component.scss", +}) +export class GlobalNodesManagementComponent { + query: string; + allNodes: Node[]; + matchingNodes: Node[]; + + constructor( + private dataService: DataService, + private nodeService: NodeService, + private uiInteractionService: UiInteractionService, + ) { + this.query = ""; + this.allNodes = this.nodeService.getNodes(); + this.nodeService.nodes.subscribe((nodes) => this.updateState({nodes})); + } + + updateState({nodes = this.allNodes, query = this.query}: {nodes?: Node[]; query?: string}) { + // Save state locally + this.query = query; + this.allNodes = nodes; + + const normalizedQuery = normalizeStr(this.query); + + this.matchingNodes = normalizedQuery + ? this.allNodes.filter( + (node) => + normalizeStr(node.getFullName()).includes(normalizedQuery) || + normalizeStr(node.getBetriebspunktName()).includes(normalizedQuery), + ) + : this.allNodes; + } + + getGlobalCheckboxStatus(): boolean | undefined { + let allCollapsed = true; + let noneCollapsed = true; + this.matchingNodes.every((node) => { + const isCollapsed = node.getIsCollapsed(); + allCollapsed = allCollapsed && isCollapsed; + noneCollapsed = noneCollapsed && !isCollapsed; + + // If both allCollapsed and noneCollapsed fail, stop iterating + return allCollapsed || noneCollapsed; + }); + + if (allCollapsed) return false; + if (noneCollapsed) return true; + return undefined; + } + + toggleIsCollapsed(node: Node, isCollapsed: boolean) { + node.setIsCollapsed(isCollapsed); + this.dataService.triggerViewUpdate(); + } + onClickGlobalCheckbox(event: MouseEvent) { + event.preventDefault(); + event.stopPropagation(); + + const currentGlobalCheckboxStatus = this.getGlobalCheckboxStatus(); + const newCheckboxStatus = !currentGlobalCheckboxStatus; + const newIsCollapsed = !newCheckboxStatus; + + const allNodesImpacted = this.allNodes.length === this.matchingNodes.length; + const impactedNodesCount = this.matchingNodes.length; + + const dialogTitle = $localize`:@@app.view.editor-edit-tools-view-component.global-nodes-management:Global nodes management`; + const dialogContent = newIsCollapsed + ? allNodesImpacted + ? $localize`:@@app.view.editor-edit-tools-view-component.confirm-collapse-all:Are you sure you want to collapse all nodes?` + : $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:"?` + : allNodesImpacted + ? $localize`:@@app.view.editor-edit-tools-view-component.confirm-expand-all:Are you sure you want to expand all nodes?` + : $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:"?`; + const confirmationDialogParameter = new ConfirmationDialogParameter(dialogTitle, dialogContent); + + this.uiInteractionService + .showConfirmationDiagramDialog(confirmationDialogParameter) + .subscribe((confirmed: boolean) => { + if (confirmed) { + this.matchingNodes.forEach((node) => { + node.setIsCollapsed(newIsCollapsed); + }); + this.dataService.triggerViewUpdate(); + } + }); + } +} diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 12223bbc5..63fd0221b 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -337,7 +337,15 @@ "trainruns": "Trainruns", "notes": "Notes", "nodes": "Nodes" - } + }, + "nodes-search-placeholder": "Search for names or short names", + "nodes-expanded": "Expanded", + "nodes-no-result": "There is no node matching the query.", + "global-nodes-management": "Global nodes management", + "confirm-expand-all": "Are you sure you want to expand all nodes?", + "confirm-expand-matching": "Are you sure you want to expand the {$count} nodes matching \"{$query}\"?", + "confirm-collapse-all": "Are you sure you want to collapse all nodes?", + "confirm-collapse-matching": "Are you sure you want to collapse the {$count} nodes matching \"{$query}\"?" }, "editor-filter-view": { "filter": "Filter", diff --git a/src/assets/i18n/fr.json b/src/assets/i18n/fr.json index b6d1979be..4b5fb98fb 100644 --- a/src/assets/i18n/fr.json +++ b/src/assets/i18n/fr.json @@ -336,7 +336,15 @@ "trainruns": "Trajets de train", "notes": "Notes", "nodes": "Noeuds" - } + }, + "nodes-search-placeholder": "Rechercher par nom ou trigramme", + "nodes-expanded": "Développés", + "nodes-no-result": "Aucun noeud ne correspond à cette recherche.", + "global-nodes-management": "Gestion globale des noeuds", + "confirm-expand-all": "Êtes-vous sûr(e) de vouloir développer tous les noeuds ?", + "confirm-expand-matching": "Êtes-vous sûr(e) de vouloir développer les {$count} noeuds qui contiennent «{$query}» ?", + "confirm-collapse-all": "Êtes-vous sûr(e) de vouloir réduire tous les noeuds ?", + "confirm-collapse-matching": "Êtes-vous sûr(e) de vouloir réduire les {$count} noeuds qui contiennent «{$query}» ?" }, "editor-filter-view": { "filter": "Filtres",