MOBILE-3931 module: Add a new module summary page

main
Pau Ferrer Ocaña 2022-02-17 15:55:10 +01:00
parent 279071634b
commit d224876f42
30 changed files with 559 additions and 14 deletions

View File

@ -24,6 +24,10 @@
iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
</core-context-menu-item>
</core-context-menu>
<ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.course.modulesummary' | translate">
<ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
</ion-button>
</core-navbar-buttons>
<!-- Content. -->

View File

@ -17,6 +17,10 @@
[iconAction]="prefetchStatusIcon" [closeOnClick]="false">
</core-context-menu-item>
</core-context-menu>
<ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.course.modulesummary' | translate">
<ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
</ion-button>
</core-navbar-buttons>
<!-- Content. -->

View File

@ -15,6 +15,10 @@
iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
</core-context-menu-item>
</core-context-menu>
<ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.course.modulesummary' | translate">
<ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
</ion-button>
</core-navbar-buttons>
<!-- Content. -->

View File

@ -21,6 +21,10 @@
[iconAction]="prefetchStatusIcon" [closeOnClick]="false">
</core-context-menu-item>
</core-context-menu>
<ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.course.modulesummary' | translate">
<ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
</ion-button>
</core-navbar-buttons>
<!-- Content. -->

View File

@ -23,6 +23,10 @@
iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
</core-context-menu-item>
</core-context-menu>
<ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.course.modulesummary' | translate">
<ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
</ion-button>
</core-navbar-buttons>
<!-- Content. -->

View File

@ -33,6 +33,10 @@
iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
</core-context-menu-item>
</core-context-menu>
<ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.course.modulesummary' | translate">
<ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
</ion-button>
</core-navbar-buttons>
<!-- Content. -->

View File

@ -23,6 +23,10 @@
iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
</core-context-menu-item>
</core-context-menu>
<ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.course.modulesummary' | translate">
<ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
</ion-button>
</core-navbar-buttons>
<!-- Content. -->

View File

@ -20,6 +20,10 @@
iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
</core-context-menu-item>
</core-context-menu>
<ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.course.modulesummary' | translate">
<ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
</ion-button>
</core-navbar-buttons>
<!-- Content. -->

View File

@ -28,6 +28,10 @@
(action)="showSortOrderSelector()">
</core-context-menu-item>
</core-context-menu>
<ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.course.modulesummary' | translate">
<ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
</ion-button>
</core-navbar-buttons>
<!-- Content. -->

View File

@ -36,6 +36,10 @@
iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
</core-context-menu-item>
</core-context-menu>
<ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.course.modulesummary' | translate">
<ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
</ion-button>
</core-navbar-buttons>
<!-- Content. -->

View File

@ -30,6 +30,10 @@
iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
</core-context-menu-item>
</core-context-menu>
<ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.course.modulesummary' | translate">
<ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
</ion-button>
</core-navbar-buttons>
<!-- Content. -->

View File

@ -23,6 +23,10 @@
iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
</core-context-menu-item>
</core-context-menu>
<ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.course.modulesummary' | translate">
<ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
</ion-button>
</core-navbar-buttons>
<!-- Content. -->

View File

@ -23,6 +23,10 @@
iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
</core-context-menu-item>
</core-context-menu>
<ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.course.modulesummary' | translate">
<ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
</ion-button>
</core-navbar-buttons>
<!-- Content. -->

View File

@ -14,6 +14,10 @@
(action)="doRefresh(null, $event)" [iconAction]="refreshIcon" [closeOnClick]="false">
</core-context-menu-item>
</core-context-menu>
<ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.course.modulesummary' | translate">
<ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
</ion-button>
</core-navbar-buttons>
<!-- Content. -->

View File

@ -20,6 +20,10 @@
iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
</core-context-menu-item>
</core-context-menu>
<ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.course.modulesummary' | translate">
<ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
</ion-button>
</core-navbar-buttons>
<!-- Content. -->

View File

@ -23,6 +23,10 @@
iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
</core-context-menu-item>
</core-context-menu>
<ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.course.modulesummary' | translate">
<ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
</ion-button>
</core-navbar-buttons>
<!-- Content. -->

View File

@ -15,6 +15,10 @@
iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
</core-context-menu-item>
</core-context-menu>
<ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.course.modulesummary' | translate">
<ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
</ion-button>
</core-navbar-buttons>
<!-- Content. -->

View File

@ -23,6 +23,10 @@
iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
</core-context-menu-item>
</core-context-menu>
<ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.course.modulesummary' | translate">
<ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
</ion-button>
</core-navbar-buttons>
<!-- Content. -->

View File

