MOBILE-3833 course: Collapse sections in downloads page

main
Dani Palou 2022-03-29 12:39:18 +02:00
parent 37af8e3c69
commit 0298273fc4
7 changed files with 152 additions and 100 deletions

View File

@ -47,7 +47,13 @@
<ng-container *ngFor="let section of sections"> <ng-container *ngFor="let section of sections">
<ion-card class="section" *ngIf="section.modules.length > 0"> <ion-card class="section" *ngIf="section.modules.length > 0">
<ion-card-header> <ion-card-header>
<ion-item class="ion-no-padding" lines="full"> <ion-item class="ion-no-padding" [lines]="section.expanded ? 'full' : 'none'" button detail="false"
(click)="toggleExpand($event, section)" [class.core-course-storage-section-expanded]="section.expanded"
[attr.aria-label]="(section.expanded ? 'core.collapse' : 'core.expand') | translate"
[attr.aria-expanded]="section.expanded" [attr.aria-controls]="'core-course-storage-section-' + section.id">
<ion-icon name="fas-chevron-right" flip-rtl slot="start" class="expandable-status-icon"
[class.expandable-status-icon-expanded]="section.expanded">
</ion-icon>
<ion-label> <ion-label>
<p class="item-heading ion-text-wrap"> <p class="item-heading ion-text-wrap">
<core-format-text [text]="section.name" contextLevel="course" [contextInstanceId]="section.course" <core-format-text [text]="section.name" contextLevel="course" [contextInstanceId]="section.course"
@ -93,7 +99,8 @@
</div> </div>
</ion-item> </ion-item>
</ion-card-header> </ion-card-header>
<ion-card-content> <ion-card-content id="core-course-storage-section-{{section.id}}">
<ng-container *ngIf="section.expanded">
<ng-container *ngFor="let module of section.modules"> <ng-container *ngFor="let module of section.modules">
<ion-item class="ion-no-padding core-course-storage-activity" <ion-item class="ion-no-padding core-course-storage-activity"
*ngIf="downloadEnabled || (!module.calculatingSize && module.totalSize > 0)"> *ngIf="downloadEnabled || (!module.calculatingSize && module.totalSize > 0)">
@ -132,6 +139,7 @@
</div> </div>
</ion-item> </ion-item>
</ng-container> </ng-container>
</ng-container>
</ion-card-content> </ion-card-content>
</ion-card> </ion-card>
</ng-container> </ng-container>

View File

