-
Notifications
You must be signed in to change notification settings - Fork 1
feat(arc): implement breadcrumb feature #128
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
export const USERS = [ | ||
{id: '123', name: 'John Doe', email: 'john.doe123@example.com'}, | ||
{id: '124', name: 'Jane Smith', email: 'jane.smith124@example.com'}, | ||
]; | ||
export const TITLES = [ | ||
{id: '1', title: 'Contract.pdf'}, | ||
{id: '2', title: 'Appointment.pdf'}, | ||
]; | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
export interface User { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. user model already exists in the library There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done mam,i changed the name |
||
id: string; | ||
name: string; | ||
email: string; | ||
} | ||
export interface Title { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. use name id model in library core |
||
id: string; | ||
title: string; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
<div class="container mt-4"> | ||
<h2>Documentation</h2> | ||
<p><strong>ID:</strong>{{ title?.id }}</p> | ||
<p><strong>Title:</strong>{{ title?.title }}</p> | ||
</div> |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,14 @@ | ||||||
import {Component} from '@angular/core'; | ||||||
import {ActivatedRoute} from '@angular/router'; | ||||||
import {Title} from '../user-title.interface'; | ||||||
|
||||||
@Component({ | ||||||
selector: 'lib-user-title', | ||||||
templateUrl: './user-title.component.html', | ||||||
}) | ||||||
export class UserTitleComponent { | ||||||
title: Title; | ||||||
constructor(private readonly route: ActivatedRoute) { | ||||||
this.title = this.route.snapshot.data['document']; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The component is accessing 'document' from route data while the resolver likely provides the title data under a different key (e.g., 'title'). Please ensure the data key used in the resolver and the component are consistent.
Suggested change
Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback |
||||||
} | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import {Injectable} from '@angular/core'; | ||
import {ActivatedRouteSnapshot} from '@angular/router'; | ||
import {Observable} from 'rxjs'; | ||
import {TitleService} from './user-title.service'; | ||
import {Title} from '../user-title.interface'; | ||
|
||
@Injectable() | ||
export class TitleResolver { | ||
constructor(private readonly titleService: TitleService) {} | ||
|
||
resolve(route: ActivatedRouteSnapshot): Observable<Title> { | ||
const id = route.paramMap.get('id'); | ||
return this.titleService.getTitleById(id); | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. use linter |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import {Injectable} from '@angular/core'; | ||
import {Observable, of} from 'rxjs'; | ||
import {TITLES} from '../mock-data.constants'; | ||
|
||
@Injectable() | ||
export class TitleService { | ||
private readonly titles = TITLES; | ||
|
||
getTitleById(id: string): Observable<any> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. remove any There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done ! |
||
const title = this.titles.find(u => u.id === id); | ||
return of(title); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
<div class="container mt-4"> | ||
<h2>User Details</h2> | ||
<p><strong>ID:</strong> {{ user?.id }}</p> | ||
<p><strong>Name:</strong> {{ user?.name }}</p> | ||
<p><strong>Email:</strong> {{ user?.email }}</p> | ||
|
||
<router-outlet></router-outlet> | ||
</div> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import {CommonModule} from '@angular/common'; | ||
import {Component} from '@angular/core'; | ||
import {ActivatedRoute, RouterModule} from '@angular/router'; | ||
import {User} from '../user-title.interface'; | ||
import {UserResolver} from './user.resolver'; | ||
|
||
@Component({ | ||
selector: 'lib-user', | ||
standalone: true, | ||
templateUrl: './user.component.html', | ||
imports: [CommonModule, RouterModule], | ||
}) | ||
export class UserComponent { | ||
user: User; | ||
|
||
constructor(private readonly route: ActivatedRoute) { | ||
this.user = this.route.snapshot.data['user']; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import {Injectable} from '@angular/core'; | ||
import {ActivatedRouteSnapshot} from '@angular/router'; | ||
import {Observable} from 'rxjs'; | ||
import {UserService} from './user.service'; | ||
import {User} from '../user-title.interface'; | ||
|
||
@Injectable() | ||
export class UserResolver { | ||
constructor(private readonly userService: UserService) {} | ||
|
||
resolve(route: ActivatedRouteSnapshot): Observable<User> { | ||
const id = route.paramMap.get('id'); | ||
return this.userService.getUserById(id); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import {Injectable} from '@angular/core'; | ||
import {Observable, of} from 'rxjs'; | ||
import {USERS} from '../mock-data.constants'; | ||
|
||
@Injectable() | ||
export class UserService { | ||
private readonly users = USERS; | ||
|
||
getUserById(id: string): Observable<any> { | ||
const user = this.users.find(u => u.id === id); | ||
return of(user); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
<nav aria-label="breadcrumb" *ngIf="breadcrumbs$ | async as breadcrumbs"> | ||
<ul class="breadcrumb"> | ||
<ng-container | ||
*ngIf="breadcrumbs.length > maxItems && !expanded; else fullBreadcrumb" | ||
> | ||
<li class="breadcrumb-item"> | ||
<ng-container *ngIf="!breadcrumbs[0].skipLink; else noLinkFirst"> | ||
<a [routerLink]="breadcrumbs[0].url">{{ breadcrumbs[0].label }}</a> | ||
</ng-container> | ||
<ng-template #noLinkFirst> | ||
<span>{{ breadcrumbs[0].label }}</span> | ||
</ng-template> | ||
</li> | ||
<span class="separator">{{ separator }}</span> | ||
|
||
<li class="breadcrumb-item clickable" (click)="toggleExpand()">...</li> | ||
<span class="separator">{{ separator }}</span> | ||
|
||
<li class="breadcrumb-item" [class.active]="true"> | ||
<ng-container | ||
*ngIf="!breadcrumbs[breadcrumbs.length - 1].skipLink; else noLinkLast" | ||
> | ||
<a [routerLink]="breadcrumbs[breadcrumbs.length - 1].url"> | ||
{{ breadcrumbs[breadcrumbs.length - 1].label }} | ||
</a> | ||
</ng-container> | ||
<ng-template #noLinkLast> | ||
<span>{{ breadcrumbs[breadcrumbs.length - 1].label }}</span> | ||
</ng-template> | ||
</li> | ||
</ng-container> | ||
|
||
<ng-template #fullBreadcrumb> | ||
<ng-container *ngFor="let breadcrumb of breadcrumbs; let last = last"> | ||
<li class="breadcrumb-item" [class.active]="last"> | ||
<ng-container *ngIf="!breadcrumb.skipLink && !last; else noLink"> | ||
<a [routerLink]="breadcrumb.url">{{ breadcrumb.label }}</a> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what if a breacrumb label is too long? Shouldn't we trim it ? |
||
</ng-container> | ||
<ng-template #noLink> | ||
<span>{{ breadcrumb.label }}</span> | ||
</ng-template> | ||
</li> | ||
<span *ngIf="!last" class="separator">{{ separator }}</span> | ||
</ng-container> | ||
</ng-template> | ||
</ul> | ||
</nav> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
.breadcrumb { | ||
display: flex; | ||
flex-wrap: wrap; | ||
list-style: none; | ||
padding: 0; | ||
margin: 0; | ||
background-color: transparent; | ||
|
||
.breadcrumb-item { | ||
display: flex; | ||
align-items: center; | ||
font-size: 14px; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't we use rem for responsive design? |
||
color: #555; | ||
|
||
a { | ||
color: #007bff; | ||
text-decoration: none; | ||
|
||
&:hover { | ||
text-decoration: underline; | ||
} | ||
} | ||
|
||
&.active { | ||
font-weight: 600; | ||
color: #333; | ||
} | ||
&.clickable { | ||
cursor: pointer; | ||
color: #007bff; | ||
text-decoration: underline; | ||
|
||
&:hover { | ||
color: #0056b3; | ||
} | ||
} | ||
} | ||
|
||
.separator { | ||
margin: 0 6px; | ||
color: #aaa; | ||
font-size: 14px; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,36 @@ | ||||||
import {Component, Input, isDevMode, OnInit} from '@angular/core'; | ||||||
import {CommonModule} from '@angular/common'; | ||||||
import {Breadcrumb} from './breadcrumb.interface'; | ||||||
import {BreadcrumbService} from './breadcrumb.service'; | ||||||
import {Observable} from 'rxjs'; | ||||||
import {RouterModule} from '@angular/router'; | ||||||
|
||||||
@Component({ | ||||||
selector: 'app-breadcrumb', | ||||||
templateUrl: './breadcrumb.component.html', | ||||||
standalone: true, | ||||||
imports: [CommonModule, RouterModule], | ||||||
styleUrls: ['./breadcrumb.component.scss'], | ||||||
}) | ||||||
export class BreadcrumbComponent implements OnInit { | ||||||
breadcrumbs$: Observable<Breadcrumb[]> = this.breadcrumbService.breadcrumbs; | ||||||
|
||||||
@Input() staticBreadcrumbs = []; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. where is this being used in component? |
||||||
@Input() separator = '>'; | ||||||
@Input() maxItems = 8; | ||||||
@Input() separatorClass = 'separator'; | ||||||
@Input() itemClass = 'breadcrumb-item'; | ||||||
|
||||||
expanded = false; | ||||||
constructor(private readonly breadcrumbService: BreadcrumbService) {} | ||||||
ngOnInit(): void { | ||||||
this.breadcrumbs$.subscribe(breadcrumbs => { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. destroy observable for all the observables There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done! |
||||||
if (isDevMode()) { | ||||||
console.log('Breadcrumbs:', breadcrumbs); | ||||||
} | ||||||
}); | ||||||
} | ||||||
toggleExpand() { | ||||||
this.expanded = true; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The toggleExpand method always sets 'expanded' to true instead of toggling its state, which prevents collapsing the expanded breadcrumb view. Consider updating it to: this.expanded = !this.expanded;
Suggested change
Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback |
||||||
} | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export interface Breadcrumb { | ||
label: string; | ||
url: string; | ||
skipLink?: boolean; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import {Injectable} from '@angular/core'; | ||
import {ActivatedRouteSnapshot, NavigationEnd, Router} from '@angular/router'; | ||
import {BehaviorSubject} from 'rxjs'; | ||
import {filter} from 'rxjs/operators'; | ||
import {Breadcrumb} from './breadcrumb.interface'; | ||
|
||
@Injectable({providedIn: 'root'}) | ||
export class BreadcrumbService { | ||
private readonly breadcrumbs$ = new BehaviorSubject<Breadcrumb[]>([]); | ||
|
||
constructor(private readonly router: Router) { | ||
this.router.events | ||
.pipe(filter(event => event instanceof NavigationEnd)) | ||
.subscribe(() => { | ||
const root = this.router.routerState.snapshot.root; | ||
const breadcrumbs = this.buildBreadcrumbs(root); | ||
this.breadcrumbs$.next(breadcrumbs); | ||
}); | ||
} | ||
|
||
private buildBreadcrumbs( | ||
route: ActivatedRouteSnapshot, | ||
url = '', | ||
breadcrumbs: Breadcrumb[] = [], | ||
): Breadcrumb[] { | ||
if (!route.routeConfig) { | ||
return route.firstChild | ||
? this.buildBreadcrumbs(route.firstChild, url, breadcrumbs) | ||
: breadcrumbs; | ||
} | ||
|
||
let path = route.routeConfig.path || ''; | ||
Object.keys(route.params).forEach(key => { | ||
path = path.replace(`:${key}`, route.params[key]); | ||
}); | ||
const nextUrl = path ? `${url}/${path}` : url; | ||
const label = this._resolveLabel(route, path); | ||
const skipLink = route.routeConfig.data?.['skipLink'] ?? false; | ||
|
||
if (label) { | ||
breadcrumbs.push({label, url: nextUrl, skipLink}); | ||
} | ||
|
||
return route.firstChild | ||
? this.buildBreadcrumbs(route.firstChild, nextUrl, breadcrumbs) | ||
: breadcrumbs; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. modify this function to keep it clean, readable and remove if else if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done mam |
||
|
||
private _toTitleCase(str: string): string { | ||
return str.replace(/-/g, ' ').replace(/\b\w/g, char => char.toUpperCase()); | ||
} | ||
|
||
private _resolveLabel(route: ActivatedRouteSnapshot, path: string): string { | ||
const breadcrumbData = route.routeConfig.data?.['breadcrumb']; | ||
|
||
if (typeof breadcrumbData === 'function') { | ||
return breadcrumbData(route.data, route.paramMap, route); | ||
} | ||
if (typeof breadcrumbData === 'string') { | ||
return breadcrumbData; | ||
} | ||
if (route.routeConfig.path?.startsWith(':')) { | ||
const paramName = route.routeConfig.path.slice(1); | ||
return route.params[paramName] ?? paramName; | ||
} | ||
return this._toTitleCase(path); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. remove so many ifs, use array instead |
||
} | ||
|
||
get breadcrumbs() { | ||
return this.breadcrumbs$.asObservable(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,7 +19,7 @@ import {environment} from '../environments/environment'; | |
import {ThemeModule} from '@project-lib/theme/theme.module'; | ||
import {OverlayModule} from '@angular/cdk/overlay'; | ||
import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; | ||
import {GanttModule} from '@project-lib/components/index'; | ||
import {BreadcrumbComponent, GanttModule} from '@project-lib/components/index'; | ||
import {SelectModule} from '@project-lib/components/selector'; | ||
import {HeaderComponent} from '@project-lib/components/header/header.component'; | ||
import {SidebarComponent} from '@project-lib/components/sidebar/sidebar.component'; | ||
|
@@ -40,6 +40,7 @@ import {SidebarComponent} from '@project-lib/components/sidebar/sidebar.componen | |
BrowserAnimationsModule, | ||
HeaderComponent, | ||
SidebarComponent, | ||
BreadcrumbComponent, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. resolve this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To move the
Here’s how the modified code should look: import { NgModule } from '@angular/core';
// Other imports...
import { BreadcrumbComponent } from '@project-lib/components/breadcrumb/breadcrumb.component';
@NgModule({
declarations: [
// Other components...
HeaderComponent,
SidebarComponent,
// Remove BreadcrumbComponent from here
],
imports: [
BrowserAnimationsModule,
HeaderComponent,
SidebarComponent,
BreadcrumbComponent, // Add BreadcrumbComponent here
],
// Other configurations...
})
export class AppModule { } By following these steps, you'll properly integrate the standalone There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sir ,its already in imports array @NgModule({ |
||
], | ||
providers: [ | ||
TranslationService, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use types here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done mam