@ -24,6 +24,10 @@
iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
</core-context-menu-item>
</core-context-menu>
<ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.course.modulesummary' | translate">
<ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
</ion-button>
</core-navbar-buttons>
<!-- Content. -->

View File

@ -10,6 +10,10 @@
<core-context-menu-item [priority]="700" [content]="'core.refresh' | translate" (action)="doRefresh(null, $event)"
[iconAction]="refreshIcon" [closeOnClick]="false"></core-context-menu-item>
</core-context-menu>
<ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.course.modulesummary' | translate">
<ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
</ion-button>
</core-navbar-buttons>
<!-- Content. -->

View File

@ -42,6 +42,10 @@
iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
</core-context-menu-item>
</core-context-menu>
<ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.course.modulesummary' | translate">
<ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
</ion-button>
</core-navbar-buttons>
<!-- Content. -->

View File

@ -24,6 +24,10 @@
iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
</core-context-menu-item>
</core-context-menu>
<ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.course.modulesummary' | translate">
<ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
</ion-button>
</core-navbar-buttons>
<!-- Content. -->

View File

@ -1,8 +1,5 @@
<ion-header>
<ion-header class="no-title">
<ion-toolbar>
<ion-title>
<h2>{{ 'core.block.blocks' | translate }}</h2>
</ion-title>
<ion-buttons slot="end">
<ion-button fill="clear" (click)="closeModal()" [attr.aria-label]="'core.close' | translate">
<ion-icon name="fas-times" slot="icon-only" aria-hidden=true></ion-icon>
@ -10,7 +7,7 @@
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-content [fullscreen]="true">
<ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="doRefresh($event.target)">
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
</ion-refresher>

View File

@ -42,7 +42,6 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR
// Data for context menu.
syncIcon?: string; // Sync icon.
hasOffline?: boolean; // If it has offline data to be synced.
isOnline?: boolean; // If the app is online or not.
protected syncObserver?: CoreEventObserver; // It will observe the sync auto event.

View File

@ -29,6 +29,7 @@ import { CoreUtils } from '@services/utils/utils';
import { Translate } from '@singletons';
import { CoreEventObserver, CoreEvents } from '@singletons/events';
import { CoreLogger } from '@singletons/logger';
import { CoreCourseModuleSummaryComponent, CoreCourseModuleSummaryResult } from '../components/module-summary/module-summary';
import { CoreCourseContentsPage } from '../pages/contents/contents';
import { CoreCourse } from '../services/course';
import { CoreCourseHelper, CoreCourseModuleData } from '../services/course-helper';
@ -58,6 +59,7 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
loaded = false; // If the component has been loaded.
component?: string; // Component name.
componentId?: number; // Component ID.
hasOffline = false; // Resources don't have any data to sync.
blog?: boolean; // If blog is available.
// Data for context menu.
@ -253,16 +255,11 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
/**
* Expand the description.
*
* @deprecated Use openModuleSummary instead.
*/
expandDescription(): void {
CoreTextUtils.viewText(Translate.instant('core.description'), this.description!, {
component: this.component,
componentId: this.module.id,
filter: true,
contextLevel: 'module',
instanceId: this.module.id,
courseId: this.courseId,
});
this.openModuleSummary();
}
/**
@ -449,6 +446,48 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
this.module = module;
}
/**
* Opens a module summary page.
*/
async openModuleSummary(): Promise<void> {
if (!this.module) {
return;
}
const data = await CoreDomUtils.openSideModal<CoreCourseModuleSummaryResult>({
component: CoreCourseModuleSummaryComponent,
componentProps: {
moduleId: this.module.id,
module: this.module,
description: this.description,
component: this.component,
courseId: this.courseId,
hasOffline: this.hasOffline,
},
});
if (data) {
if (data.action == 'refresh') {
const modal = await CoreDomUtils.showModalLoading();
try {
await this.doRefresh();
} finally {
modal.dismiss();
}
} else if(data.action == 'sync') {
const modal = await CoreDomUtils.showModalLoading();
try {
await this.doRefresh( undefined, undefined, true);
} finally {
modal.dismiss();
}
}
}
}
/**
* Component being destroyed.
*/

View File