@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
import { CoreConstants } from '@/core/constants'; import { CoreConstants } from '@/core/constants';
import { Component, OnDestroy, OnInit } from '@angular/core'; import { Component, ElementRef, OnDestroy, OnInit } from '@angular/core';
import { CoreCourse, CoreCourseProvider } from '@features/course/services/course'; import { CoreCourse, CoreCourseProvider } from '@features/course/services/course';
import { import {
CoreCourseHelper, CoreCourseHelper,
@ -30,6 +30,7 @@ import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom'; import { CoreDomUtils } from '@services/utils/dom';
import { CoreUtils } from '@services/utils/utils'; import { CoreUtils } from '@services/utils/utils';
import { Translate } from '@singletons'; import { Translate } from '@singletons';
import { CoreDom } from '@singletons/dom';
import { CoreEventObserver, CoreEvents } from '@singletons/events'; import { CoreEventObserver, CoreEvents } from '@singletons/events';
/** /**
@ -62,6 +63,7 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
statusDownloaded = CoreConstants.DOWNLOADED; statusDownloaded = CoreConstants.DOWNLOADED;
protected initialSectionId?: number;
protected siteUpdatedObserver?: CoreEventObserver; protected siteUpdatedObserver?: CoreEventObserver;
protected courseStatusObserver?: CoreEventObserver; protected courseStatusObserver?: CoreEventObserver;
protected sectionStatusObserver?: CoreEventObserver; protected sectionStatusObserver?: CoreEventObserver;
@ -69,7 +71,7 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
protected isDestroyed = false; protected isDestroyed = false;
protected isGuest = false; protected isGuest = false;
constructor() { constructor(protected elementRef: ElementRef) {
// Refresh the enabled flags if site is updated. // Refresh the enabled flags if site is updated.
this.siteUpdatedObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => { this.siteUpdatedObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => {
this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite(); this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite();
@ -100,16 +102,19 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
} }
this.isGuest = !!CoreNavigator.getRouteBooleanParam('isGuest'); this.isGuest = !!CoreNavigator.getRouteBooleanParam('isGuest');
this.initialSectionId = CoreNavigator.getRouteNumberParam('sectionId');
this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite(); this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite();
this.downloadEnabled = !CoreSites.getRequiredCurrentSite().isOfflineDisabled(); this.downloadEnabled = !CoreSites.getRequiredCurrentSite().isOfflineDisabled();
const sections = await CoreCourse.getSections(this.courseId, false, true); const sections = (await CoreCourse.getSections(this.courseId, false, true))
.filter((section) => !CoreCourseHelper.isSectionStealth(section));
this.sections = (await CoreCourseHelper.addHandlerDataForModules(sections, this.courseId)).sections this.sections = (await CoreCourseHelper.addHandlerDataForModules(sections, this.courseId)).sections
.map(section => ({ .map(section => ({
...section, ...section,
totalSize: 0, totalSize: 0,
calculatingSize: true, calculatingSize: true,
expanded: section.id === this.initialSectionId,
modules: section.modules.map(module => ({ modules: section.modules.map(module => ({
...module, ...module,
calculatingSize: true, calculatingSize: true,
@ -118,6 +123,12 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
this.loaded = true; this.loaded = true;
CoreDom.scrollToElement(
this.elementRef.nativeElement,
'.core-course-storage-section-expanded',
{ addYAxis: -10 },
);
await Promise.all([ await Promise.all([
this.initSizes(), this.initSizes(),
this.initCoursePrefetch(), this.initCoursePrefetch(),
@ -641,6 +652,18 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
} }
} }
/**
* Toggle expand status.
*
* @param event Event object.
* @param section Section to expand / collapse.
*/
toggleExpand(event: Event, section: AddonStorageManagerCourseSection): void {
section.expanded = !section.expanded;
event.stopPropagation();
event.preventDefault();
}
/** /**
* @inheritdoc * @inheritdoc
*/ */
@ -663,6 +686,7 @@ export class AddonStorageManagerCourseStoragePage implements OnInit, OnDestroy {
type AddonStorageManagerCourseSection = Omit<CoreCourseSectionWithStatus, 'modules'> & { type AddonStorageManagerCourseSection = Omit<CoreCourseSectionWithStatus, 'modules'> & {
totalSize: number; totalSize: number;
calculatingSize: boolean; calculatingSize: boolean;
expanded: boolean;
modules: AddonStorageManagerModule[]; modules: AddonStorageManagerModule[];
}; };

View File

@ -1,3 +1,8 @@
<core-navbar-buttons slot="end" prepend>
<ion-button fill="clear" (click)="gotoCourseDownloads()" [attr.aria-label]="'addon.storagemanager.coursedownloads' | translate">
<ion-icon name="fas-cloud-download-alt" slot="icon-only" aria-hidden="true"></ion-icon>
</ion-button>
</core-navbar-buttons>
<core-dynamic-component [component]="courseFormatComponent" [data]="data"> <core-dynamic-component [component]="courseFormatComponent" [data]="data">
<!-- Default course format. --> <!-- Default course format. -->
<core-loading [hideUntil]="loaded"> <core-loading [hideUntil]="loaded">

View File

@ -392,15 +392,18 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
} }
/** /**
* Display the course index modal. * Get selected section ID. If viewing all sections, use current scrolled section.
*
* @return Section ID, undefined if not found.
*/ */
async openCourseIndex(): Promise<void> { protected async getSelectedSectionId(): Promise<number | undefined> {
let selectedId = this.selectedSection?.id; if (this.selectedSection?.id !== this.allSectionsId) {
return this.selectedSection?.id;
}
if (selectedId == this.allSectionsId) {
// Check current scrolled section. // Check current scrolled section.
const allSectionElements: NodeListOf<HTMLElement> = const allSectionElements: NodeListOf<HTMLElement> =
this.elementRef.nativeElement.querySelectorAll('section.section-wrapper'); this.elementRef.nativeElement.querySelectorAll('section.core-course-module-list-wrapper');
const scroll = await this.content.getScrollElement(); const scroll = await this.content.getScrollElement();
const containerTop = scroll.getBoundingClientRect().top; const containerTop = scroll.getBoundingClientRect().top;
@ -412,9 +415,15 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
return position.bottom >= containerTop; return position.bottom >= containerTop;
}); });
selectedId = Number(element?.getAttribute('id')) || undefined; return Number(element?.getAttribute('id')) || undefined;
} }
/**
* Display the course index modal.
*/
async openCourseIndex(): Promise<void> {
const selectedId = await this.getSelectedSectionId();
const data = await CoreDomUtils.openModal<CoreCourseIndexSectionWithModule>({ const data = await CoreDomUtils.openModal<CoreCourseIndexSectionWithModule>({
component: CoreCourseCourseIndexComponent, component: CoreCourseCourseIndexComponent,
componentProps: { componentProps: {
@ -453,6 +462,23 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
this.moduleId = data.moduleId; this.moduleId = data.moduleId;
} }
/**
* Open course downloads page.
*/
async gotoCourseDownloads(): Promise<void> {
const selectedId = await this.getSelectedSectionId();
CoreNavigator.navigateToSitePath(
`storage/${this.course.id}`,
{
params: {
title: this.course.fullname,
sectionId: selectedId,
},
},
);
}
/** /**
* Function called when selected section changes. * Function called when selected section changes.
* *

View File

@ -48,14 +48,15 @@
<ion-icon name="fas-eye-slash" *ngIf="!section.visible && section.uservisible" slot="end" class="restricted" <ion-icon name="fas-eye-slash" *ngIf="!section.visible && section.uservisible" slot="end" class="restricted"
[attr.aria-label]="'core.course.hiddenfromstudents' | translate"></ion-icon> [attr.aria-label]="'core.course.hiddenfromstudents' | translate"></ion-icon>
</ion-item> </ion-item>
<div id="core-course-index-section-{{section.id}}">
<ng-container *ngIf="section.expanded"> <ng-container *ngIf="section.expanded">
<ng-container *ngFor="let module of section.modules"> <ng-container *ngFor="let module of section.modules">
<ion-item class="module" [class.item-dimmed]="!module.visible" [class.item-hightlighted]="section.highlighted" <ion-item class="module" [class.item-dimmed]="!module.visible" [class.item-hightlighted]="section.highlighted"
(click)="selectSectionOrModule($event, section.id, module.id)" button> (click)="selectSectionOrModule($event, section.id, module.id)" button>
<ion-icon class="completioninfo completion_none" name="" *ngIf="module.completionStatus === undefined" <ion-icon class="completioninfo completion_none" name="" *ngIf="module.completionStatus === undefined"
slot="start" aria-hidden="true"></ion-icon> slot="start" aria-hidden="true"></ion-icon>
<ion-icon class="completioninfo completion_incomplete" name="far-circle" *ngIf="module.completionStatus === 0" <ion-icon class="completioninfo completion_incomplete" name="far-circle"
slot="start" [attr.aria-label]="'core.course.todo' | translate"> *ngIf="module.completionStatus === 0" slot="start" [attr.aria-label]="'core.course.todo' | translate">
</ion-icon> </ion-icon>
<ion-icon class="completioninfo completion_complete" name="fas-circle" <ion-icon class="completioninfo completion_complete" name="fas-circle"
*ngIf="module.completionStatus === 1 || module.completionStatus === 2" color="success" slot="start" *ngIf="module.completionStatus === 1 || module.completionStatus === 2" color="success" slot="start"
@ -76,6 +77,7 @@
</ion-item> </ion-item>
</ng-container> </ng-container>
</ng-container> </ng-container>
</div>
</ng-container> </ng-container>
</ng-container> </ng-container>
</ion-list> </ion-list>

View File

@ -1,8 +1,3 @@
<core-navbar-buttons slot="end" prepend>
<ion-button fill="clear" (click)="gotoCourseDownloads()" [attr.aria-label]="'addon.storagemanager.coursedownloads' | translate">
<ion-icon name="fas-cloud-download-alt" slot="icon-only" aria-hidden="true"></ion-icon>
</ion-button>
</core-navbar-buttons>
<ion-content> <ion-content>
<ion-refresher slot="fixed" [disabled]="!dataLoaded || !displayRefresher" (ionRefresh)="doRefresh($event.target)"> <ion-refresher slot="fixed" [disabled]="!dataLoaded || !displayRefresher" (ionRefresh)="doRefresh($event.target)">
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>

View File

@ -366,14 +366,6 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy {
} }
} }
gotoCourseDownloads(): void {
CoreNavigator.navigateToSitePath(
`storage/${this.course.id}`,
{ params: { title: this.course.fullname } },
);
}
/** /**
* @inheritdoc * @inheritdoc
*/ */