diff --git a/front-public/src/app/pages/api-detail/api-detail.component.ts b/front-public/src/app/pages/api-detail/api-detail.component.ts
index 28d5bfd..d0bd0c0 100644
--- a/front-public/src/app/pages/api-detail/api-detail.component.ts
+++ b/front-public/src/app/pages/api-detail/api-detail.component.ts
@@ -14,34 +14,17 @@ import { ReactiveFormsModule, FormBuilder, Validators } from '@angular/forms';
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
import { ApiService } from '../../core/api.service';
import { CategoryService } from '../../core/category.service';
+import { BreadcrumbComponent, BreadcrumbItem } from '../../shared/breadcrumb/breadcrumb.component';
import { ApiEntry, ApiEntryListItem, Category } from '@datacat/shared';
@Component({
selector: 'app-api-detail',
standalone: true,
- imports: [CommonModule, RouterLink, ReactiveFormsModule],
+ imports: [CommonModule, RouterLink, ReactiveFormsModule, BreadcrumbComponent],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
-
-
+
@if (loading()) {
@@ -436,6 +419,15 @@ export class ApiDetailComponent implements OnInit, OnDestroy {
return ['/browse'];
});
+ breadcrumbItems = computed
(() => {
+ const crumbs = this.categoryBreadcrumb();
+ const items: BreadcrumbItem[] = [{ label: 'Accueil', link: '/' }];
+ if (crumbs.length === 0) items.push({ label: 'Parcourir', link: '/browse' });
+ for (const crumb of crumbs) items.push({ label: crumb.name, link: ['/browse', crumb.id] });
+ items.push({ label: this.api()?.title ?? 'Chargement...' });
+ return items;
+ });
+
/* Onglets */
activeTab = signal<'info' | 'contacts'>('info');
editingInfo = signal(false);
diff --git a/front-public/src/app/pages/browse/browse.component.ts b/front-public/src/app/pages/browse/browse.component.ts
index 8434a1b..6cd967d 100644
--- a/front-public/src/app/pages/browse/browse.component.ts
+++ b/front-public/src/app/pages/browse/browse.component.ts
@@ -17,32 +17,16 @@ import { CategoryBrowseResponse, Category, CategoryWithCounts, ApiEntryListItem
import { forkJoin } from 'rxjs';
import { CategoryFormModalComponent } from '../../shared/category-form-modal/category-form-modal.component';
import { CategoryDeleteModalComponent } from '../../shared/category-delete-modal/category-delete-modal.component';
+import { BreadcrumbComponent, BreadcrumbItem } from '../../shared/breadcrumb/breadcrumb.component';
@Component({
selector: 'app-browse',
standalone: true,
- imports: [CommonModule, RouterLink, CategoryFormModalComponent, CategoryDeleteModalComponent],
+ imports: [CommonModule, RouterLink, CategoryFormModalComponent, CategoryDeleteModalComponent, BreadcrumbComponent],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
-
-
+
@if (categoryId()) {
@@ -309,6 +293,16 @@ export class BrowseComponent implements OnInit {
return d.breadcrumb.slice(0, -1);
});
+ breadcrumbItems = computed
(() => {
+ const items: BreadcrumbItem[] = [{ label: 'Accueil', link: '/' }];
+ for (const crumb of this.ancestors()) {
+ items.push({ label: crumb.name, link: ['/browse', crumb.id] });
+ }
+ const current = this.browseData()?.category;
+ if (current) items.push({ label: current.name });
+ return items;
+ });
+
backUrl = computed(() => {
const anc = this.ancestors();
if (anc.length > 0) return ['/browse', anc[anc.length - 1].id];
diff --git a/front-public/src/app/pages/upload/upload.component.ts b/front-public/src/app/pages/upload/upload.component.ts
index 97cb840..e1b7eb1 100644
--- a/front-public/src/app/pages/upload/upload.component.ts
+++ b/front-public/src/app/pages/upload/upload.component.ts
@@ -13,23 +13,17 @@ import { ReactiveFormsModule, FormBuilder, Validators } from '@angular/forms';
import { Router, RouterLink, ActivatedRoute } from '@angular/router';
import { ApiService } from '../../core/api.service';
import { CategoryService } from '../../core/category.service';
+import { BreadcrumbComponent, BreadcrumbItem } from '../../shared/breadcrumb/breadcrumb.component';
import { ApiEntry, ApiEntryListItem, Category, API_CONVENTION_LABELS, ApiConvention } from '@datacat/shared';
@Component({
selector: 'app-upload',
standalone: true,
- imports: [CommonModule, ReactiveFormsModule, RouterLink],
+ imports: [CommonModule, ReactiveFormsModule, RouterLink, BreadcrumbComponent],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
-
-
+
Importer une API
@@ -511,6 +505,12 @@ export class UploadComponent implements OnInit {
private destroyRef = inject(DestroyRef);
step = signal(1);
+ readonly breadcrumbItems: BreadcrumbItem[] = [
+ { label: 'Parcourir', link: '/browse' },
+ { label: 'Catalogue', link: '/catalog' },
+ { label: 'Importer une API' },
+ ];
+
private readonly maxFileSize = 5 * 1024 * 1024; // 5 Mo
selectedFiles = signal
([]);
detectedType = signal<'ASYNCAPI' | 'OPENAPI' | null>(null);
diff --git a/front-public/src/app/shared/breadcrumb/breadcrumb.component.ts b/front-public/src/app/shared/breadcrumb/breadcrumb.component.ts
new file mode 100644
index 0000000..17a854d
--- /dev/null
+++ b/front-public/src/app/shared/breadcrumb/breadcrumb.component.ts
@@ -0,0 +1,37 @@
+import { ChangeDetectionStrategy, Component, input } from '@angular/core';
+import { RouterLink } from '@angular/router';
+
+/** Un maillon du fil d'Ariane. `link` absent => élément courant (aria-current). */
+export interface BreadcrumbItem {
+ label: string;
+ link?: string | unknown[];
+}
+
+/**
+ * Fil d'Ariane DSFR générique (présentationnel). Chaque page construit sa liste
+ * d'items et la passe ; le dernier item sans `link` est marqué comme courant.
+ */
+@Component({
+ selector: 'app-breadcrumb',
+ standalone: true,
+ imports: [RouterLink],
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ template: `
+
+ `,
+})
+export class BreadcrumbComponent {
+ items = input.required();
+}