@ -27,6 +27,7 @@ import { CoreCourseModuleCompletionLegacyComponent } from './module-completion-l
import { CoreCourseModuleInfoComponent } from './module-info/module-info';
import { CoreCourseModuleManualCompletionComponent } from './module-manual-completion/module-manual-completion';
import { CoreCourseModuleNavigationComponent } from './module-navigation/module-navigation';
import { CoreCourseModuleSummaryComponent } from './module-summary/module-summary';
@NgModule({
declarations: [
@ -41,6 +42,7 @@ import { CoreCourseModuleNavigationComponent } from './module-navigation/module-
CoreCourseTagAreaComponent,
CoreCourseUnsupportedModuleComponent,
CoreCourseModuleNavigationComponent,
CoreCourseModuleSummaryComponent,
],
imports: [
CoreBlockComponentsModule,
@ -58,6 +60,7 @@ import { CoreCourseModuleNavigationComponent } from './module-navigation/module-
CoreCourseTagAreaComponent,
CoreCourseUnsupportedModuleComponent,
CoreCourseModuleNavigationComponent,
CoreCourseModuleSummaryComponent,
],
})
export class CoreCourseComponentsModule {}

View File

@ -0,0 +1,82 @@
<ion-header class="no-title">
<ion-toolbar>
<ion-buttons slot="end">
<ion-button fill="clear" (click)="closeModal()" [attr.aria-label]="'core.close' | translate">
<ion-icon slot="icon-only" name="fas-times" aria-hidden="true"></ion-icon>
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content [fullscreen]="true">
<!-- Content. -->
<core-loading [hideUntil]="loaded">
<!-- Activity info. -->
<ion-item class="ion-text-wrap" *ngIf="module">
<ion-label>
<h1>
<core-format-text [text]="module.name" contextLevel="module" [component]="component" [componentId]="componentId"
[contextInstanceId]="module.id" [courseId]="courseId">
</core-format-text>
</h1>
</ion-label>
<ion-button fill="clear" [href]="externalUrl" core-link [showBrowserWarning]="false" color="dark"
[attr.aria-label]="'core.openinbrowser' | translate" slot="end">
<ion-icon name="fas-external-link-alt" slot="icon-only" aria-hidden="true"></ion-icon>
</ion-button>
</ion-item>
<ion-item class="ion-text-wrap" *ngIf="module && description">
<ion-label>
<core-format-text [text]="description" [component]="component" [componentId]="componentId" contextLevel="module"
[contextInstanceId]="module.id" [courseId]="courseId" [maxHeight]="120">
</core-format-text>
</ion-label>
</ion-item>
<ion-item button class="ion-margin" *ngIf="prefetchText" class="ion-text-wrap">
<ion-label>
<p class="item-heading ion-text-wrap">{{ prefetchText }}</p>
<p *ngIf="downloadTimeReadable">{{ downloadTimeReadable }}</p>
</ion-label>
<ion-button *ngIf="prefetchStatusIcon && prefetchStatusIcon != 'spinner'" (click)="prefetch()" color="primary" fill="outline"
[attr.aria-label]="'core.download' | translate" slot="end">
<ion-icon [name]="prefetchStatusIcon" slot="icon-only" aria-hidden="true">
</ion-icon>
</ion-button>
<ion-spinner *ngIf="prefetchStatusIcon == 'spinner'" slot="end" aria-hidden="true"></ion-spinner>
</ion-item>
<ion-item button class="ion-margin" *ngIf="sizeReadable" class="ion-text-wrap">
<ion-label>
<p class="item-heading ion-text-wrap">{{ 'addon.storagemanager.totalspaceusage' | translate }}</p>
<ion-badge color="light">{{ sizeReadable | coreBytesToSize }}</ion-badge>
</ion-label>
<ion-button *ngIf="!removeFilesLoading && size > 0" (click)="removeFiles()" color="danger" fill="outline"
[attr.aria-label]="'core.clearstoreddata' | translate:{$a: sizeReadable}" slot="end">
<ion-icon name="fas-trash" slot="icon-only" aria-hidden="true"></ion-icon>
</ion-button>
<ion-spinner *ngIf="removeFilesLoading" slot="end" aria-hidden="true"></ion-spinner>
</ion-item>
<ion-button *ngIf="blog" class="ion-margin" (click)="gotoBlog()" expand="block" fill="outline">
<ion-icon name="far-newspaper" slot="start" aria-hidden="true"></ion-icon>
<ion-label>
{{ 'addon.blog.blog' | translate }}
</ion-label>
</ion-button>
</core-loading>
</ion-content>
<ion-footer *ngIf="loaded && isOnline">
<ion-button class="ion-margin" *ngIf="!hasOffline" (click)="refresh()" expand="block">
<ion-icon name="fas-redo-alt" slot="start" aria-hidden="true"></ion-icon>
<ion-label>
{{ 'core.refresh' | translate }}
</ion-label>
</ion-button>
<ion-button class="ion-margin" *ngIf="hasOffline" (click)="sync()" expand="block">
<ion-icon name="fas-sync-alt" slot="start" aria-hidden="true"></ion-icon>
<ion-label>
{{ 'core.settings.synchronizenow' | translate }}
</ion-label>
</ion-button>
</ion-footer>

