Skip to content

Commit

Permalink
Development: Migrate conversation components to use signals (#10223)
Browse files Browse the repository at this point in the history
  • Loading branch information
PaRangger authored Feb 5, 2025
1 parent 5babeab commit 1d2d1c5
Show file tree
Hide file tree
Showing 16 changed files with 237 additions and 242 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<h3 jhiTranslate="artemisApp.codeOfConduct.title"></h3>
<div [innerHTML]="course.courseInformationSharingMessagingCodeOfConduct || '' | htmlForMarkdown"></div>
<div [innerHTML]="course().courseInformationSharingMessagingCodeOfConduct || '' | htmlForMarkdown"></div>
<ul>
@for (user of responsibleContacts; track user) {
<li>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, Input, OnInit, inject } from '@angular/core';
import { Component, OnInit, inject, input } from '@angular/core';
import { User } from 'app/core/user/user.model';
import { HttpErrorResponse, HttpResponse } from '@angular/common/http';
import { onError } from 'app/shared/util/global.utils';
Expand All @@ -17,14 +17,13 @@ export class CourseConversationsCodeOfConductComponent implements OnInit {
private alertService = inject(AlertService);
private conversationService = inject(ConversationService);

@Input()
course: Course;
course = input.required<Course>();

responsibleContacts: User[] = [];

ngOnInit() {
if (this.course.id) {
this.conversationService.getResponsibleUsersForCodeOfConduct(this.course.id).subscribe({
if (this.course().id) {
this.conversationService.getResponsibleUsersForCodeOfConduct(this.course().id!).subscribe({
next: (res: HttpResponse<User[]>) => {
if (res.body) {
this.responsibleContacts = res.body;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { NgClass } from '@angular/common';
import { ChangeDetectorRef, Component, ElementRef, EventEmitter, HostListener, OnDestroy, OnInit, ViewChild, ViewEncapsulation, inject } from '@angular/core';
import { ChangeDetectorRef, Component, ElementRef, EventEmitter, HostListener, OnDestroy, OnInit, ViewEncapsulation, inject, viewChild } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import {
Expand Down Expand Up @@ -171,10 +171,8 @@ export class CourseConversationsComponent implements OnInit, OnDestroy {
isCodeOfConductAccepted?: boolean;
isCodeOfConductPresented = false;

@ViewChild(CourseWideSearchComponent)
courseWideSearch: CourseWideSearchComponent;
@ViewChild('courseWideSearchInput')
searchElement: ElementRef;
courseWideSearch = viewChild<CourseWideSearchComponent>(CourseWideSearchComponent);
searchElement = viewChild<ElementRef>('courseWideSearchInput');

courseWideSearchConfig: CourseWideSearchConfig;
courseWideSearchTerm = '';
Expand Down Expand Up @@ -420,7 +418,7 @@ export class CourseConversationsComponent implements OnInit, OnDestroy {
this.activeConversation = undefined;
this.updateQueryParameters();
this.courseWideSearchConfig.searchTerm = this.courseWideSearchTerm;
this.courseWideSearch?.onSearch();
this.courseWideSearch()?.onSearch();
}

prepareSidebarData() {
Expand Down Expand Up @@ -597,7 +595,7 @@ export class CourseConversationsComponent implements OnInit, OnDestroy {
handleSearchShortcut(event: KeyboardEvent) {
if ((event.metaKey || event.ctrlKey) && event.key === 'k') {
event.preventDefault();
this.searchElement.nativeElement.focus();
this.searchElement()!.nativeElement.focus();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
<div class="course-wide-search-mobile-disclaimer" [ngClass]="{ 'is-not-in-active-search': !courseWideSearchConfig.searchTerm }">
<div class="course-wide-search-mobile-disclaimer" [ngClass]="{ 'is-not-in-active-search': !courseWideSearchConfig().searchTerm }">
<span class="mb-2" jhiTranslate="artemisApp.metis.overview.mobileDisclaimer"></span>
<button class="btn btn-outline-secondary" jhiTranslate="artemisApp.metis.overview.mobileDisclaimerCallToAction" (click)="openSidebar()"></button>
</div>
<div [ngClass]="{ 'is-not-in-active-search': !courseWideSearchConfig.searchTerm }">
<div [ngClass]="{ 'is-not-in-active-search': !courseWideSearchConfig().searchTerm }">
<div class="mt-2 mx-2">
<div>
<h4 class="d-inline-block rounded p-1 info">
@if (!courseWideSearchConfig.searchTerm) {
@if (!courseWideSearchConfig().searchTerm) {
<span jhiTranslate="artemisApp.metis.overview.allPublicMessages"></span>
} @else {
<button class="btn btn-sm btn-outline-secondary d-inline-block d-sm-none me-2" (click)="openSidebar()">
<fa-icon [icon]="faChevronLeft" />
</button>
<span jhiTranslate="artemisApp.metis.overview.searchResults" [translateValues]="{ search: courseWideSearchConfig.searchTerm }"></span>
<span jhiTranslate="artemisApp.metis.overview.searchResults" [translateValues]="{ search: courseWideSearchConfig().searchTerm }"></span>
}
</h4>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,8 @@
import {
AfterViewInit,
ChangeDetectorRef,
Component,
ElementRef,
EventEmitter,
Input,
OnDestroy,
OnInit,
Output,
QueryList,
ViewChild,
ViewChildren,
ViewEncapsulation,
inject,
} from '@angular/core';
import { AfterViewInit, ChangeDetectorRef, Component, ElementRef, OnDestroy, OnInit, ViewEncapsulation, inject, input, output, viewChild, viewChildren } from '@angular/core';
import { faChevronLeft, faCircleNotch, faEnvelope, faFilter, faLongArrowAltDown, faLongArrowAltUp, faPlus, faTimes } from '@fortawesome/free-solid-svg-icons';
import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms';
import { Subject, takeUntil } from 'rxjs';
import { toObservable } from '@angular/core/rxjs-interop';
import { Course } from 'app/entities/course.model';
import { ChannelDTO, getAsChannelDTO } from 'app/entities/metis/conversation/channel.model';
import { Post } from 'app/entities/metis/post.model';
Expand All @@ -41,35 +27,27 @@ import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe';
imports: [NgClass, TranslateDirective, FaIconComponent, FormsModule, ReactiveFormsModule, NgbTooltip, InfiniteScrollDirective, PostingThreadComponent, ArtemisTranslatePipe],
})
export class CourseWideSearchComponent implements OnInit, AfterViewInit, OnDestroy {
metisService = inject(MetisService);
metisConversationService = inject(MetisConversationService);
private formBuilder = inject(FormBuilder);
cdr = inject(ChangeDetectorRef);

@Input()
courseWideSearchConfig: CourseWideSearchConfig;

@ViewChildren('postingThread')
messages: QueryList<any>;
readonly courseWideSearchConfig = input.required<CourseWideSearchConfig>();

@ViewChild('container')
content: ElementRef;
readonly messages = viewChildren<ElementRef>('postingThread');
readonly messages$ = toObservable(this.messages);
readonly content = viewChild<ElementRef>('container');

@Output() openThread = new EventEmitter<Post>();
readonly openThread = output<Post>();

course: Course;
currentPostContextFilter?: PostContextFilter;
// as set for the css class '.posting-infinite-scroll-container'
messagesContainerHeight = 700;

faPlus = faPlus;
faFilter = faFilter;
faLongArrowAltUp = faLongArrowAltUp;
faLongArrowAltDown = faLongArrowAltDown;
faTimes = faTimes;
faEnvelope = faEnvelope;
faCircleNotch = faCircleNotch;
faChevronLeft = faChevronLeft;
readonly faPlus = faPlus;
readonly faFilter = faFilter;
readonly faLongArrowAltUp = faLongArrowAltUp;
readonly faLongArrowAltDown = faLongArrowAltDown;
readonly faTimes = faTimes;
readonly faEnvelope = faEnvelope;
readonly faCircleNotch = faCircleNotch;
readonly faChevronLeft = faChevronLeft;

readonly SortDirection = SortDirection;
sortingOrder = SortDirection.ASCENDING;
Expand All @@ -85,7 +63,11 @@ export class CourseWideSearchComponent implements OnInit, AfterViewInit, OnDestr

getAsChannel = getAsChannelDTO;

private courseSidebarService: CourseSidebarService = inject(CourseSidebarService);
private courseSidebarService = inject(CourseSidebarService);
private metisService = inject(MetisService);
private metisConversationService = inject(MetisConversationService);
private formBuilder = inject(FormBuilder);
private cdr = inject(ChangeDetectorRef);

ngOnInit() {
this.subscribeToMetis();
Expand All @@ -95,7 +77,7 @@ export class CourseWideSearchComponent implements OnInit, AfterViewInit, OnDestr
}

ngAfterViewInit() {
this.messages.changes.pipe(takeUntil(this.ngUnsubscribe)).subscribe(this.handleScrollOnNewMessage);
this.messages$.pipe(takeUntil(this.ngUnsubscribe)).subscribe(this.handleScrollOnNewMessage);
}

ngOnDestroy() {
Expand All @@ -118,20 +100,24 @@ export class CourseWideSearchComponent implements OnInit, AfterViewInit, OnDestr
}

setPosts(posts: Post[]): void {
if (this.content) {
this.previousScrollDistanceFromTop = this.content.nativeElement.scrollHeight - this.content.nativeElement.scrollTop;
if (this.content()) {
this.previousScrollDistanceFromTop = this.content()!.nativeElement.scrollHeight - this.content()!.nativeElement.scrollTop;
}
this.posts = posts.slice().reverse();
}

handleScrollOnNewMessage = () => {
if ((this.posts.length > 0 && this.content.nativeElement.scrollTop === 0 && this.page === 1) || this.previousScrollDistanceFromTop === this.messagesContainerHeight) {
if (
(this.posts.length > 0 && this.content() && this.content()!.nativeElement.scrollTop === 0 && this.page === 1) ||
this.previousScrollDistanceFromTop === this.messagesContainerHeight
) {
this.scrollToBottomOfMessages();
}
};

scrollToBottomOfMessages() {
this.content.nativeElement.scrollTop = this.content.nativeElement.scrollHeight;
if (!this.content()) return;
this.content()!.nativeElement.scrollTop = this.content()!.nativeElement.scrollHeight;
}

fetchNextPage() {
Expand All @@ -140,7 +126,8 @@ export class CourseWideSearchComponent implements OnInit, AfterViewInit, OnDestr
this.page += 1;
this.commandMetisToFetchPosts();
}
this.content.nativeElement.scrollTop = this.content.nativeElement.scrollTop + 50;
if (!this.content()) return;
this.content()!.nativeElement.scrollTop = this.content()!.nativeElement.scrollTop + 50;
}

public commandMetisToFetchPosts(forceUpdate = false) {
Expand All @@ -152,14 +139,18 @@ export class CourseWideSearchComponent implements OnInit, AfterViewInit, OnDestr
}

private refreshMetisConversationPostContextFilter(): void {
const searchConfig = this.courseWideSearchConfig();

if (!searchConfig) return;

this.currentPostContextFilter = {
courseId: this.course?.id,
searchText: this.courseWideSearchConfig.searchTerm ? this.courseWideSearchConfig.searchTerm.trim() : undefined,
searchText: searchConfig.searchTerm ? searchConfig.searchTerm.trim() : undefined,
postSortCriterion: PostSortCriterion.CREATION_DATE,
filterToUnresolved: this.courseWideSearchConfig.filterToUnresolved,
filterToOwn: this.courseWideSearchConfig.filterToOwn,
filterToAnsweredOrReacted: this.courseWideSearchConfig.filterToAnsweredOrReacted,
sortingOrder: this.courseWideSearchConfig.sortingOrder,
filterToUnresolved: searchConfig.filterToUnresolved,
filterToOwn: searchConfig.filterToOwn,
filterToAnsweredOrReacted: searchConfig.filterToAnsweredOrReacted,
sortingOrder: searchConfig.sortingOrder,
pagingEnabled: true,
page: this.page - 1,
pageSize: 50,
Expand Down Expand Up @@ -203,10 +194,12 @@ export class CourseWideSearchComponent implements OnInit, AfterViewInit, OnDestr
}

onSelectContext(): void {
this.courseWideSearchConfig.filterToUnresolved = this.formGroup.get('filterToUnresolved')?.value;
this.courseWideSearchConfig.filterToOwn = this.formGroup.get('filterToOwn')?.value;
this.courseWideSearchConfig.filterToAnsweredOrReacted = this.formGroup.get('filterToAnsweredOrReacted')?.value;
this.courseWideSearchConfig.sortingOrder = this.sortingOrder;
const searchConfig = this.courseWideSearchConfig();
if (!searchConfig) return;
searchConfig.filterToUnresolved = this.formGroup.get('filterToUnresolved')?.value;
searchConfig.filterToOwn = this.formGroup.get('filterToOwn')?.value;
searchConfig.filterToAnsweredOrReacted = this.formGroup.get('filterToAnsweredOrReacted')?.value;
searchConfig.sortingOrder = this.sortingOrder;
this.onSearch();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@
<div class="ps-3">
<jhi-posting-header
[posting]="posting"
[readOnlyMode]="isReadOnlyMode"
[readOnlyMode]="isReadOnlyMode()"
[isCommunicationPage]="isCommunicationPage"
[lastReadDate]="lastReadDate"
[lastReadDate]="lastReadDate()"
[isDeleted]="isDeleted"
(onUserNameClicked)="onUserNameClicked()"
/>
Expand All @@ -45,9 +45,9 @@
/>
<div class="post-content-padding hover-actions">
<jhi-posting-reactions-bar
[isReadOnlyMode]="isReadOnlyMode"
[isReadOnlyMode]="isReadOnlyMode()"
[posting]="posting"
[isLastAnswer]="isLastAnswer"
[isLastAnswer]="isLastAnswer()"
[isThreadSidebar]="isThreadSidebar"
(openPostingCreateEditModal)="createAnswerPostModal.open()"
(reactionsUpdated)="onReactionsUpdated($event)"
Expand All @@ -66,9 +66,9 @@
@if (!isDeleted) {
<div class="post-content-padding post-reactions-bar" [ngClass]="{ 'is-saved': posting.isSaved && isConsecutive() }" @fade>
<jhi-posting-reactions-bar
[isReadOnlyMode]="isReadOnlyMode"
[isReadOnlyMode]="isReadOnlyMode()"
[posting]="posting"
[isLastAnswer]="isLastAnswer"
[isLastAnswer]="isLastAnswer()"
[isThreadSidebar]="isThreadSidebar"
(openPostingCreateEditModal)="createAnswerPostModal.open()"
(reactionsUpdated)="onReactionsUpdated($event)"
Expand All @@ -78,7 +78,7 @@
</div>
}
</div>
<jhi-answer-post-create-edit-modal #createAnswerPostModal [posting]="posting" (postingUpdated)="onPostingUpdated($event)" [createEditAnswerPostContainerRef]="containerRef" />
<jhi-answer-post-create-edit-modal #createAnswerPostModal [posting]="posting" (postingUpdated)="onPostingUpdated($event)" [createEditAnswerPostContainerRef]="containerRef()" />

<!-- Right-Click Dropdown -->
@if (showDropdown) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,16 @@ import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
EventEmitter,
HostListener,
Input,
OnChanges,
OnDestroy,
OnInit,
Output,
Renderer2,
ViewChild,
ViewContainerRef,
inject,
input,
output,
viewChild,
} from '@angular/core';
import { AnswerPost } from 'app/entities/metis/answer-post.model';
import { PostingDirective } from 'app/shared/metis/posting.directive';
Expand Down Expand Up @@ -66,23 +64,23 @@ export class AnswerPostComponent extends PostingDirective<AnswerPost> implements
renderer = inject(Renderer2);
private document = inject<Document>(DOCUMENT);

@Input() lastReadDate?: dayjs.Dayjs;
@Input() isLastAnswer: boolean;
@Output() openPostingCreateEditModal = new EventEmitter<void>();
@Output() userReferenceClicked = new EventEmitter<string>();
@Output() channelReferenceClicked = new EventEmitter<number>();
isAnswerPost = true;
lastReadDate = input<dayjs.Dayjs | undefined>(undefined);
isLastAnswer = input<boolean>(false);
isReadOnlyMode = input<boolean>(false);
isConsecutive = input<boolean>(false);

@Input() isReadOnlyMode = false;
openPostingCreateEditModal = output<void>();
userReferenceClicked = output<string>();
channelReferenceClicked = output<number>();

// ng-container to render answerPostCreateEditModalComponent
@ViewChild('createEditAnswerPostContainer', { read: ViewContainerRef }) containerRef: ViewContainerRef;
@ViewChild(PostingReactionsBarComponent) protected reactionsBarComponent!: PostingReactionsBarComponent<AnswerPost>;
containerRef = viewChild.required('createEditAnswerPostContainer', { read: ViewContainerRef });
reactionsBarComponent = viewChild<PostingReactionsBarComponent<AnswerPost>>(PostingReactionsBarComponent);

isAnswerPost = true;

// Icons
faBookmark = faBookmark;

isConsecutive = input<boolean>(false);
readonly faPencilAlt = faPencilAlt;
readonly faSmile = faSmile;
readonly faTrash = faTrash;
Expand All @@ -100,7 +98,7 @@ export class AnswerPostComponent extends PostingDirective<AnswerPost> implements
}

get reactionsBar() {
return this.reactionsBarComponent;
return this.reactionsBarComponent();
}

onPostingUpdated(updatedPosting: AnswerPost) {
Expand Down
Loading

0 comments on commit 1d2d1c5

Please sign in to comment.