Update explore.component.ts

This commit is contained in:
Milad Raeisi
2024-09-28 00:40:53 +04:00
parent 73d4983757
commit 7031205559

View File

@@ -25,290 +25,310 @@ import { Contact } from '../chat/chat.types';
import { ChatService } from '../chat/chat.service';
@Component({
selector: 'explore',
standalone: true,
templateUrl: './explore.component.html',
encapsulation: ViewEncapsulation.None,
imports: [
MatButtonModule, RouterLink, MatIconModule, AngorCardComponent,
CdkScrollable, MatFormFieldModule, MatSelectModule, MatOptionModule,
MatInputModule, MatSlideToggleModule, NgClass, MatTooltipModule,
MatProgressBarModule, AngorFindByKeyPipe, PercentPipe, I18nPluralPipe, CommonModule
],
selector: 'explore',
standalone: true,
templateUrl: './explore.component.html',
encapsulation: ViewEncapsulation.None,
imports: [
MatButtonModule, RouterLink, MatIconModule, AngorCardComponent,
CdkScrollable, MatFormFieldModule, MatSelectModule, MatOptionModule,
MatInputModule, MatSlideToggleModule, NgClass, MatTooltipModule,
MatProgressBarModule, AngorFindByKeyPipe, PercentPipe, I18nPluralPipe, CommonModule
],
})
export class ExploreComponent implements OnInit, OnDestroy {
projects: Project[] = [];
errorMessage: string = '';
loading: boolean = false;
private metadataLoadLimit = 5;
private _unsubscribeAll: Subject<any> = new Subject<any>();
filteredProjects: Project[] = [];
projects: Project[] = [];
errorMessage: string = '';
loading: boolean = false;
private metadataLoadLimit = 5;
private _unsubscribeAll: Subject<any> = new Subject<any>();
filteredProjects: Project[] = [];
constructor(
private projectService: ProjectsService,
private router: Router,
private stateService: StateService,
private metadataService: MetadataService,
private indexedDBService: IndexedDBService,
private changeDetectorRef: ChangeDetectorRef,
private sanitizer: DomSanitizer,
private _chatService: ChatService
) {}
constructor(
private projectService: ProjectsService,
private router: Router,
private stateService: StateService,
private metadataService: MetadataService,
private indexedDBService: IndexedDBService,
private changeDetectorRef: ChangeDetectorRef,
private sanitizer: DomSanitizer,
private _chatService: ChatService
) { }
async ngOnInit(): Promise<void> {
this.loadInitialProjects();
this.subscribeToMetadataUpdates();
}
private async loadInitialProjects(): Promise<void> {
try {
this.loading = true;
this.projects = this.stateService.getProjects();
if (this.projects.length === 0) {
await this.loadProjectsFromService();
} else {
this.filteredProjects = [...this.projects];
const pubkeysToFetch = this.getProjectsWithoutMetadata();
if (pubkeysToFetch.length > 0) {
await this.loadMetadataForProjects(pubkeysToFetch);
}
}
} catch (error) {
this.handleError('Error loading initial projects');
} finally {
this.loading = false;
this.changeDetectorRef.detectChanges();
async ngOnInit(): Promise<void> {
this.loadInitialProjects();
this.subscribeToMetadataUpdates();
}
}
private async loadProjectsFromService(): Promise<void> {
try {
const projects = await this.projectService.fetchProjects();
if (projects.length === 0) {
this.errorMessage = 'No projects found';
return;
}
private async loadInitialProjects(): Promise<void> {
try {
this.loading = true;
this.projects = this.stateService.getProjects();
this.projects = projects;
this.filteredProjects = [...this.projects];
this.stateService.setProjects(this.projects);
const pubkeys = projects.map(p => p.nostrPubKey);
await this.loadMetadataForProjects(pubkeys);
} catch (error) {
this.handleError('Error fetching projects from service');
}
}
private subscribeToMetadataUpdates(): void {
this.indexedDBService.getMetadataStream()
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((updatedMetadata: any) => {
if (updatedMetadata) {
const projectToUpdate = this.projects.find(p => p.nostrPubKey === updatedMetadata.pubkey);
if (projectToUpdate) {
this.updateProjectMetadata(projectToUpdate, updatedMetadata.metadata);
}
}
});
}
private getProjectsWithoutMetadata(): string[] {
return this.projects
.filter(project => !project.displayName || !project.about)
.map(project => project.nostrPubKey);
}
private async loadMetadataForProjects(pubkeys: string[]): Promise<void> {
const metadataPromises = pubkeys.map(async (pubkey) => {
// Check cache first
const cachedMetadata = await this.indexedDBService.getUserMetadata(pubkey);
if (cachedMetadata) {
return { pubkey, metadata: cachedMetadata };
}
return null; // This will allow us to fetch the missing ones later
});
const metadataResults = await Promise.all(metadataPromises);
// Filter out nulls (which represent pubkeys without cached metadata)
const missingPubkeys = metadataResults
.filter(result => result === null)
.map((_, index) => pubkeys[index]);
// Update projects that have cached metadata
metadataResults.forEach(result => {
if (result && result.metadata) {
const project = this.projects.find(p => p.nostrPubKey === result.pubkey);
if (project) {
this.updateProjectMetadata(project, result.metadata);
}
}
});
// Fetch metadata for pubkeys that are not cached
if (missingPubkeys.length > 0) {
await this.metadataService.fetchMetadataForMultipleKeys(missingPubkeys)
.then((metadataList: any[]) => {
metadataList.forEach(metadata => {
const project = this.projects.find(p => p.nostrPubKey === metadata.pubkey);
if (project) {
this.updateProjectMetadata(project, metadata);
if (this.projects.length === 0) {
await this.loadProjectsFromService();
} else {
this.filteredProjects = [...this.projects];
const pubkeysToFetch = this.getProjectsWithoutMetadata();
if (pubkeysToFetch.length > 0) {
await this.loadMetadataForProjects(pubkeysToFetch);
}
}
});
this.changeDetectorRef.detectChanges();
})
.catch(error => {
console.error('Error fetching metadata for projects:', error);
} catch (error) {
this.handleError('Error loading initial projects');
} finally {
this.loading = false;
this.changeDetectorRef.detectChanges();
}
}
private async loadProjectsFromService(): Promise<void> {
try {
const projects = await this.projectService.fetchProjects();
if (projects.length === 0) {
this.errorMessage = 'No projects found';
return;
}
this.projects = projects;
this.filteredProjects = [...this.projects];
this.stateService.setProjects(this.projects);
const pubkeys = projects.map(p => p.nostrPubKey);
await this.loadMetadataForProjects(pubkeys);
} catch (error) {
this.handleError('Error fetching projects from service');
}
}
private subscribeToMetadataUpdates(): void {
this.indexedDBService.getMetadataStream()
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((updatedMetadata: any) => {
if (updatedMetadata) {
const projectToUpdate = this.projects.find(p => p.nostrPubKey === updatedMetadata.pubkey);
if (projectToUpdate) {
this.updateProjectMetadata(projectToUpdate, updatedMetadata.metadata);
}
}
});
}
private getProjectsWithoutMetadata(): string[] {
return this.projects
.filter(project => !project.displayName || !project.about)
.map(project => project.nostrPubKey);
}
private async loadMetadataForProjects(pubkeys: string[]): Promise<void> {
const metadataPromises = pubkeys.map(async (pubkey) => {
// Check cache first
const cachedMetadata = await this.indexedDBService.getUserMetadata(pubkey);
if (cachedMetadata) {
return { pubkey, metadata: cachedMetadata };
}
return null; // This will allow us to fetch the missing ones later
});
const metadataResults = await Promise.all(metadataPromises);
// Filter out nulls (which represent pubkeys without cached metadata)
const missingPubkeys = metadataResults
.filter(result => result === null)
.map((_, index) => pubkeys[index]);
// Update projects that have cached metadata
metadataResults.forEach(result => {
if (result && result.metadata) {
const project = this.projects.find(p => p.nostrPubKey === result.pubkey);
if (project) {
this.updateProjectMetadata(project, result.metadata);
}
}
});
// Fetch metadata for pubkeys that are not cached
if (missingPubkeys.length > 0) {
await this.metadataService.fetchMetadataForMultipleKeys(missingPubkeys)
.then((metadataList: any[]) => {
metadataList.forEach(metadata => {
const project = this.projects.find(p => p.nostrPubKey === metadata.pubkey);
if (project) {
this.updateProjectMetadata(project, metadata);
}
});
this.changeDetectorRef.detectChanges();
})
.catch(error => {
console.error('Error fetching metadata for projects:', error);
});
}
}
async loadProjects(): Promise<void> {
if (this.loading || this.errorMessage === 'No more projects found') return;
this.loading = true;
this.projectService.fetchProjects().then(async (projects: Project[]) => {
if (projects.length === 0 && this.projects.length === 0) {
this.errorMessage = 'No projects found';
} else if (projects.length === 0) {
this.errorMessage = 'No more projects found';
} else {
this.projects = [...this.projects, ...projects];
this.filteredProjects = [...this.projects];
const pubkeys = projects.map(project => project.nostrPubKey);
await this.loadMetadataForProjects(pubkeys);
this.stateService.setProjects(this.projects);
this.projects.forEach(project => this.subscribeToProjectMetadata(project));
}
this.loading = false;
this.changeDetectorRef.detectChanges();
}).catch((error: any) => {
console.error('Error fetching projects:', error);
this.errorMessage = 'Error fetching projects. Please try again later.';
this.loading = false;
this.changeDetectorRef.detectChanges();
});
}
}
async loadMetadataForProject(project: Project): Promise<void> {
try {
const metadata = await this.metadataService.fetchMetadataWithCache(project.nostrPubKey);
if (metadata) {
this.updateProjectMetadata(project, metadata);
} else {
console.warn(`No metadata found for project ${project.nostrPubKey}`);
}
} catch (error) {
console.error(`Error fetching metadata for project ${project.nostrPubKey}:`, error);
}
}
updateProjectMetadata(project: Project, metadata: any): void {
async loadProjects(): Promise<void> {
if (this.loading || this.errorMessage === 'No more projects found') return;
const updatedProject: Project = {
...project,
displayName: metadata.name || '',
about: metadata.about ? metadata.about.replace(/<\/?[^>]+(>|$)/g, '') : '',
picture: metadata.picture || '',
banner: metadata.banner || ''
};
this.loading = true;
const index = this.projects.findIndex(p => p.projectIdentifier === project.projectIdentifier);
if (index !== -1) {
this.projects[index] = updatedProject;
this.projects = [...this.projects];
}
this.projectService.fetchProjects().then(async (projects: Project[]) => {
if (projects.length === 0 && this.projects.length === 0) {
this.errorMessage = 'No projects found';
} else if (projects.length === 0) {
this.errorMessage = 'No more projects found';
} else {
this.projects = [...this.projects, ...projects];
this.filteredProjects = [...this.projects];
const pubkeys = projects.map(project => project.nostrPubKey);
await this.loadMetadataForProjects(pubkeys);
this.stateService.setProjects(this.projects);
this.projects.forEach(project => this.subscribeToProjectMetadata(project));
}
this.loading = false;
this.changeDetectorRef.detectChanges();
}).catch((error: any) => {
console.error('Error fetching projects:', error);
this.errorMessage = 'Error fetching projects. Please try again later.';
this.loading = false;
this.changeDetectorRef.detectChanges();
});
}
async loadMetadataForProject(project: Project): Promise<void> {
try {
const metadata = await this.metadataService.fetchMetadataWithCache(project.nostrPubKey);
if (metadata) {
this.updateProjectMetadata(project, metadata);
} else {
console.warn(`No metadata found for project ${project.nostrPubKey}`);
}
} catch (error) {
console.error(`Error fetching metadata for project ${project.nostrPubKey}:`, error);
}
}
updateProjectMetadata(project: Project, metadata: any): void {
const updatedProject: Project = {
...project,
displayName: metadata.name || '',
about: metadata.about ? metadata.about.replace(/<\/?[^>]+(>|$)/g, '') : '',
picture: metadata.picture || '',
banner: metadata.banner || ''
};
const index = this.projects.findIndex(p => p.projectIdentifier === project.projectIdentifier);
if (index !== -1) {
this.projects[index] = updatedProject;
this.projects = [...this.projects];
this.changeDetectorRef.detectChanges();
}
this.filteredProjects = [...this.projects];
this.changeDetectorRef.detectChanges();
}
subscribeToProjectMetadata(project: Project): void {
this.metadataService.getMetadataStream()
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((updatedMetadata: any) => {
if (updatedMetadata && updatedMetadata.pubkey === project.nostrPubKey) {
this.updateProjectMetadata(project, updatedMetadata.metadata);
}
});
}
goToProjectDetails(project: Project): void {
this.router.navigate(['/profile', project.nostrPubKey]);
}
filterByQuery(query: string): void {
if (!query) {
this.filteredProjects = [...this.projects];
return;
}
this.filteredProjects = this.projects.filter(project =>
project.displayName?.toLowerCase().includes(query.toLowerCase()) ||
project.about?.toLowerCase().includes(query.toLowerCase())
);
}
toggleCompleted(event: any): void {
}
ngOnDestroy(): void {
this._unsubscribeAll.next(null);
this._unsubscribeAll.complete();
}
private handleError(message: string): void {
console.error(message);
this.errorMessage = message;
this.loading = false;
this.changeDetectorRef.detectChanges();
}
getSafeUrl(url: any, isBanner: boolean): SafeUrl {
if (url && typeof url === 'string' && this.isImageUrl(url)) {
return this.sanitizer.bypassSecurityTrustUrl(url);
} else {
const defaultImage = isBanner ? '/images/pages/profile/cover.jpg' : 'images/avatars/avatar-placeholder.png';
return this.sanitizer.bypassSecurityTrustUrl(defaultImage);
}
}
private isImageUrl(url: string): boolean {
return /\.(jpeg|jpg|gif|png|svg|bmp|webp|tiff|ico)$/i.test(url);
}
async openChat(publicKey: string): Promise<void> {
try {
const metadata = await this.metadataService.fetchMetadataWithCache(publicKey);
if (metadata) {
const contact: Contact = {
pubKey: publicKey,
name: metadata.name || 'Unknown',
picture: metadata.picture || '/images/avatars/avatar-placeholder.png',
about: metadata.about || '',
displayName: metadata.displayName || metadata.name || 'Unknown',
};
this._chatService.getChatById(contact.pubKey, contact).subscribe((chat) => {
this.router.navigate(['/chat', contact.pubKey]);
subscribeToProjectMetadata(project: Project): void {
this.metadataService.getMetadataStream()
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((updatedMetadata: any) => {
if (updatedMetadata && updatedMetadata.pubkey === project.nostrPubKey) {
this.updateProjectMetadata(project, updatedMetadata.metadata);
}
});
} else {
console.error('No metadata found for the public key:', publicKey);
}
} catch (error) {
console.error('Error opening chat:', error);
}
}
goToProjectDetails(project: Project): void {
this.loading = true;
this.projectService.fetchAndSaveProjectStats(project.projectIdentifier)
.then((stats) => {
if (stats) {
this.navigateToProfile(project.nostrPubKey);
} else {
console.error('Failed to fetch project stats from API');
this.handleError('Error fetching project stats');
}
})
.catch((error) => {
this.handleError('Error fetching project stats from server');
})
.finally(() => {
this.loading = false;
});
}
private navigateToProfile(nostrPubKey: string): void {
this.router.navigate(['/profile', nostrPubKey]);
}
filterByQuery(query: string): void {
if (!query) {
this.filteredProjects = [...this.projects];
return;
}
this.filteredProjects = this.projects.filter(project =>
project.displayName?.toLowerCase().includes(query.toLowerCase()) ||
project.about?.toLowerCase().includes(query.toLowerCase())
);
}
toggleCompleted(event: any): void {
}
ngOnDestroy(): void {
this._unsubscribeAll.next(null);
this._unsubscribeAll.complete();
}
private handleError(message: string): void {
console.error(message);
this.errorMessage = message;
this.loading = false;
this.changeDetectorRef.detectChanges();
}
getSafeUrl(url: any, isBanner: boolean): SafeUrl {
if (url && typeof url === 'string' && this.isImageUrl(url)) {
return this.sanitizer.bypassSecurityTrustUrl(url);
} else {
const defaultImage = isBanner ? '/images/pages/profile/cover.jpg' : 'images/avatars/avatar-placeholder.png';
return this.sanitizer.bypassSecurityTrustUrl(defaultImage);
}
}
private isImageUrl(url: string): boolean {
return /\.(jpeg|jpg|gif|png|svg|bmp|webp|tiff|ico)$/i.test(url);
}
async openChat(publicKey: string): Promise<void> {
try {
const metadata = await this.metadataService.fetchMetadataWithCache(publicKey);
if (metadata) {
const contact: Contact = {
pubKey: publicKey,
name: metadata.name || 'Unknown',
picture: metadata.picture || '/images/avatars/avatar-placeholder.png',
about: metadata.about || '',
displayName: metadata.displayName || metadata.name || 'Unknown',
};
this._chatService.getChatById(contact.pubKey, contact).subscribe((chat) => {
this.router.navigate(['/chat', contact.pubKey]);
});
} else {
console.error('No metadata found for the public key:', publicKey);
}
} catch (error) {
console.error('Error opening chat:', error);
}
}
}