Merge pull request #1856 from sammarshallou/MOBILE-2905
MOBILE-2905 Allow user to manage storage within coursemain
commit
386c95d42f
|
@ -881,6 +881,11 @@
|
|||
"addon.notifications.notifications": "local_moodlemobileapp",
|
||||
"addon.notifications.playsound": "local_moodlemobileapp",
|
||||
"addon.notifications.therearentnotificationsyet": "local_moodlemobileapp",
|
||||
"addon.storagemanager.deletecourse": "local_moodlemobileapp",
|
||||
"addon.storagemanager.deletedatafrom": "local_moodlemobileapp",
|
||||
"addon.storagemanager.info": "local_moodlemobileapp",
|
||||
"addon.storagemanager.managestorage": "local_moodlemobileapp",
|
||||
"addon.storagemanager.storageused": "local_moodlemobileapp",
|
||||
"assets.countries.AD": "countries",
|
||||
"assets.countries.AE": "countries",
|
||||
"assets.countries.AF": "countries",
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"deletecourse": "Offload all course data",
|
||||
"deletedatafrom": "Offload data from {{name}}",
|
||||
"info": "Files stored on your device make the app work faster, and when offline. You can safely offload them if you need to free up storage space.",
|
||||
"managestorage": "Manage storage",
|
||||
"storageused": "File storage used:"
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
<ion-header>
|
||||
<ion-navbar core-back-button>
|
||||
<ion-title>{{ 'addon.storagemanager.managestorage' | translate }}</ion-title>
|
||||
</ion-navbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<core-loading [hideUntil]="loaded">
|
||||
<ion-card class="wholecourse">
|
||||
<ion-card-header>
|
||||
<h1 text-wrap>{{ course.displayname }}</h1>
|
||||
<p text-wrap>{{ 'addon.storagemanager.info' | translate }}</p>
|
||||
<ion-item no-padding padding-top>
|
||||
<ion-row class="size">
|
||||
<ion-icon name="cube"></ion-icon>
|
||||
{{ 'addon.storagemanager.storageused' | translate }}
|
||||
{{ totalSize | coreBytesToSize }}
|
||||
</ion-row>
|
||||
<button ion-button icon-only item-end no-padding (click)="deleteForCourse()" [disabled]="totalSize == 0">
|
||||
<core-icon name="trash" label="{{ 'addon.storagemanager.deletecourse' | translate }}"></core-icon>
|
||||
</button>
|
||||
</ion-item>
|
||||
</ion-card-header>
|
||||
</ion-card>
|
||||
<ng-container *ngFor="let section of sections">
|
||||
<ion-card *ngIf="section.totalSize > 0" class="section">
|
||||
<ion-card-header>
|
||||
<ion-item no-padding>
|
||||
<ion-row>
|
||||
<h2 text-wrap>{{ section.name }}</h2>
|
||||
</ion-row>
|
||||
<ion-row class="size">
|
||||
<ion-icon name="cube"></ion-icon>
|
||||
{{ section.totalSize | coreBytesToSize }}
|
||||
</ion-row>
|
||||
<button ion-button icon-only item-end no-padding (click)="deleteForSection(section)">
|
||||
<core-icon name="trash" label="{{ 'addon.storagemanager.deletedatafrom' | translate: { name: section.name } }}"></core-icon>
|
||||
</button>
|
||||
</ion-item>
|
||||
</ion-card-header>
|
||||
<ion-card-content>
|
||||
<ng-container *ngFor="let module of section.modules">
|
||||
<div *ngIf="module.totalSize > 0">
|
||||
<ion-item no-padding>
|
||||
<ion-row class="{{module.handlerData.class}}">
|
||||
<img *ngIf="module.handlerData.icon" [src]="module.handlerData.icon" alt="" role="presentation" class="core-module-icon"
|
||||
>{{ module.name }}
|
||||
</ion-row>
|
||||
<ion-row class="size">
|
||||
<ion-icon name="cube"></ion-icon>
|
||||
{{ module.totalSize | coreBytesToSize }}
|
||||
</ion-row>
|
||||
<button ion-button icon-only outline item-end (click)="deleteForModule(module)">
|
||||
<core-icon name="trash" label="{{ 'addon.storagemanager.deletedatafrom' | translate: { name: module.name } }}"></core-icon>
|
||||
</button>
|
||||
</ion-item>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ion-card-content>
|
||||
</ion-card>
|
||||
</ng-container>
|
||||
</core-loading>
|
||||
</ion-content>
|
|
@ -0,0 +1,36 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// 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 { NgModule } from '@angular/core';
|
||||
import { IonicPageModule } from 'ionic-angular';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { CoreComponentsModule } from '@components/components.module';
|
||||
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||
import { CorePipesModule } from '@pipes/pipes.module';
|
||||
import { AddonStorageManagerCourseStoragePage } from './course-storage';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AddonStorageManagerCourseStoragePage,
|
||||
],
|
||||
imports: [
|
||||
CoreComponentsModule,
|
||||
CoreDirectivesModule,
|
||||
CorePipesModule,
|
||||
IonicPageModule.forChild(AddonStorageManagerCourseStoragePage),
|
||||
TranslateModule.forChild()
|
||||
],
|
||||
})
|
||||
export class AddonStorageManagerCourseStoragePageModule {
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
ion-app.app-root page-addon-storagemanager-course-storage {
|
||||
.item-md.item-block .item-inner {
|
||||
padding-right: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
ion-card.section ion-card-header.card-header {
|
||||
border-bottom: 1px solid $list-border-color;
|
||||
margin-bottom: 8px;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
ion-card.section h2 {
|
||||
font-weight: bold;
|
||||
font-size: 2rem;
|
||||
}
|
||||
.size {
|
||||
margin-top: 4px;
|
||||
}
|
||||
.size ion-icon {
|
||||
margin-right: 4px;
|
||||
}
|
||||
.core-module-icon {
|
||||
margin-right: 4px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: inline;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,163 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// 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 { Component, ViewChild } from '@angular/core';
|
||||
import { IonicPage, Content, NavParams } from 'ionic-angular';
|
||||
import { CoreCourseProvider } from '@core/course/providers/course';
|
||||
import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module-prefetch-delegate';
|
||||
import { CoreCourseHelperProvider } from '@core/course/providers/helper';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
/**
|
||||
* Page that displays the amount of file storage used by each activity on the course, and allows
|
||||
* the user to delete these files.
|
||||
*/
|
||||
@IonicPage({ segment: 'addon-storagemanager-course-storage' })
|
||||
@Component({
|
||||
selector: 'page-addon-storagemanager-course-storage',
|
||||
templateUrl: 'course-storage.html',
|
||||
})
|
||||
export class AddonStorageManagerCourseStoragePage {
|
||||
@ViewChild(Content) content: Content;
|
||||
|
||||
course: any;
|
||||
loaded: boolean;
|
||||
sections: any;
|
||||
totalSize: number;
|
||||
|
||||
constructor(navParams: NavParams,
|
||||
private courseProvider: CoreCourseProvider,
|
||||
private prefetchDelegate: CoreCourseModulePrefetchDelegate,
|
||||
private courseHelperProvider: CoreCourseHelperProvider,
|
||||
private domUtils: CoreDomUtilsProvider,
|
||||
private translate: TranslateService) {
|
||||
|
||||
this.course = navParams.get('course');
|
||||
}
|
||||
|
||||
/**
|
||||
* View loaded.
|
||||
*/
|
||||
ionViewDidLoad(): void {
|
||||
this.courseProvider.getSections(this.course.id, false, true).then((sections) => {
|
||||
this.courseHelperProvider.addHandlerDataForModules(sections, this.course.id);
|
||||
this.sections = sections;
|
||||
this.totalSize = 0;
|
||||
|
||||
const allPromises = [];
|
||||
this.sections.forEach((section) => {
|
||||
section.totalSize = 0;
|
||||
section.modules.forEach((module) => {
|
||||
module.parentSection = section;
|
||||
// Note: This function only gets the size for modules which are downloadable.
|
||||
// For other modules it always returns 0, even if they have downloaded some files.
|
||||
// However there is no 100% reliable way to actually track the files in this case.
|
||||
// You can maybe guess it based on the component and componentid.
|
||||
// But these aren't necessarily consistent, for example mod_frog vs mmaModFrog.
|
||||
// There is nothing enforcing correct values.
|
||||
// Most modules which have large files are downloadable, so I think this is sufficient.
|
||||
const promise = this.prefetchDelegate.getModuleDownloadedSize(module, this.course.id).
|
||||
then((size) => {
|
||||
module.totalSize = size;
|
||||
section.totalSize += size;
|
||||
this.totalSize += size;
|
||||
});
|
||||
allPromises.push(promise);
|
||||
});
|
||||
});
|
||||
|
||||
Promise.all(allPromises).then(() => {
|
||||
this.loaded = true;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The user has requested a delete for the whole course data.
|
||||
*
|
||||
* (This works by deleting data for each module on the course that has data.)
|
||||
*/
|
||||
deleteForCourse(): void {
|
||||
const modules = [];
|
||||
this.sections.forEach((section) => {
|
||||
section.modules.forEach((module) => {
|
||||
if (module.totalSize > 0) {
|
||||
modules.push(module);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this.deleteModules(modules);
|
||||
}
|
||||
|
||||
/**
|
||||
* The user has requested a delete for a section's data.
|
||||
*
|
||||
* (This works by deleting data for each module in the section that has data.)
|
||||
*
|
||||
* @param {any} section Section object with information about section and modules
|
||||
*/
|
||||
deleteForSection(section: any): void {
|
||||
const modules = [];
|
||||
section.modules.forEach((module) => {
|
||||
if (module.totalSize > 0) {
|
||||
modules.push(module);
|
||||
}
|
||||
});
|
||||
|
||||
this.deleteModules(modules);
|
||||
}
|
||||
|
||||
/**
|
||||
* The user has requested a delete for a module's data
|
||||
*
|
||||
* @param {any} module Module details
|
||||
*/
|
||||
deleteForModule(module: any): void {
|
||||
if (module.totalSize > 0) {
|
||||
this.deleteModules([module]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the specified modules, showing the loading overlay while it happens.
|
||||
*
|
||||
* @param {any[]} modules Modules to delete
|
||||
* @return Promise<void> Once deleting has finished
|
||||
*/
|
||||
protected deleteModules(modules: any[]): Promise<void> {
|
||||
const modal = this.domUtils.showModalLoading();
|
||||
|
||||
const promises = [];
|
||||
modules.forEach((module) => {
|
||||
// Remove the files.
|
||||
const promise = this.prefetchDelegate.removeModuleFiles(module, this.course.id).then(() => {
|
||||
// When the files are removed, update the size.
|
||||
module.parentSection.totalSize -= module.totalSize;
|
||||
this.totalSize -= module.totalSize;
|
||||
module.totalSize = 0;
|
||||
});
|
||||
promises.push(promise);
|
||||
});
|
||||
|
||||
return Promise.all(promises).then(() => {
|
||||
modal.dismiss();
|
||||
}).catch((error) => {
|
||||
modal.dismiss();
|
||||
|
||||
this.domUtils.showErrorModalDefault(error, this.translate.instant('core.errordeletefile'));
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// 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 { Injectable } from '@angular/core';
|
||||
import { CoreCourseOptionsMenuHandler, CoreCourseOptionsMenuHandlerData } from '@core/course/providers/options-delegate';
|
||||
|
||||
/**
|
||||
* Handler to inject an option into course menu so that user can get to the manage storage page.
|
||||
*/
|
||||
@Injectable()
|
||||
export class AddonStorageManagerCourseMenuHandler implements CoreCourseOptionsMenuHandler {
|
||||
name = 'AddonStorageManager';
|
||||
priority = 500;
|
||||
isMenuHandler = true;
|
||||
|
||||
/**
|
||||
* Checks if the handler is enabled for specified course. This handler is always available.
|
||||
*
|
||||
* @param {number} courseId Course id
|
||||
* @param {any} accessData Access data
|
||||
* @param {any} [navOptions] Navigation options if any
|
||||
* @param {any} [admOptions] Admin options if any
|
||||
* @return {boolean | Promise<boolean>} True
|
||||
*/
|
||||
isEnabledForCourse(courseId: number, accessData: any, navOptions?: any, admOptions?: any): boolean | Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the handler is enabled on a site level.
|
||||
*
|
||||
* @return {boolean | Promise<boolean>} Whether or not the handler is enabled on a site level.
|
||||
*/
|
||||
isEnabled(): boolean | Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data needed to render the handler.
|
||||
*
|
||||
* @return {CoreCourseOptionsMenuHandlerData} Data needed to render the handler.
|
||||
*/
|
||||
getMenuDisplayData(): CoreCourseOptionsMenuHandlerData {
|
||||
return {
|
||||
icon: 'cube',
|
||||
title: 'addon.storagemanager.managestorage',
|
||||
page: 'AddonStorageManagerCourseStoragePage',
|
||||
class: 'addon-storagemanager-coursemenu-handler'
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// 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 { NgModule } from '@angular/core';
|
||||
import { AddonStorageManagerCourseMenuHandler } from '@addon/storagemanager/providers/coursemenu-handler';
|
||||
import { CoreCourseOptionsDelegate } from '@core/course/providers/options-delegate';
|
||||
|
||||
@NgModule({
|
||||
declarations: [],
|
||||
imports: [
|
||||
],
|
||||
providers: [
|
||||
AddonStorageManagerCourseMenuHandler
|
||||
],
|
||||
exports: []
|
||||
})
|
||||
export class AddonStorageManagerModule {
|
||||
constructor(private courseOptionsDelegate: CoreCourseOptionsDelegate,
|
||||
private courseMenuHandler: AddonStorageManagerCourseMenuHandler) {
|
||||
// Register handlers.
|
||||
this.courseOptionsDelegate.registerHandler(this.courseMenuHandler);
|
||||
}
|
||||
}
|
|
@ -126,6 +126,7 @@ import { AddonNotificationsModule } from '@addon/notifications/notifications.mod
|
|||
import { AddonRemoteThemesModule } from '@addon/remotethemes/remotethemes.module';
|
||||
import { AddonQbehaviourModule } from '@addon/qbehaviour/qbehaviour.module';
|
||||
import { AddonQtypeModule } from '@addon/qtype/qtype.module';
|
||||
import { AddonStorageManagerModule } from '@addon/storagemanager/storagemanager.module';
|
||||
|
||||
// For translate loader. AoT requires an exported function for factories.
|
||||
export function createTranslateLoader(http: HttpClient): TranslateHttpLoader {
|
||||
|
@ -244,7 +245,8 @@ export const CORE_PROVIDERS: any[] = [
|
|||
AddonNotificationsModule,
|
||||
AddonRemoteThemesModule,
|
||||
AddonQbehaviourModule,
|
||||
AddonQtypeModule
|
||||
AddonQtypeModule,
|
||||
AddonStorageManagerModule
|
||||
],
|
||||
bootstrap: [IonicApp],
|
||||
entryComponents: [
|
||||
|
|
|
@ -881,6 +881,11 @@
|
|||
"addon.notifications.notifications": "Notifications",
|
||||
"addon.notifications.playsound": "Play sound",
|
||||
"addon.notifications.therearentnotificationsyet": "There are no notifications.",
|
||||
"addon.storagemanager.deletecourse": "Offload all course data",
|
||||
"addon.storagemanager.deletedatafrom": "Offload data from {{name}}",
|
||||
"addon.storagemanager.info": "Files stored on your device make the app work faster, and when offline. You can safely offload them if you need to free up storage space.",
|
||||
"addon.storagemanager.managestorage": "Manage storage",
|
||||
"addon.storagemanager.storageused": "File storage used:",
|
||||
"assets.countries.AD": "Andorra",
|
||||
"assets.countries.AE": "United Arab Emirates",
|
||||
"assets.countries.AF": "Afghanistan",
|
||||
|
|
Loading…
Reference in New Issue