View File

@ -0,0 +1,8 @@
:host ::ng-deep .collapsible-title ion-label {
margin-top: 12px;
}
h1 {
font-size: 20px;
}

View File

@ -0,0 +1,311 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { CoreConstants } from '@/core/constants';
import { AddonBlog } from '@addons/blog/services/blog';
import { AddonBlogMainMenuHandlerService } from '@addons/blog/services/handlers/mainmenu';
import { Component, Input, OnDestroy, OnInit } from '@angular/core';
import { Params } from '@angular/router';
import { CoreCourseHelper, CoreCourseModuleData } from '@features/course/services/course-helper';
import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate';
import { CoreApp } from '@services/app';
import { CoreFilepool } from '@services/filepool';
import { CoreNavigator } from '@services/navigator';
import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreTextUtils } from '@services/utils/text';
import { CoreUtils } from '@services/utils/utils';
import { ModalController, Network, Translate, NgZone } from '@singletons';
import { CoreEventObserver, CoreEvents } from '@singletons/events';
import { Subscription } from 'rxjs';
/**
* Component to display a module summary modal.
*/
@Component({
selector: 'core-course-module-summary',
templateUrl: 'module-summary.html',
styleUrls: ['module-summary.scss'],
})
export class CoreCourseModuleSummaryComponent implements OnInit, OnDestroy {
@Input() module?: CoreCourseModuleData; // The module of the component.
@Input() courseId = 0; // Course ID the component belongs to.
@Input() moduleId = 0; // Module ID the component belongs to.
@Input() component = ''; // Component name.
@Input() description = ''; // Module description.
@Input() hasOffline = false; // If it has offline data to be synced.
loaded = false; // If the component has been loaded.
componentId?: number; // Component ID.
// Data for context menu.
externalUrl?: string; // External URL to open in browser.
removeFilesLoading = false;
prefetchStatusIcon?: string;
prefetchStatus?: string;
prefetchText?: string;
sizeReadable?: string;
downloadTimeReadable?: string; // Last download time in a readable format.
size = 0;
blog = false; // If blog is available.
isOnline = false; // If the app is online or not.
protected onlineSubscription: Subscription; // It will observe the status of the network connection.
protected packageStatusObserver?: CoreEventObserver; // Observer of package status.
protected fileStatusObserver?: CoreEventObserver; // Observer of file status.
protected siteId: string;
protected isDestroyed = false;
constructor() {
this.siteId = CoreSites.getCurrentSiteId();
this.isOnline = CoreApp.isOnline();
// Refresh online status when changes.
this.onlineSubscription = Network.onChange().subscribe(() => {
// Execute the callback in the Angular zone, so change detection doesn't stop working.
NgZone.run(() => {
this.isOnline = CoreApp.isOnline();
});
});
}
/**
* @inheritdoc
*/
async ngOnInit(): Promise<void> {
if (!this.module) {
this.closeModal();
return;
}
this.fetchContent();
if (this.component) {
this.packageStatusObserver = CoreEvents.on(
CoreEvents.PACKAGE_STATUS_CHANGED,
(data) => {
if (data.componentId == module.id && data.component == this.component) {
this.getPackageStatus();
}
},
this.siteId,
);
// Debounce the update size function to prevent too many calls when downloading or deleting a whole activity.
const debouncedUpdateSize = CoreUtils.debounce(async () => {
if (!this.module) {
return;
}
const moduleSize = await CoreCourseModulePrefetchDelegate.getModuleStoredSize(this.module, this.courseId);
this.sizeReadable = moduleSize > 0 ? CoreTextUtils.bytesToSize(moduleSize, 2) : '';
}, 1000);
this.fileStatusObserver = CoreEvents.on(
CoreEvents.COMPONENT_FILE_ACTION,
(data) => {
if (data.component != this.component || data.componentId != module.id) {
// The event doesn't belong to this component, ignore.
return;
}
if (!CoreFilepool.isFileEventDownloadedOrDeleted(data)) {
return;
}
// Update the module size.
debouncedUpdateSize();
},
this.siteId,
);
}
}
/**
* Fetch content to populate the page.
*/
protected async fetchContent(): Promise<void> {
if (!this.module) {
return;
}
this.componentId = this.module.id;
this.externalUrl = this.module.url;
this.courseId = this.courseId || this.module.course;
this.blog = await AddonBlog.isPluginEnabled();
await this.getPackageStatus();
this.loaded = true;
}
/**
* Updage package status.
*
* @param refresh If prefetch info has to be refreshed.
*/
async getPackageStatus(refresh = false): Promise<void> {
if (!this.module) {
return;
}
const moduleInfo =
await CoreCourseHelper.getModulePrefetchInfo(this.module, this.courseId, refresh, this.component);
this.prefetchStatusIcon = moduleInfo.statusIcon;
this.prefetchStatus = moduleInfo.status;
this.downloadTimeReadable = '';
if (moduleInfo.status != CoreConstants.NOT_DOWNLOADABLE) {
// Module is downloadable, get the text to display to prefetch.
if (moduleInfo.downloadTime && moduleInfo.downloadTime > 0) {
this.prefetchText = Translate.instant('core.lastdownloaded');
this.downloadTimeReadable = CoreTextUtils.ucFirst(moduleInfo.downloadTimeReadable);
} else {
// Module not downloaded, show a default text.
this.prefetchText = Translate.instant('core.download');
}
}
this.sizeReadable = moduleInfo.sizeReadable;
this.size = moduleInfo.size;
if (moduleInfo.status == CoreConstants.DOWNLOADING) {
// Set this to empty to prevent "remove file" option showing up while downloading.
this.sizeReadable = '';
}
}
/**
* Go to blog posts.
*/
async gotoBlog(): Promise<void> {
const params: Params = { cmId: this.moduleId };
await CoreNavigator.navigateToSitePath(AddonBlogMainMenuHandlerService.PAGE_NAME, { params });
}
/**
* Prefetch the module.
*/
async prefetch(): Promise<void> {
if (!this.module) {
return;
}
const initialIcon = this.prefetchStatusIcon;
this.prefetchStatusIcon = CoreConstants.ICON_DOWNLOADING; // Show spinner since this operation might take a while.
try {
// We need to call getDownloadSize, the package might have been updated.
const size = await CoreCourseModulePrefetchDelegate.getModuleDownloadSize(this.module, this.courseId, true);
await CoreDomUtils.confirmDownloadSize(size);
await CoreCourseModulePrefetchDelegate.prefetchModule(this.module, this.courseId, true);
await this.getPackageStatus(true);
} catch (error) {
this.prefetchStatusIcon = initialIcon;
if (!this.isDestroyed) {
CoreDomUtils.showErrorModalDefault(error, 'core.errordownloading', true);
}
}
}
/**
* Confirm and remove downloaded files.
*/
async removeFiles(): Promise<void> {
if (!this.module) {
return;
}
if (this.prefetchStatus == CoreConstants.DOWNLOADING) {
CoreDomUtils.showAlertTranslated(undefined, 'core.course.cannotdeletewhiledownloading');
return;
}
try {
await CoreDomUtils.showDeleteConfirm('addon.storagemanager.confirmdeletedatafrom', { name: this.module.name });
this.removeFilesLoading = true;
await CoreCourseHelper.removeModuleStoredData(this.module, this.courseId);
} catch (error) {
if (!this.isDestroyed &&error) {
CoreDomUtils.showErrorModal(error);
}
} finally {
this.removeFilesLoading = false;
}
await this.getPackageStatus();
}
/**
* Refresh the data.
*/
async refresh(): Promise<void> {
if (!this.module) {
return;
}
ModalController.dismiss({ action: 'refresh' });
}
/**
* Sync the data.
*/
async sync(): Promise<void> {
if (!this.module) {
return;
}
ModalController.dismiss({ action: 'sync' });
}
/**
* Close the modal.
*/
closeModal(): void {
ModalController.dismiss();
}
/**
* @inheritdoc
*/
ngOnDestroy(): void {
this.isDestroyed = true;
this.packageStatusObserver?.off();
this.fileStatusObserver?.off();
this.onlineSubscription.unsubscribe();
}
}
export type CoreCourseModuleSummaryResult = {
action: 'sync'|'refresh';
};

View File

@ -297,6 +297,14 @@ button,
ion-button {
margin: 4px 8px;
ion-spinner[slot=start] {
@include margin-horizontal(-0.3em, 0.3em);
}
ion-spinner[slot=end] {
@include margin-horizontal(-0.3em, 0.3em);
}
}
ion-button.button-outline {
@ -1465,6 +1473,16 @@ ion-grid.core-no-grid > ion-row {
}
}
ion-header.no-title {
--core-header-toolbar-border-width: 0;
--core-header-toolbar-background: transparent;
ion-toolbar .button.button-clear,
ion-toolbar .button.button-solid {
--background: var(--ion-background-color);
}
}
ion-header[collapsible] {
@include core-transition(all, 500ms);