Skip to content

Commit

Permalink
Learning path: Introduce loading spinner for navigation between learn…
Browse files Browse the repository at this point in the history
…ing objects (#9500)
  • Loading branch information
JohannesWt authored Oct 25, 2024
1 parent 142c0f1 commit f785992
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 175 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,19 @@
<div class="row justify-content-start align-items-center">
@if (predecessorLearningObject(); as predecessorLearningObject) {
<div class="col-md-auto">
<button id="previous-button" (click)="selectLearningObject(predecessorLearningObject)" type="button" class="btn btn-secondary">
<button
id="previous-button"
(click)="selectLearningObject(predecessorLearningObject, false)"
type="button"
class="btn btn-secondary d-flex justify-content-center"
>
<div class="me-1 loading-icon-container">
@if (isLoadingPredecessor()) {
<fa-icon [icon]="faSpinner" animation="spin" />
} @else {
<fa-icon [icon]="faChevronLeft" />
}
</div>
<span jhiTranslate="artemisApp.learningPath.navigation.previousButton"></span>
</button>
</div>
Expand All @@ -13,18 +25,16 @@
</div>
</div>
<div ngbDropdown class="col-4 dropdown" (openChange)="setIsDropdownOpen($event)" #navOverview="ngbDropdown">
@if (!isLoading()) {
<div type="button" class="row justify-content-center align-items-center h-100" id="navigation-overview" ngbDropdownToggle>
@if (currentLearningObject()) {
<span class="col-md-auto fw-bold">
{{ currentLearningObject()?.name }}
</span>
} @else {
<span class="col-md-auto fw-bold" jhiTranslate="artemisApp.learningPath.navigation.recapLabel"></span>
}
<fa-icon [icon]="faChevronDown" class="col-md-auto ps-0" />
</div>
}
<div type="button" class="row justify-content-center align-items-center h-100" id="navigation-overview" ngbDropdownToggle>
@if (currentLearningObject()) {
<span class="col-md-auto fw-bold">
{{ currentLearningObject()?.name }}
</span>
} @else {
<span class="col-md-auto fw-bold" jhiTranslate="artemisApp.learningPath.navigation.recapLabel"></span>
}
<fa-icon [icon]="faChevronDown" class="col-md-auto ps-0" />
</div>
<div ngbDropdownMenu class="mt-3 col p-0" aria-labelledby="navigation-overview">
@if (isDropdownOpen()) {
<jhi-learning-path-nav-overview (onLearningObjectSelected)="navOverview.close()" [learningPathId]="learningPathId()" />
Expand All @@ -36,8 +46,15 @@
@if (successorLearningObject(); as successorLearningObject) {
<span class="col text-end pe-3 text-truncate text-secondary">{{ successorLearningObject.name }}</span>
<div class="col-md-auto">
<button id="next-button" (click)="selectLearningObject(successorLearningObject)" type="button" class="btn btn-primary">
<button id="next-button" (click)="selectLearningObject(successorLearningObject, true)" type="button" class="btn btn-primary d-flex justify-content-center">
<span jhiTranslate="artemisApp.learningPath.navigation.nextButton"></span>
<div class="ms-1 loading-icon-container">
@if (isLoadingSuccessor()) {
<fa-icon [icon]="faSpinner" animation="spin" />
} @else {
<fa-icon [icon]="faChevronRight" />
}
</div>
</button>
</div>
} @else if (currentLearningObject() && !successorLearningObject()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,7 @@
width: 500px;
box-shadow: 0 8px 12px 0 var(--lecture-unit-card-shadow);
}

.loading-icon-container {
width: 15px;
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Component, InputSignal, Signal, WritableSignal, computed, effect, inject, input, signal } from '@angular/core';
import { Component, computed, effect, inject, input, signal, untracked } from '@angular/core';
import { LearningPathNavigationObjectDTO } from 'app/entities/competency/learning-path.model';
import { CommonModule } from '@angular/common';
import { NgbAccordionModule, NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
import { IconDefinition, faCheckCircle, faChevronDown, faFlag } from '@fortawesome/free-solid-svg-icons';
import { faCheckCircle, faChevronDown, faChevronLeft, faChevronRight, faFlag, faSpinner } from '@fortawesome/free-solid-svg-icons';
import { LearningPathNavOverviewComponent } from 'app/course/learning-paths/components/learning-path-nav-overview/learning-path-nav-overview.component';
import { ArtemisSharedModule } from 'app/shared/shared.module';
import { LearningPathNavigationService } from 'app/course/learning-paths/services/learning-path-navigation.service';
Expand All @@ -16,33 +16,44 @@ import { LearningPathNavigationService } from 'app/course/learning-paths/service
styleUrl: './learning-path-student-nav.component.scss',
})
export class LearningPathNavComponent {
protected readonly faChevronDown: IconDefinition = faChevronDown;
protected readonly faCheckCircle: IconDefinition = faCheckCircle;
protected readonly faFlag: IconDefinition = faFlag;
protected readonly faChevronDown = faChevronDown;
protected readonly faCheckCircle = faCheckCircle;
protected readonly faFlag = faFlag;
protected readonly faSpinner = faSpinner;
protected readonly faChevronLeft = faChevronLeft;
protected readonly faChevronRight = faChevronRight;

private learningPathNavigationService: LearningPathNavigationService = inject(LearningPathNavigationService);
private learningPathNavigationService = inject(LearningPathNavigationService);

readonly learningPathId: InputSignal<number> = input.required<number>();
readonly learningPathId = input.required<number>();

readonly isLoading: WritableSignal<boolean> = this.learningPathNavigationService.isLoading;
readonly isLoading = this.learningPathNavigationService.isLoading;
readonly isLoadingPredecessor = signal<boolean>(false);
readonly isLoadingSuccessor = signal<boolean>(false);

readonly learningPathProgress: Signal<number> = computed(() => this.learningPathNavigationService.learningPathNavigation()?.progress ?? 0);
readonly predecessorLearningObject: Signal<LearningPathNavigationObjectDTO | undefined> = computed(
() => this.learningPathNavigationService.learningPathNavigation()?.predecessorLearningObject,
);
readonly currentLearningObject: Signal<LearningPathNavigationObjectDTO | undefined> = computed(() => this.learningPathNavigationService.currentLearningObject());
readonly successorLearningObject: Signal<LearningPathNavigationObjectDTO | undefined> = computed(
() => this.learningPathNavigationService.learningPathNavigation()?.successorLearningObject,
);
private readonly learningPathNavigation = this.learningPathNavigationService.learningPathNavigation;
readonly learningPathProgress = computed(() => this.learningPathNavigation()?.progress ?? 0);
readonly predecessorLearningObject = computed(() => this.learningPathNavigation()?.predecessorLearningObject);
readonly currentLearningObject = computed(() => this.learningPathNavigation()?.currentLearningObject);
readonly successorLearningObject = computed(() => this.learningPathNavigation()?.successorLearningObject);

readonly isDropdownOpen: WritableSignal<boolean> = signal(false);
readonly isDropdownOpen = signal<boolean>(false);

constructor() {
effect(async () => await this.learningPathNavigationService.loadLearningPathNavigation(this.learningPathId()), { allowSignalWrites: true });
effect(
() => {
const learningPathId = this.learningPathId();
untracked(() => this.learningPathNavigationService.loadLearningPathNavigation(learningPathId));
},
{ allowSignalWrites: true },
);
}

async selectLearningObject(selectedLearningObject: LearningPathNavigationObjectDTO): Promise<void> {
async selectLearningObject(selectedLearningObject: LearningPathNavigationObjectDTO, isSuccessor: boolean): Promise<void> {
const loadingSpinner = isSuccessor ? this.isLoadingSuccessor : this.isLoadingPredecessor;
loadingSpinner.set(true);
await this.learningPathNavigationService.loadRelativeLearningPathNavigation(this.learningPathId(), selectedLearningObject);
loadingSpinner.set(false);
}

completeLearningPath(): void {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import { Injectable, Signal, WritableSignal, computed, inject, signal } from '@angular/core';
import { Injectable, computed, inject, signal } from '@angular/core';
import { LearningPathNavigationDTO, LearningPathNavigationObjectDTO } from 'app/entities/competency/learning-path.model';
import { AlertService } from 'app/core/util/alert.service';
import { LearningPathApiService } from 'app/course/learning-paths/services/learning-path-api.service';

@Injectable({ providedIn: 'root' })
export class LearningPathNavigationService {
private readonly learningPathApiService: LearningPathApiService = inject(LearningPathApiService);
private readonly alertService: AlertService = inject(AlertService);
private readonly learningPathApiService = inject(LearningPathApiService);
private readonly alertService = inject(AlertService);

readonly isLoading: WritableSignal<boolean> = signal(false);
readonly isLoading = signal<boolean>(false);

readonly learningPathNavigation: WritableSignal<LearningPathNavigationDTO | undefined> = signal(undefined);
readonly currentLearningObject: Signal<LearningPathNavigationObjectDTO | undefined> = computed(() => this.learningPathNavigation()?.currentLearningObject);
readonly learningPathNavigation = signal<LearningPathNavigationDTO | undefined>(undefined);
readonly currentLearningObject = computed(() => this.learningPathNavigation()?.currentLearningObject);

readonly isCurrentLearningObjectCompleted: WritableSignal<boolean> = signal(false);
readonly isCurrentLearningObjectCompleted = signal<boolean>(false);

async loadLearningPathNavigation(learningPathId: number): Promise<void> {
try {
Expand Down
Loading

0 comments on commit f785992

Please sign in to comment.