Skip to content

Commit

Permalink
Communication: Improve sidebar user interface design (#9356)
Browse files Browse the repository at this point in the history
  • Loading branch information
asliayk authored Oct 19, 2024
1 parent a0d7a97 commit 0a82c75
Show file tree
Hide file tree
Showing 42 changed files with 481 additions and 454 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,14 @@
[itemSelected]="conversationSelected"
[courseId]="course.id"
[sidebarData]="sidebarData"
(onPlusPressed)="onAccordionPlusButtonPressed($event)"
(onCreateChannelPressed)="openCreateChannelDialog()"
(onBrowsePressed)="openChannelOverviewDialog()"
(onDirectChatPressed)="openCreateOneToOneChatDialog()"
(onGroupChatPressed)="openCreateGroupChatDialog()"
[showAddOption]="CHANNEL_TYPE_SHOW_ADD_OPTION"
[channelTypeIcon]="CHANNEL_TYPE_ICON"
[collapseState]="DEFAULT_COLLAPSE_STATE"
[inCommunication]="true"
/>
</div>
@if (course && !activeConversation && isCodeOfConductPresented) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Component, ElementRef, HostListener, OnDestroy, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import { Component, ElementRef, EventEmitter, HostListener, OnDestroy, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import { ConversationDTO } from 'app/entities/metis/conversation/conversation.model';
import { Post } from 'app/entities/metis/post.model';
import { ActivatedRoute, Router } from '@angular/router';
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
import { EMPTY, Subject, Subscription, from, take, takeUntil } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { EMPTY, Observable, Subject, Subscription, from, take, takeUntil } from 'rxjs';
import { catchError, debounceTime, distinctUntilChanged } from 'rxjs/operators';
import { MetisConversationService } from 'app/shared/metis/metis-conversation.service';
import { ChannelSubType, getAsChannelDTO } from 'app/entities/metis/conversation/channel.model';
import { ChannelDTO, ChannelSubType, getAsChannelDTO } from 'app/entities/metis/conversation/channel.model';
import { MetisService } from 'app/shared/metis/metis.service';
import { Course, isMessagingEnabled } from 'app/entities/course.model';
import { PageType, SortDirection } from 'app/shared/metis/metis.util';
Expand All @@ -16,11 +16,12 @@ import { CourseWideSearchComponent, CourseWideSearchConfig } from 'app/overview/
import { AccordionGroups, ChannelAccordionShowAdd, ChannelTypeIcons, CollapseState, SidebarCardElement, SidebarData } from 'app/types/sidebar';
import { CourseOverviewService } from 'app/overview/course-overview.service';
import { GroupChatCreateDialogComponent } from 'app/overview/course-conversations/dialogs/group-chat-create-dialog/group-chat-create-dialog.component';
import { defaultFirstLayerDialogOptions } from 'app/overview/course-conversations/other/conversation.util';
import { defaultFirstLayerDialogOptions, defaultSecondLayerDialogOptions } from 'app/overview/course-conversations/other/conversation.util';
import { UserPublicInfoDTO } from 'app/core/user/user.model';
import { OneToOneChatCreateDialogComponent } from 'app/overview/course-conversations/dialogs/one-to-one-chat-create-dialog/one-to-one-chat-create-dialog.component';
import { ChannelsOverviewDialogComponent } from 'app/overview/course-conversations/dialogs/channels-overview-dialog/channels-overview-dialog.component';
import { ChannelAction, ChannelsOverviewDialogComponent } from 'app/overview/course-conversations/dialogs/channels-overview-dialog/channels-overview-dialog.component';
import { ProfileService } from 'app/shared/layouts/profiles/profile.service';
import { ChannelsCreateDialogComponent } from 'app/overview/course-conversations/dialogs/channels-create-dialog/channels-create-dialog.component';

const DEFAULT_CHANNEL_GROUPS: AccordionGroups = {
favoriteChannels: { entityData: [] },
Expand Down Expand Up @@ -114,7 +115,9 @@ export class CourseConversationsComponent implements OnInit, OnDestroy {
faFilter = faFilter;
faSearch = faSearch;

// MetisConversationService is created in course overview, so we can use it here
createChannelFn?: (channel: ChannelDTO) => Observable<never>;
channelActions$ = new EventEmitter<ChannelAction>();

constructor(
private router: Router,
private activatedRoute: ActivatedRoute,
Expand Down Expand Up @@ -162,6 +165,16 @@ export class CourseConversationsComponent implements OnInit, OnDestroy {
this.isServiceSetUp = true;
this.isLoading = false;
}
this.channelActions$
.pipe(
debounceTime(500),
distinctUntilChanged((prev, curr) => prev.action === curr.action && prev.channel.id === curr.channel.id),
takeUntil(this.ngUnsubscribe),
)
.subscribe((channelAction) => {
this.performChannelAction(channelAction);
});
this.createChannelFn = (channel: ChannelDTO) => this.metisConversationService.createChannel(channel);
});

this.profileSubscription = this.profileService.getProfileInfo()?.subscribe((profileInfo) => {
Expand All @@ -170,6 +183,21 @@ export class CourseConversationsComponent implements OnInit, OnDestroy {
});
}

performChannelAction(channelAction: ChannelAction) {
if (this.createChannelFn) {
this.createChannelFn(channelAction.channel)
.pipe(takeUntil(this.ngUnsubscribe))
.subscribe({
complete: () => {
this.prepareSidebarData();
},
error: (error) => {
console.error('Error creating channel:', error);
},
});
}
}

subscribeToQueryParameter() {
this.activatedRoute.queryParams.pipe(take(1), takeUntil(this.ngUnsubscribe)).subscribe((queryParams) => {
if (queryParams.conversationId) {
Expand Down Expand Up @@ -270,8 +298,8 @@ export class CourseConversationsComponent implements OnInit, OnDestroy {
storageId: 'conversation',
groupedData: this.accordionConversationGroups,
ungroupedData: this.sidebarConversations,
showAccordionAddOption: true,
showAccordionLeadingIcon: true,
messagingEnabled: isMessagingEnabled(this.course),
};
}

Expand All @@ -284,16 +312,6 @@ export class CourseConversationsComponent implements OnInit, OnDestroy {
this.courseOverviewService.setSidebarCollapseState('conversation', this.isCollapsed);
}

onAccordionPlusButtonPressed(chatType: string) {
if (chatType === 'groupChats') {
this.openCreateGroupChatDialog();
} else if (chatType === 'directMessages') {
this.openCreateOneToOneChatDialog();
} else {
this.openChannelOverviewDialog(chatType);
}
}

openCreateGroupChatDialog() {
const modalRef: NgbModalRef = this.modalService.open(GroupChatCreateDialogComponent, defaultFirstLayerDialogOptions);
modalRef.componentInstance.course = this.course;
Expand Down Expand Up @@ -332,8 +350,22 @@ export class CourseConversationsComponent implements OnInit, OnDestroy {
});
}

openChannelOverviewDialog(groupKey: string) {
const subType = this.getChannelSubType(groupKey);
openCreateChannelDialog() {
const modalRef: NgbModalRef = this.modalService.open(ChannelsCreateDialogComponent, defaultSecondLayerDialogOptions);
modalRef.componentInstance.course = this.course;
modalRef.componentInstance.initialize();
from(modalRef.result)
.pipe(
catchError(() => EMPTY),
takeUntil(this.ngUnsubscribe),
)
.subscribe((channel: ChannelDTO) => {
this.channelActions$.emit({ action: 'create', channel });
});
}

openChannelOverviewDialog() {
const subType = null;
const modalRef: NgbModalRef = this.modalService.open(ChannelsOverviewDialogComponent, defaultFirstLayerDialogOptions);
modalRef.componentInstance.course = this.course;
modalRef.componentInstance.createChannelFn = subType === ChannelSubType.GENERAL ? this.metisConversationService.createChannel : undefined;
Expand Down Expand Up @@ -363,22 +395,6 @@ export class CourseConversationsComponent implements OnInit, OnDestroy {
});
}

getChannelSubType(groupKey: string) {
if (groupKey === 'exerciseChannels') {
return ChannelSubType.EXERCISE;
}
if (groupKey === 'generalChannels') {
return ChannelSubType.GENERAL;
}
if (groupKey === 'lectureChannels') {
return ChannelSubType.LECTURE;
}
if (groupKey === 'examChannels') {
return ChannelSubType.EXAM;
}
return ChannelSubType.GENERAL;
}

toggleChannelSearch() {
this.channelSearchCollapsed = !this.channelSearchCollapsed;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
@if (isInitialized) {
<div class="channels-overview">
<div class="channels-overview" ngbAutofocus>
<div class="modal-header">
<h4 class="modal-title">
<span
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.channel-item {
padding: 0.5rem;
.interaction {
visibility: hidden;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
<div class="channels-overview">
<div class="modal-header">
<h4 class="modal-title">
<span>{{ 'artemisApp.dialogs.channelOverview.title.' + channelSubType | artemisTranslate: { courseTitle: course.title } }}</span>
<span>{{ 'artemisApp.dialogs.channelOverview.title' | artemisTranslate: { courseTitle: course.title } }}</span>
</h4>
<button type="button" class="btn-close" (click)="clear()"></button>
</div>
<div class="modal-body">
<!-- Overview Table -->
@if (channels && channels.length > 0) {
<div class="form-group mt-4">
<div class="form-group mt-2">
<div class="table-wrapper-scroll-y scrollbar">
<ul class="list-group">
@for (channel of channels; track channels.indexOf(channel)) {
Expand All @@ -22,36 +22,6 @@ <h4 class="modal-title">
</div>
</div>
}
@if (otherChannels && otherChannels.length > 0) {
<div class="form-group mt-4">
<div (click)="otherChannelsAreCollapsed = !otherChannelsAreCollapsed" class="other-channels">
<fa-icon [icon]="faChevronRight" [rotate]="!otherChannelsAreCollapsed ? 90 : undefined" />
<span class="h5" jhiTranslate="artemisApp.dialogs.channelOverview.otherChannels"></span>
</div>
<div [(ngbCollapse)]="otherChannelsAreCollapsed">
<div class="table-wrapper-scroll-y scrollbar mt-2">
<ul class="list-group">
@for (channel of otherChannels; track otherChannels.indexOf(channel)) {
<li [id]="'channel-' + channel.id" class="list-group-item">
<jhi-channel-item [channel]="channel" (channelAction)="onChannelAction($event)" />
</li>
}
</ul>
</div>
</div>
</div>
}
</div>
<div class="modal-footer justify-content-between">
@if (createChannelFn && canCreateChannel(course)) {
<button
type="button"
class="btn btn-secondary"
(click)="openCreateChannelDialog($event)"
id="createChannel"
jhiTranslate="artemisApp.dialogs.channelOverview.createChannelButton"
></button>
}
</div>
</div>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

.list-group-item {
overflow: hidden;
padding: 0;
}

.other-channels {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
import { Component, Input, OnDestroy, OnInit } from '@angular/core';
import { EMPTY, Observable, Subject, debounceTime, distinctUntilChanged, finalize, from, map, takeUntil } from 'rxjs';
import { Observable, Subject, debounceTime, distinctUntilChanged, finalize, map, takeUntil } from 'rxjs';
import { faChevronRight } from '@fortawesome/free-solid-svg-icons';

import { HttpErrorResponse, HttpResponse } from '@angular/common/http';
import { onError } from 'app/shared/util/global.utils';
import { AlertService } from 'app/core/util/alert.service';
import { NgbActiveModal, NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { ChannelService } from 'app/shared/metis/conversations/channel.service';
import { ChannelDTO, ChannelSubType } from 'app/entities/metis/conversation/channel.model';
import { Course } from 'app/entities/course.model';
import { ChannelsCreateDialogComponent } from 'app/overview/course-conversations/dialogs/channels-create-dialog/channels-create-dialog.component';
import { canCreateChannel } from 'app/shared/metis/conversations/conversation-permissions.utils';
import { AbstractDialogComponent } from 'app/overview/course-conversations/dialogs/abstract-dialog.component';
import { defaultSecondLayerDialogOptions } from 'app/overview/course-conversations/other/conversation.util';
import { catchError } from 'rxjs/operators';

export type ChannelActionType = 'register' | 'deregister' | 'view' | 'create';
export type ChannelAction = {
Expand Down Expand Up @@ -44,12 +40,10 @@ export class ChannelsOverviewDialogComponent extends AbstractDialogComponent imp
channelModificationPerformed = false;
isLoading = false;
channels: ChannelDTO[] = [];
otherChannels: ChannelDTO[] = [];

isInitialized = false;

faChevronRight = faChevronRight;
otherChannelsAreCollapsed = true;

initialize() {
super.initialize(['course', 'channelSubType']);
Expand All @@ -61,7 +55,6 @@ export class ChannelsOverviewDialogComponent extends AbstractDialogComponent imp
constructor(
private channelService: ChannelService,
private alertService: AlertService,
private modalService: NgbModal,

activeModal: NgbActiveModal,
) {
Expand Down Expand Up @@ -114,18 +107,6 @@ export class ChannelsOverviewDialogComponent extends AbstractDialogComponent imp
case 'view':
this.close([channelAction.channel, this.channelModificationPerformed]);
break;
case 'create':
if (this.createChannelFn) {
this.createChannelFn(channelAction.channel)
.pipe(takeUntil(this.ngUnsubscribe))
.subscribe({
complete: () => {
this.loadChannelsOfCourse();
this.channelModificationPerformed = true;
},
});
}
break;
}
}

Expand All @@ -142,28 +123,12 @@ export class ChannelsOverviewDialogComponent extends AbstractDialogComponent imp
)
.subscribe({
next: (channels: ChannelDTO[]) => {
this.channels = channels?.filter((channel) => channel.subType === this.channelSubType) ?? [];
this.otherChannels = channels?.filter((channel) => channel.subType !== this.channelSubType) ?? [];
this.channels = channels;
this.noOfChannels = this.channels.length;
},
error: (errorResponse: HttpErrorResponse) => {
onError(this.alertService, errorResponse);
},
});
}

openCreateChannelDialog(event: MouseEvent) {
event.stopPropagation();
const modalRef: NgbModalRef = this.modalService.open(ChannelsCreateDialogComponent, defaultSecondLayerDialogOptions);
modalRef.componentInstance.course = this.course;
modalRef.componentInstance.initialize();
from(modalRef.result)
.pipe(
catchError(() => EMPTY),
takeUntil(this.ngUnsubscribe),
)
.subscribe((channel: ChannelDTO) => {
this.channelActions$.next({ action: 'create', channel });
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
(delete)="deleteChannel()"
[dialogError]="dialogError$"
>
<fa-icon [icon]="faTimes" />
<fa-icon [icon]="faTrash" />
</button>
</div>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { onError } from 'app/shared/util/global.utils';
import { EMPTY, Subject, from, takeUntil } from 'rxjs';
import { HttpErrorResponse } from '@angular/common/http';
import { AlertService } from 'app/core/util/alert.service';
import { faTimes } from '@fortawesome/free-solid-svg-icons';
import { faTrash } from '@fortawesome/free-solid-svg-icons';
import { canChangeChannelArchivalState, canDeleteChannel, canLeaveConversation } from 'app/shared/metis/conversations/conversation-permissions.utils';
import { GroupChatService } from 'app/shared/metis/conversations/group-chat.service';
import { isGroupChatDTO } from 'app/entities/metis/conversation/group-chat.model';
Expand Down Expand Up @@ -42,7 +42,7 @@ export class ConversationSettingsComponent implements OnInit, OnDestroy {
private dialogErrorSource = new Subject<string>();
dialogError$ = this.dialogErrorSource.asObservable();

faTimes = faTimes;
readonly faTrash = faTrash;

conversationAsChannel: ChannelDTO | undefined;
canLeaveConversation: boolean;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
@if (isInitialized) {
<div>
<div ngbAutofocus>
<div class="modal-header">
<h4 class="modal-title">
<span jhiTranslate="artemisApp.dialogs.createGroupChat.title"></span>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
@if (isInitialized) {
<div>
<div ngbAutofocus>
<div class="modal-header">
<h4 class="modal-title">
<span jhiTranslate="artemisApp.dialogs.createOneToOneChat.title"></span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
[resultFormatter]="usersFormatter"
[editable]="false"
[focusFirst]="false"
(input)="onInputChange($event)"
placement="bottom-start"
#instance="ngbTypeahead"
/>
Expand Down
Loading

0 comments on commit 0a82c75

Please sign in to comment.