Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Learning path: Introduce loading spinner for navigation between learning objects #9500

Merged
merged 6 commits into from
Oct 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
}
JohannesWt marked this conversation as resolved.
Show resolved Hide resolved

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
Loading