import { HttpResponse } from '@angular/common/http'; import { AfterViewInit, Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core'; import { Router } from '@angular/router'; import { faArrowRight, faFolderOpen } from '@fortawesome/free-solid-svg-icons'; import { ReviewManagementService } from 'app/admin/review-management/review-management.service'; import { ApplicationInfoService } from 'app/core/application/applicationInfo.service'; import { Account } from 'app/core/auth/account.model'; import { AccountService } from 'app/core/auth/account.service'; import { AlertService } from 'app/core/util/alert.service'; import { LikesService } from 'app/entities/likes/likes.service'; import { ExerciseService } from 'app/exercise/service/exercise.service'; import { SearchService } from 'app/search/service/search-service'; import { ShoppingBasketInfo, ShoppingBasketRedirectInfoDTO } from 'app/shared/model/basket/shopping-basket-info.model'; import { Person } from 'app/shared/model/person.model'; import { ChildInfo, ExtendedSearchResultDTO, hasChildren, PluginActionInfo, SearchResultDTO, } from 'app/shared/model/search/search-result-dto.model'; import { PluginService } from 'app/shared/service/plugin-service'; import { WatchlistManager } from 'app/shared/watchlist/watchlist-manager'; import { Subscription } from 'rxjs'; @Component({ selector: 'jhi-exercise-modal-details', templateUrl: './exercise-details-modal.component.html', }) export class ExerciseDetailsModalComponent { @Output() exerciseChangedEvent = new EventEmitter<SearchResultDTO>(); @Input() get referencedExercise(): SearchResultDTO | undefined { return this.exercise; } set referencedExercise(exercise: SearchResultDTO | undefined) { this.exercise = exercise as ExtendedSearchResultDTO; } exercise: ExtendedSearchResultDTO | undefined; constructor(private watchlistManager: WatchlistManager) {} handleExerciseChangedEvent(exercise: SearchResultDTO): void { this.exerciseChangedEvent.emit(exercise); } } @Component({ selector: 'jhi-exercise-nonmodal-details', templateUrl: './exercise-details-nonmodal.component.html', }) export class ExerciseDetailsNonModalComponent { @Input() exercise: ExtendedSearchResultDTO | undefined; } @Component({ selector: 'jhi-exercise-header', templateUrl: './exerciseComponents/exercise-header.component.html', styleUrls: ['./exerciseComponents/exercise-header.component.scss'], }) export class ExerciseHeaderComponent { @Input() exercise: ExtendedSearchResultDTO | undefined; /** * correct missing image urls */ correctImageURL(event: Event): void { const srcElement = event.target as HTMLImageElement; if (srcElement) { srcElement.src = '/content/images/Logo_codeAbility_4c_300dpi_RGB3.gif'; } } } @Component({ selector: 'jhi-exercise-body', templateUrl: './exerciseComponents/exercise-body.component.html', styleUrls: ['./exerciseComponents/exercise-body.component.scss'], }) export class ExerciseBodyComponent implements OnInit, OnDestroy, AfterViewInit { @Output() exerciseChangedEvent = new EventEmitter<SearchResultDTO>(); @Input() get referencedExercise(): SearchResultDTO | undefined { return this.exercise; } set referencedExercise(exercise: SearchResultDTO | undefined) { this.exercise = exercise as ExtendedSearchResultDTO; } exercise: ExtendedSearchResultDTO | undefined; parent: SearchResultDTO | undefined; bookmarked = false; downloadWithChildren = false; markDownExercise: SearchResultDTO | undefined; account: Account | null = null; authSubscription?: Subscription; hasLiked: boolean | null = null; likeSubscription?: Subscription; authenticated = false; treeIcon = faFolderOpen; faArrowRight = faArrowRight; oerLink?: string; oerExerciseMatch = /([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})$/; constructor( private reviewManagementService: ReviewManagementService, private accountService: AccountService, protected pluginService: PluginService, private searchService: SearchService, private likesService: LikesService, private jhiAlertService: AlertService, private watchlistManager: WatchlistManager, private exerciseService: ExerciseService, private applicationInfoService: ApplicationInfoService, private router: Router ) {} toggleWithChildren() { this.downloadWithChildren = !this.downloadWithChildren; } selectREADME(): void { this.markDownExercise = this.exercise; } hasChildren(): boolean { return hasChildren(this.exercise); } getChildrenInfos(): ChildInfo[] { if (this.exercise?.childInfos) { return this.exercise?.childInfos; } else { this.loadChildrenInfoDisplayString(); return []; } } loadChildrenInfoDisplayString(): string { if (!this.exercise?.childInfos) { this.exercise!.childInfos = []; this.exerciseService.populateExerciseWithChildrenInfo(this.exercise!).subscribe({ next: () => { // nothing to do }, error: () => console.warn('Could not load children info'), }); } return ''; } public startAction(action: PluginActionInfo, exercise: SearchResultDTO): void { const basketInfo: ShoppingBasketInfo = { plugin: action.plugin, action: action.action, itemInfos: [exercise], }; this.pluginService.getRedirectLink(basketInfo).subscribe({ next: (redirectInfo: ShoppingBasketRedirectInfoDTO) => { window.open(redirectInfo.redirectURL, action.action); }, error: () => alert('Search failed'), }); } public download(): void { this.exportProject(this.exercise!.exerciseId); } exportProject(exerciseId: string) { const recursion = this.downloadWithChildren ? 'WITH_DESCENDANTS' : 'JUST_PROJECT'; return this.searchService.exportProject(exerciseId, recursion).subscribe({ next: (response: HttpResponse<Blob>) => { this.jhiAlertService.addAlert({ type: 'success', translationKey: 'artemisApp.programmingExercise.export.successMessage', }); if (response.body) { const zipFile = new Blob([response.body], { type: 'application/zip' }); const url = window.URL.createObjectURL(zipFile); const link = document.createElement('a'); link.setAttribute('href', url); link.setAttribute('download', response.headers.get('filename')!); document.body.appendChild(link); // Required for FF link.click(); window.URL.revokeObjectURL(url); } }, error: () => alert('Unable to export exercise. Please log in to export, or check your git lab permissions.'), }); } getGitLabUrl(): string { return this.reviewManagementService.getGitLabUrl(this.exercise!); } openLink(link: string): void { window.open(link); } searchChildren(parentId: string): void { this.router.navigate(['/search'], { queryParams: { p: parentId } }); } toParent(parentId: string): void { this.toExercise(parentId); } toExercise(exerciseId: string): void { this.exerciseService.loadExercise(exerciseId).subscribe({ next: searchResult => { this.exercise = this.exerciseService.populateExerciseWithData(searchResult); this.exerciseChangedEvent.emit(this.exercise); this.updateParent(this.exercise); }, error: () => { alert(`exercise ${exerciseId} cannot be loaded`); }, }); } isLoggedIn(): boolean { return this.accountService.isAuthenticated(); } public isEditable(): boolean { if (!this.exercise) return false; if (!this.exercise.project.url) return false; const matchGroups = this.exercise.project.url.match(this.oerExerciseMatch); if (!matchGroups) return false; // Just for Testing: allow every edit if (this.oerLink) return true; const email = this.accountService.getUserEMail(); if (!email || email == '') return false; // test all: creator, publisher let potentialEditors: Person[] = []; potentialEditors = potentialEditors.concat(this.exercise.metadata.creator); potentialEditors = potentialEditors.concat(this.exercise.metadata.publisher); for (const pE of potentialEditors) { if (pE.email.localeCompare(email, undefined, { sensitivity: 'base' }) === 0) return true; } return false; } public getOEResourceLink(): string { if (this.oerLink) { const matchGroups = this.exercise!.project.url.match(this.oerExerciseMatch); if (!matchGroups) return ''; const guid = matchGroups[1]; return this.oerLink.toString() + '/en/update/1/' + guid; } return ''; } likeAction(): void { // to do call like service this.likesService.likeExercise(this.exercise!.exerciseId).subscribe(() => // eslint-disable-next-line no-console console.log('Liked post' + this.exercise!.exerciseId) ); this.exercise!.numberOfLikes = this.exercise!.numberOfLikes + 1; this.exercise!.userHasLiked = true; } unlikeAction(): void { // to do call like service this.likesService.unlikeExercise(this.exercise!.exerciseId); this.exercise!.numberOfLikes = this.exercise!.numberOfLikes - 1; this.exercise!.userHasLiked = false; } public isAuthenticated(): boolean { return this.accountService.isAuthenticated(); } ngOnInit(): void { this.bookmarked = this.watchlistManager.isExerciseOnCurrentWatchlist(this.exercise!); this.authSubscription = this.accountService.getAuthenticationState().subscribe(account => (this.account = account)); this.applicationInfoService.loadOerLink().subscribe({ next: (res: string) => { this.oerLink = res; }, error: error => // eslint-disable-next-line no-console console.error(error), }); } ngAfterViewInit(): void { this.updateParent(this.exercise!); } updateParent(exercise: ExtendedSearchResultDTO): void { if (this.exercise!.file.parentId) { this.exerciseService.loadExercise(this.exercise!.file.parentId).subscribe({ next: searchResult => { this.parent = searchResult; }, error: () => { alert(`exercise ${this.exercise!.file.parentId} cannot be loaded`); }, }); } } ngOnDestroy(): void { if (this.authSubscription) { this.authSubscription.unsubscribe(); } } }