MOBILE-2343 imscp: Migrate IMSCP
parent
ecf835e1eb
commit
7497059175
|
@ -0,0 +1,49 @@
|
||||||
|
// (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 { CommonModule } from '@angular/common';
|
||||||
|
import { IonicModule } from 'ionic-angular';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { CoreComponentsModule } from '@components/components.module';
|
||||||
|
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||||
|
import { CoreCourseComponentsModule } from '@core/course/components/components.module';
|
||||||
|
import { AddonModImscpIndexComponent } from './index/index';
|
||||||
|
import { AddonModImscpTocPopoverComponent } from './toc-popover/toc-popover';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
AddonModImscpIndexComponent,
|
||||||
|
AddonModImscpTocPopoverComponent,
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
IonicModule,
|
||||||
|
TranslateModule.forChild(),
|
||||||
|
CoreComponentsModule,
|
||||||
|
CoreDirectivesModule,
|
||||||
|
CoreCourseComponentsModule
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
AddonModImscpIndexComponent,
|
||||||
|
AddonModImscpTocPopoverComponent
|
||||||
|
],
|
||||||
|
entryComponents: [
|
||||||
|
AddonModImscpIndexComponent,
|
||||||
|
AddonModImscpTocPopoverComponent
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class AddonModImscpComponentsModule {}
|
|
@ -0,0 +1,21 @@
|
||||||
|
<!-- Buttons to add to the header. -->
|
||||||
|
<core-navbar-buttons end>
|
||||||
|
<button ion-button icon-only (click)="showToc($event)">
|
||||||
|
<ion-icon name="bookmark" *ngIf="items.length"></ion-icon>
|
||||||
|
</button>
|
||||||
|
<core-context-menu>
|
||||||
|
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl" [iconAction]="'open'"></core-context-menu-item>
|
||||||
|
<core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate" (action)="expandDescription()" [iconAction]="'arrow-forward'"></core-context-menu-item>
|
||||||
|
<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-item *ngIf="prefetchStatusIcon" [priority]="600" [content]="prefetchText" (action)="prefetch()" [iconAction]="prefetchStatusIcon" [closeOnClick]="false"></core-context-menu-item>
|
||||||
|
<core-context-menu-item *ngIf="size" [priority]="500" [content]="size" [iconDescription]="'cube'" (action)="removeFiles()" [iconAction]="'trash'"></core-context-menu-item>
|
||||||
|
</core-context-menu>
|
||||||
|
</core-navbar-buttons>
|
||||||
|
|
||||||
|
<!-- Content. -->
|
||||||
|
<core-loading [hideUntil]="loaded" class="core-loading-center">
|
||||||
|
<div class="addon-mod-imscp-container">
|
||||||
|
<core-navigation-bar [previous]="previousItem" [next]="nextItem" (action)="loadItem($event)" [info]="description" [title]="'core.description' | translate" [component]="component" [componentId]="componentId"></core-navigation-bar>
|
||||||
|
<core-iframe [src]="src"></core-iframe>
|
||||||
|
</div>
|
||||||
|
</core-loading>
|
|
@ -0,0 +1,12 @@
|
||||||
|
addon-mod-imscp-index {
|
||||||
|
.addon-mod-imscp-container {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
core-iframe {
|
||||||
|
flex-basis: 100%;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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, Injector } from '@angular/core';
|
||||||
|
import { PopoverController } from 'ionic-angular';
|
||||||
|
import { CoreAppProvider } from '@providers/app';
|
||||||
|
import { CoreCourseProvider } from '@core/course/providers/course';
|
||||||
|
import { CoreCourseModuleMainResourceComponent } from '@core/course/classes/main-resource-component';
|
||||||
|
import { AddonModImscpProvider } from '../../providers/imscp';
|
||||||
|
import { AddonModImscpPrefetchHandler } from '../../providers/prefetch-handler';
|
||||||
|
import { AddonModImscpTocPopoverComponent } from '../../components/toc-popover/toc-popover';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component that displays a IMSCP.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'addon-mod-imscp-index',
|
||||||
|
templateUrl: 'index.html',
|
||||||
|
})
|
||||||
|
export class AddonModImscpIndexComponent extends CoreCourseModuleMainResourceComponent {
|
||||||
|
component = AddonModImscpProvider.COMPONENT;
|
||||||
|
|
||||||
|
items = [];
|
||||||
|
currentItem: string;
|
||||||
|
src = '';
|
||||||
|
|
||||||
|
// Initialize empty previous/next to prevent showing arrows for an instant before they're hidden.
|
||||||
|
previousItem = '';
|
||||||
|
nextItem = '';
|
||||||
|
|
||||||
|
constructor(injector: Injector, private imscpProvider: AddonModImscpProvider, private courseProvider: CoreCourseProvider,
|
||||||
|
private appProvider: CoreAppProvider, private popoverCtrl: PopoverController,
|
||||||
|
private imscpPrefetch: AddonModImscpPrefetchHandler) {
|
||||||
|
super(injector);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component being initialized.
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
super.ngOnInit();
|
||||||
|
|
||||||
|
this.loadContent().then(() => {
|
||||||
|
this.imscpProvider.logView(this.module.instance).then(() => {
|
||||||
|
this.courseProvider.checkModuleCompletion(this.courseId, this.module.completionstatus);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform the invalidate content function.
|
||||||
|
*
|
||||||
|
* @return {Promise<any>} Resolved when done.
|
||||||
|
*/
|
||||||
|
protected invalidateContent(): Promise<any> {
|
||||||
|
return this.imscpProvider.invalidateContent(this.module.id, this.courseId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download imscp contents.
|
||||||
|
*
|
||||||
|
* @param {boolean} [refresh] Whether we're refreshing data.
|
||||||
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
|
*/
|
||||||
|
protected fetchContent(refresh?: boolean): Promise<any> {
|
||||||
|
let downloadFailed = false;
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
|
promises.push(this.imscpProvider.getImscp(this.courseId, this.module.id).then((imscp) => {
|
||||||
|
this.description = imscp.intro || imscp.description;
|
||||||
|
this.dataRetrieved.emit(imscp);
|
||||||
|
}));
|
||||||
|
|
||||||
|
promises.push(this.imscpPrefetch.download(this.module, this.courseId).catch(() => {
|
||||||
|
// Mark download as failed but go on since the main files could have been downloaded.
|
||||||
|
downloadFailed = true;
|
||||||
|
|
||||||
|
return this.courseProvider.loadModuleContents(this.module, this.courseId).catch((error) => {
|
||||||
|
// Error getting module contents, fail.
|
||||||
|
this.domUtils.showErrorModalDefault(error, 'core.course.errorgetmodule', true);
|
||||||
|
|
||||||
|
return Promise.reject(null);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
return Promise.all(promises).then(() => {
|
||||||
|
this.items = this.imscpProvider.createItemList(this.module.contents);
|
||||||
|
if (this.items.length && typeof this.currentItem == 'undefined') {
|
||||||
|
this.currentItem = this.items[0].href;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.loadItem(this.currentItem).catch((error) => {
|
||||||
|
this.domUtils.showErrorModalDefault(error, 'addon.mod_imscp.deploymenterror', true);
|
||||||
|
|
||||||
|
return Promise.reject(null);
|
||||||
|
});
|
||||||
|
}).then(() => {
|
||||||
|
if (downloadFailed && this.appProvider.isOnline()) {
|
||||||
|
// We could load the main file but the download failed. Show error message.
|
||||||
|
this.domUtils.showErrorModal('core.errordownloadingsomefiles', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// All data obtained, now fill the context menu.
|
||||||
|
this.fillContextMenu(refresh);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads an item.
|
||||||
|
*
|
||||||
|
* @param {string} itemId Item ID.
|
||||||
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
|
*/
|
||||||
|
loadItem(itemId: string): Promise<any> {
|
||||||
|
return this.imscpProvider.getIframeSrc(this.module, itemId).then((src) => {
|
||||||
|
this.currentItem = itemId;
|
||||||
|
this.previousItem = this.imscpProvider.getPreviousItem(this.items, itemId);
|
||||||
|
this.nextItem = this.imscpProvider.getNextItem(this.items, itemId);
|
||||||
|
|
||||||
|
if (this.src && src == this.src) {
|
||||||
|
// Re-loading same page. Set it to empty and then re-set the src in the next digest so it detects it has changed.
|
||||||
|
this.src = '';
|
||||||
|
setTimeout(() => {
|
||||||
|
this.src = src;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.src = src;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the TOC.
|
||||||
|
*
|
||||||
|
* @param {MouseEvent} event Event.
|
||||||
|
*/
|
||||||
|
showToc(event: MouseEvent): void {
|
||||||
|
const popover = this.popoverCtrl.create(AddonModImscpTocPopoverComponent, { items: this.items });
|
||||||
|
|
||||||
|
popover.onDidDismiss((itemId) => {
|
||||||
|
if (!itemId) {
|
||||||
|
// Not valid, probably a category.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.loadItem(itemId);
|
||||||
|
});
|
||||||
|
|
||||||
|
popover.present({
|
||||||
|
ev: event
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
<ion-list>
|
||||||
|
<a ion-item *ngFor="let item of items" (click)="loadItem(item.href)" detail-none [class.core-bold]="!item.href">
|
||||||
|
<span padding-left *ngFor="let i of getNumberForPadding(item.level)"></span>{{item.title}}
|
||||||
|
</a>
|
||||||
|
</ion-list>
|
|
@ -0,0 +1,50 @@
|
||||||
|
// (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 } from '@angular/core';
|
||||||
|
import { NavParams, ViewController } from 'ionic-angular';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component to display the TOC of a IMSCP.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'addon-mod-imscp-toc-popover',
|
||||||
|
templateUrl: 'toc-popover.html'
|
||||||
|
})
|
||||||
|
export class AddonModImscpTocPopoverComponent {
|
||||||
|
items = [];
|
||||||
|
|
||||||
|
constructor(navParams: NavParams, private viewCtrl: ViewController) {
|
||||||
|
this.items = navParams.get('items') || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function called when an item is clicked.
|
||||||
|
*
|
||||||
|
* @param {string} id ID of the clicked item.
|
||||||
|
*/
|
||||||
|
loadItem(id: string): void {
|
||||||
|
this.viewCtrl.dismiss(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get dummy array for padding.
|
||||||
|
*
|
||||||
|
* @param {number} n Array length.
|
||||||
|
* @return {number[]} Dummy array with n elements.
|
||||||
|
*/
|
||||||
|
getNumberForPadding(n: number): number[] {
|
||||||
|
return new Array(n);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
// (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 { AddonModImscpComponentsModule } from './components/components.module';
|
||||||
|
import { AddonModImscpModuleHandler } from './providers/module-handler';
|
||||||
|
import { AddonModImscpProvider } from './providers/imscp';
|
||||||
|
import { AddonModImscpPrefetchHandler } from './providers/prefetch-handler';
|
||||||
|
import { AddonModImscpLinkHandler } from './providers/link-handler';
|
||||||
|
import { AddonModImscpPluginFileHandler } from './providers/pluginfile-handler';
|
||||||
|
import { CoreContentLinksDelegate } from '@core/contentlinks/providers/delegate';
|
||||||
|
import { CoreCourseModuleDelegate } from '@core/course/providers/module-delegate';
|
||||||
|
import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module-prefetch-delegate';
|
||||||
|
import { CorePluginFileDelegate } from '@providers/plugin-file-delegate';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
AddonModImscpComponentsModule
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
AddonModImscpProvider,
|
||||||
|
AddonModImscpModuleHandler,
|
||||||
|
AddonModImscpPrefetchHandler,
|
||||||
|
AddonModImscpLinkHandler,
|
||||||
|
AddonModImscpPluginFileHandler
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class AddonModImscpModule {
|
||||||
|
constructor(moduleDelegate: CoreCourseModuleDelegate, moduleHandler: AddonModImscpModuleHandler,
|
||||||
|
prefetchDelegate: CoreCourseModulePrefetchDelegate, prefetchHandler: AddonModImscpPrefetchHandler,
|
||||||
|
contentLinksDelegate: CoreContentLinksDelegate, linkHandler: AddonModImscpLinkHandler,
|
||||||
|
pluginfileDelegate: CorePluginFileDelegate, pluginfileHandler: AddonModImscpPluginFileHandler) {
|
||||||
|
moduleDelegate.registerHandler(moduleHandler);
|
||||||
|
prefetchDelegate.registerHandler(prefetchHandler);
|
||||||
|
contentLinksDelegate.registerHandler(linkHandler);
|
||||||
|
pluginfileDelegate.registerHandler(pluginfileHandler);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"deploymenterror": "Content package error!",
|
||||||
|
"showmoduledescription": "Show description"
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
<ion-header>
|
||||||
|
<ion-navbar>
|
||||||
|
<ion-title><core-format-text [text]="title"></core-format-text></ion-title>
|
||||||
|
|
||||||
|
<ion-buttons end>
|
||||||
|
<!-- The buttons defined by the component will be added in here. -->
|
||||||
|
</ion-buttons>
|
||||||
|
</ion-navbar>
|
||||||
|
</ion-header>
|
||||||
|
<ion-content>
|
||||||
|
<addon-mod-imscp-index [module]="module" [courseId]="courseId" (dataRetrieved)="updateData($event)"></addon-mod-imscp-index>
|
||||||
|
</ion-content>
|
|
@ -0,0 +1,33 @@
|
||||||
|
// (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 { CoreDirectivesModule } from '@directives/directives.module';
|
||||||
|
import { AddonModImscpComponentsModule } from '../../components/components.module';
|
||||||
|
import { AddonModImscpIndexPage } from './index';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
AddonModImscpIndexPage,
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
CoreDirectivesModule,
|
||||||
|
AddonModImscpComponentsModule,
|
||||||
|
IonicPageModule.forChild(AddonModImscpIndexPage),
|
||||||
|
TranslateModule.forChild()
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class AddonModImscpIndexPageModule {}
|
|
@ -0,0 +1,48 @@
|
||||||
|
// (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, NavParams } from 'ionic-angular';
|
||||||
|
import { AddonModImscpIndexComponent } from '../../components/index/index';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Imscp that displays a IMSCP.
|
||||||
|
*/
|
||||||
|
@IonicPage({ segment: 'addon-mod-imscp-index' })
|
||||||
|
@Component({
|
||||||
|
selector: 'page-addon-mod-imscp-index',
|
||||||
|
templateUrl: 'index.html',
|
||||||
|
})
|
||||||
|
export class AddonModImscpIndexPage {
|
||||||
|
@ViewChild(AddonModImscpIndexComponent) imscpComponent: AddonModImscpIndexComponent;
|
||||||
|
|
||||||
|
title: string;
|
||||||
|
module: any;
|
||||||
|
courseId: number;
|
||||||
|
|
||||||
|
constructor(navParams: NavParams) {
|
||||||
|
this.module = navParams.get('module') || {};
|
||||||
|
this.courseId = navParams.get('courseId');
|
||||||
|
this.title = this.module.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update some data based on the imscp instance.
|
||||||
|
*
|
||||||
|
* @param {any} imscp Imscp instance.
|
||||||
|
*/
|
||||||
|
updateData(imscp: any): void {
|
||||||
|
this.title = imscp.name || this.title;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,319 @@
|
||||||
|
// (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 { CoreAppProvider } from '@providers/app';
|
||||||
|
import { CoreFilepoolProvider } from '@providers/filepool';
|
||||||
|
import { CoreSitesProvider } from '@providers/sites';
|
||||||
|
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||||
|
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||||
|
import { CoreCourseProvider } from '@core/course/providers/course';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service that provides some features for IMSCP.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class AddonModImscpProvider {
|
||||||
|
static COMPONENT = 'mmaModImscp';
|
||||||
|
|
||||||
|
protected ROOT_CACHE_KEY = 'mmaModImscp:';
|
||||||
|
|
||||||
|
constructor(private appProvider: CoreAppProvider, private courseProvider: CoreCourseProvider,
|
||||||
|
private filepoolProvider: CoreFilepoolProvider, private sitesProvider: CoreSitesProvider,
|
||||||
|
private textUtils: CoreTextUtilsProvider, private utils: CoreUtilsProvider) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the IMSCP toc as an array.
|
||||||
|
*
|
||||||
|
* @param {any[]} contents The module contents.
|
||||||
|
* @return {any} The toc.
|
||||||
|
*/
|
||||||
|
protected getToc(contents: any[]): any {
|
||||||
|
if (!contents || !contents.length) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.parse(contents[0].content);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the imscp toc as an array of items (not nested) to build the navigation tree.
|
||||||
|
*
|
||||||
|
* @param {any[]} contents The module contents.
|
||||||
|
* @return {any[]} The toc as a list.
|
||||||
|
*/
|
||||||
|
createItemList(contents: any[]): any[] {
|
||||||
|
const items = [];
|
||||||
|
|
||||||
|
this.getToc(contents).forEach((el) => {
|
||||||
|
items.push({href: el.href, title: el.title, level: el.level});
|
||||||
|
el.subitems.forEach((sel) => {
|
||||||
|
items.push({href: sel.href, title: sel.title, level: sel.level});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the previous item to the given one.
|
||||||
|
*
|
||||||
|
* @param {any[]} items The items list.
|
||||||
|
* @param {string} itemId The current item.
|
||||||
|
* @return {string} The previous item id.
|
||||||
|
*/
|
||||||
|
getPreviousItem(items: any[], itemId: string): string {
|
||||||
|
const position = this.getItemPosition(items, itemId);
|
||||||
|
|
||||||
|
if (position != -1) {
|
||||||
|
for (let i = position - 1; i >= 0; i--) {
|
||||||
|
if (items[i] && items[i].href) {
|
||||||
|
return items[i].href;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the next item to the given one.
|
||||||
|
*
|
||||||
|
* @param {any[]} items The items list.
|
||||||
|
* @param {string} itemId The current item.
|
||||||
|
* @return {string} The next item id.
|
||||||
|
*/
|
||||||
|
getNextItem(items: any[], itemId: string): string {
|
||||||
|
const position = this.getItemPosition(items, itemId);
|
||||||
|
|
||||||
|
if (position != -1) {
|
||||||
|
for (let i = position + 1; i < items.length; i++) {
|
||||||
|
if (items[i] && items[i].href) {
|
||||||
|
return items[i].href;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the position of a item.
|
||||||
|
*
|
||||||
|
* @param {any[]} items The items list.
|
||||||
|
* @param {string} itemId The item to search.
|
||||||
|
* @return {number} The item position.
|
||||||
|
*/
|
||||||
|
protected getItemPosition(items: any[], itemId: string): number {
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
if (items[i].href == itemId) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if we should ommit the file download.
|
||||||
|
*
|
||||||
|
* @param {string} fileName The file name
|
||||||
|
* @return {boolean} True if we should ommit the file.
|
||||||
|
*/
|
||||||
|
protected checkSpecialFiles(fileName: string): boolean {
|
||||||
|
return fileName == 'imsmanifest.xml';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get cache key for imscp data WS calls.
|
||||||
|
*
|
||||||
|
* @param {number} courseId Course ID.
|
||||||
|
* @return {string} Cache key.
|
||||||
|
*/
|
||||||
|
protected getImscpDataCacheKey(courseId: number): string {
|
||||||
|
return this.ROOT_CACHE_KEY + 'imscp:' + courseId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a imscp with key=value. If more than one is found, only the first will be returned.
|
||||||
|
*
|
||||||
|
* @param {number} courseId Course ID.
|
||||||
|
* @param {string} key Name of the property to check.
|
||||||
|
* @param {any} value Value to search.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved when the imscp is retrieved.
|
||||||
|
*/
|
||||||
|
protected getImscpByKey(courseId: number, key: string, value: any, siteId?: string): Promise<any> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
const params = {
|
||||||
|
courseids: [courseId]
|
||||||
|
};
|
||||||
|
const preSets = {
|
||||||
|
cacheKey: this.getImscpDataCacheKey(courseId)
|
||||||
|
};
|
||||||
|
|
||||||
|
return site.read('mod_imscp_get_imscps_by_courses', params, preSets).then((response) => {
|
||||||
|
if (response && response.imscps) {
|
||||||
|
const currentImscp = response.imscps.find((imscp) => imscp[key] == value);
|
||||||
|
if (currentImscp) {
|
||||||
|
return currentImscp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a imscp by course module ID.
|
||||||
|
*
|
||||||
|
* @param {number} courseId Course ID.
|
||||||
|
* @param {number} cmId Course module ID.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved when the imscp is retrieved.
|
||||||
|
*/
|
||||||
|
getImscp(courseId: number, cmId: number, siteId?: string): Promise<any> {
|
||||||
|
return this.getImscpByKey(courseId, 'coursemodule', cmId, siteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a filepath, get a certain fileurl from module contents.
|
||||||
|
*
|
||||||
|
* @param {any[]} contents Module contents.
|
||||||
|
* @param {string} targetFilePath Path of the searched file.
|
||||||
|
* @return {string} File URL.
|
||||||
|
*/
|
||||||
|
protected getFileUrlFromContents(contents: any[], targetFilePath: string): string {
|
||||||
|
let indexUrl;
|
||||||
|
contents.forEach((content) => {
|
||||||
|
if (content.type == 'file' && !indexUrl) {
|
||||||
|
const filePath = this.textUtils.concatenatePaths(content.filepath, content.filename);
|
||||||
|
const filePathAlt = filePath.charAt(0) === '/' ? filePath.substr(1) : '/' + filePath;
|
||||||
|
// Check if it's main file.
|
||||||
|
if (filePath === targetFilePath || filePathAlt === targetFilePath) {
|
||||||
|
indexUrl = content.fileurl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return indexUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get src of a imscp item.
|
||||||
|
*
|
||||||
|
* @param {any} module The module object.
|
||||||
|
* @param {string} [itemHref] Href of item to get. If not defined, gets src of main item.
|
||||||
|
* @return {Promise<string>} Promise resolved with the item src.
|
||||||
|
*/
|
||||||
|
getIframeSrc(module: any, itemHref?: string): Promise<string> {
|
||||||
|
if (!itemHref) {
|
||||||
|
const toc = this.getToc(module.contents);
|
||||||
|
if (!toc.length) {
|
||||||
|
return Promise.reject(null);
|
||||||
|
}
|
||||||
|
itemHref = toc[0].href;
|
||||||
|
}
|
||||||
|
|
||||||
|
const siteId = this.sitesProvider.getCurrentSiteId();
|
||||||
|
|
||||||
|
return this.filepoolProvider.getPackageDirUrlByUrl(siteId, module.url).then((dirPath) => {
|
||||||
|
return this.textUtils.concatenatePaths(dirPath, itemHref);
|
||||||
|
}).catch(() => {
|
||||||
|
// Error getting directory, there was an error downloading or we're in browser. Return online URL if connected.
|
||||||
|
if (this.appProvider.isOnline()) {
|
||||||
|
const indexUrl = this.getFileUrlFromContents(module.contents, itemHref);
|
||||||
|
|
||||||
|
if (indexUrl) {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
return site.fixPluginfileURL(indexUrl);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidate the prefetched content.
|
||||||
|
*
|
||||||
|
* @param {number} moduleId The module ID.
|
||||||
|
* @param {number} courseId Course ID of the module.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved when the content is invalidated.
|
||||||
|
*/
|
||||||
|
invalidateContent(moduleId: number, courseId: number, siteId?: string): Promise<any> {
|
||||||
|
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||||
|
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
|
promises.push(this.invalidateImscpData(courseId, siteId));
|
||||||
|
promises.push(this.filepoolProvider.invalidateFilesByComponent(siteId, AddonModImscpProvider.COMPONENT, moduleId));
|
||||||
|
promises.push(this.courseProvider.invalidateModule(moduleId, siteId));
|
||||||
|
|
||||||
|
return this.utils.allPromises(promises);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidates imscp data.
|
||||||
|
*
|
||||||
|
* @param {number} courseId Course ID.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved when the data is invalidated.
|
||||||
|
*/
|
||||||
|
invalidateImscpData(courseId: number, siteId?: string): Promise<any> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
return site.invalidateWsCacheForKey(this.getImscpDataCacheKey(courseId));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a file is downloadable. The file param must have 'type' and 'filename' attributes
|
||||||
|
* like in core_course_get_contents response.
|
||||||
|
*
|
||||||
|
* @param {any} file File to check.
|
||||||
|
* @return {boolean} True if downloadable, false otherwise.
|
||||||
|
*/
|
||||||
|
isFileDownloadable(file: any): boolean {
|
||||||
|
return file.type === 'file' && !this.checkSpecialFiles(file.filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return whether or not the plugin is enabled in a certain site.
|
||||||
|
*
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved with true if plugin is enabled, rejected or resolved with false otherwise.
|
||||||
|
*/
|
||||||
|
isPluginEnabled(siteId?: string): Promise<any> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
return site.canDownloadFiles();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Report a IMSCP as being viewed.
|
||||||
|
*
|
||||||
|
* @param {string} id Module ID.
|
||||||
|
* @return {Promise<any>} Promise resolved when the WS call is successful.
|
||||||
|
*/
|
||||||
|
logView(id: string): Promise<any> {
|
||||||
|
const params = {
|
||||||
|
imscpid: id
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.sitesProvider.getCurrentSite().write('mod_imscp_view_imscp', params);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
// (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 { CoreContentLinksModuleIndexHandler } from '@core/contentlinks/classes/module-index-handler';
|
||||||
|
import { CoreCourseHelperProvider } from '@core/course/providers/helper';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler to treat links to IMSCP.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class AddonModImscpLinkHandler extends CoreContentLinksModuleIndexHandler {
|
||||||
|
name = 'AddonModImscpLinkHandler';
|
||||||
|
|
||||||
|
constructor(courseHelper: CoreCourseHelperProvider) {
|
||||||
|
super(courseHelper, 'AddonModImscp', 'imscp');
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
// (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 { NavController, NavOptions } from 'ionic-angular';
|
||||||
|
import { CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '@core/course/providers/module-delegate';
|
||||||
|
import { CoreCourseProvider } from '@core/course/providers/course';
|
||||||
|
import { AddonModImscpIndexComponent } from '../components/index/index';
|
||||||
|
import { AddonModImscpProvider } from './imscp';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler to support IMSCP modules.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class AddonModImscpModuleHandler implements CoreCourseModuleHandler {
|
||||||
|
name = 'AddonModImscp';
|
||||||
|
modName = 'imscp';
|
||||||
|
|
||||||
|
constructor(private courseProvider: CoreCourseProvider, protected imscpProvider: AddonModImscpProvider) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 this.imscpProvider.isPluginEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the data required to display the module in the course contents view.
|
||||||
|
*
|
||||||
|
* @param {any} module The module object.
|
||||||
|
* @param {number} courseId The course ID.
|
||||||
|
* @param {number} sectionId The section ID.
|
||||||
|
* @return {CoreCourseModuleHandlerData} Data to render the module.
|
||||||
|
*/
|
||||||
|
getData(module: any, courseId: number, sectionId: number): CoreCourseModuleHandlerData {
|
||||||
|
return {
|
||||||
|
icon: this.courseProvider.getModuleIconSrc('imscp'),
|
||||||
|
title: module.name,
|
||||||
|
class: 'addon-mod_imscp-handler',
|
||||||
|
showDownloadButton: true,
|
||||||
|
action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions): void {
|
||||||
|
navCtrl.push('AddonModImscpIndexPage', {module: module, courseId: courseId}, options);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the component to render the module. This is needed to support singleactivity course format.
|
||||||
|
* The component returned must implement CoreCourseModuleMainComponent.
|
||||||
|
*
|
||||||
|
* @param {any} course The course object.
|
||||||
|
* @param {any} module The module object.
|
||||||
|
* @return {any} The component to use, undefined if not found.
|
||||||
|
*/
|
||||||
|
getMainComponent(course: any, module: any): any {
|
||||||
|
return AddonModImscpIndexComponent;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
// (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 { CorePluginFileHandler } from '@providers/plugin-file-delegate';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler to treat links to IMSCP.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class AddonModImscpPluginFileHandler implements CorePluginFileHandler {
|
||||||
|
name = 'AddonModImscpPluginFileHandler';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the RegExp to match the revision on pluginfile URLs.
|
||||||
|
*
|
||||||
|
* @param {string[]} args Arguments of the pluginfile URL defining component and filearea at least.
|
||||||
|
* @return {RegExp} RegExp to match the revision on pluginfile URLs.
|
||||||
|
*/
|
||||||
|
getComponentRevisionRegExp(args: string[]): RegExp {
|
||||||
|
// Check filearea.
|
||||||
|
if (args[2] == 'content') {
|
||||||
|
// Component + Filearea + Revision
|
||||||
|
return new RegExp('/mod_imscp/content/([0-9]+)/');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args[2] == 'backup') {
|
||||||
|
// Component + Filearea + Revision
|
||||||
|
return new RegExp('/mod_imscp/backup/([0-9]+)/');
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should return the string to remove the revision on pluginfile url.
|
||||||
|
*
|
||||||
|
* @param {string[]} args Arguments of the pluginfile URL defining component and filearea at least.
|
||||||
|
* @return {string} String to remove the revision on pluginfile url.
|
||||||
|
*/
|
||||||
|
getComponentRevisionReplace(args: string[]): string {
|
||||||
|
// Component + Filearea + Revision
|
||||||
|
return '/mod_imscp/' + args[2] + '/0/';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,131 @@
|
||||||
|
// (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, Injector } from '@angular/core';
|
||||||
|
import { CoreCourseModulePrefetchHandlerBase } from '@core/course/classes/module-prefetch-handler';
|
||||||
|
import { CoreFilepoolProvider } from '@providers/filepool';
|
||||||
|
import { AddonModImscpProvider } from './imscp';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler to prefetch IMSCPs.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class AddonModImscpPrefetchHandler extends CoreCourseModulePrefetchHandlerBase {
|
||||||
|
name = 'AddonModImscp';
|
||||||
|
modName = 'imscp';
|
||||||
|
component = AddonModImscpProvider.COMPONENT;
|
||||||
|
isResource = true;
|
||||||
|
|
||||||
|
constructor(injector: Injector, protected imscpProvider: AddonModImscpProvider,
|
||||||
|
protected filepoolProvider: CoreFilepoolProvider) {
|
||||||
|
super(injector);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download the module.
|
||||||
|
*
|
||||||
|
* @param {any} module The module object returned by WS.
|
||||||
|
* @param {number} courseId Course ID.
|
||||||
|
* @param {string} [dirPath] Path of the directory where to store all the content files. @see downloadOrPrefetch.
|
||||||
|
* @return {Promise<any>} Promise resolved when all content is downloaded.
|
||||||
|
*/
|
||||||
|
download(module: any, courseId: number, dirPath?: string): Promise<any> {
|
||||||
|
return this.prefetch(module, courseId, false, dirPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download or prefetch the content.
|
||||||
|
*
|
||||||
|
* @param {any} module The module object returned by WS.
|
||||||
|
* @param {number} courseId Course ID.
|
||||||
|
* @param {boolean} [prefetch] True to prefetch, false to download right away.
|
||||||
|
* @param {string} [dirPath] Path of the directory where to store all the content files. This is to keep the files
|
||||||
|
* relative paths and make the package work in an iframe. Undefined to download the files
|
||||||
|
* in the filepool root folder.
|
||||||
|
* @return {Promise<any>} Promise resolved when all content is downloaded. Data returned is not reliable.
|
||||||
|
*/
|
||||||
|
downloadOrPrefetch(module: any, courseId: number, prefetch?: boolean, dirPath?: string): Promise<any> {
|
||||||
|
const siteId = this.sitesProvider.getCurrentSiteId();
|
||||||
|
|
||||||
|
return this.filepoolProvider.getPackageDirPathByUrl(siteId, module.url).then((dirPath) => {
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
|
promises.push(super.downloadOrPrefetch(module, courseId, prefetch, dirPath));
|
||||||
|
promises.push(this.imscpProvider.getImscp(courseId, module.id, siteId));
|
||||||
|
|
||||||
|
return Promise.all(promises);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns module intro files.
|
||||||
|
*
|
||||||
|
* @param {any} module The module object returned by WS.
|
||||||
|
* @param {number} courseId Course ID.
|
||||||
|
* @return {Promise<any[]>} Promise resolved with list of intro files.
|
||||||
|
*/
|
||||||
|
getIntroFiles(module: any, courseId: number): Promise<any[]> {
|
||||||
|
return this.imscpProvider.getImscp(courseId, module.id).catch(() => {
|
||||||
|
// Not found, return undefined so module description is used.
|
||||||
|
}).then((imscp) => {
|
||||||
|
return this.getIntroFilesFromInstance(module, imscp);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidate the prefetched content.
|
||||||
|
*
|
||||||
|
* @param {number} moduleId The module ID.
|
||||||
|
* @param {number} courseId Course ID the module belongs to.
|
||||||
|
* @return {Promise<any>} Promise resolved when the data is invalidated.
|
||||||
|
*/
|
||||||
|
invalidateContent(moduleId: number, courseId: number): Promise<any> {
|
||||||
|
return this.imscpProvider.invalidateContent(moduleId, courseId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidate WS calls needed to determine module status.
|
||||||
|
*
|
||||||
|
* @param {any} module Module.
|
||||||
|
* @param {number} courseId Course ID the module belongs to.
|
||||||
|
* @return {Promise<any>} Promise resolved when invalidated.
|
||||||
|
*/
|
||||||
|
invalidateModule(module: any, courseId: number): Promise<any> {
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
|
promises.push(this.imscpProvider.invalidateImscpData(courseId));
|
||||||
|
promises.push(this.courseProvider.invalidateModule(module.id));
|
||||||
|
|
||||||
|
return Promise.all(promises);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the handler is enabled on a site level.
|
||||||
|
*
|
||||||
|
* @return {boolean|Promise<boolean>} A boolean, or a promise resolved with a boolean, indicating if the handler is enabled.
|
||||||
|
*/
|
||||||
|
isEnabled(): boolean | Promise<boolean> {
|
||||||
|
return this.imscpProvider.isPluginEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a file is downloadable.
|
||||||
|
*
|
||||||
|
* @param {any} file File to check.
|
||||||
|
* @return {boolean} Whether the file is downloadable.
|
||||||
|
*/
|
||||||
|
isFileDownloadable(file: any): boolean {
|
||||||
|
return this.imscpProvider.isFileDownloadable(file);
|
||||||
|
}
|
||||||
|
}
|
|
@ -86,6 +86,7 @@ import { AddonModPageModule } from '@addon/mod/page/page.module';
|
||||||
import { AddonModQuizModule } from '@addon/mod/quiz/quiz.module';
|
import { AddonModQuizModule } from '@addon/mod/quiz/quiz.module';
|
||||||
import { AddonModUrlModule } from '@addon/mod/url/url.module';
|
import { AddonModUrlModule } from '@addon/mod/url/url.module';
|
||||||
import { AddonModSurveyModule } from '@addon/mod/survey/survey.module';
|
import { AddonModSurveyModule } from '@addon/mod/survey/survey.module';
|
||||||
|
import { AddonModImscpModule } from '@addon/mod/imscp/imscp.module';
|
||||||
import { AddonMessageOutputModule } from '@addon/messageoutput/messageoutput.module';
|
import { AddonMessageOutputModule } from '@addon/messageoutput/messageoutput.module';
|
||||||
import { AddonMessageOutputAirnotifierModule } from '@addon/messageoutput/airnotifier/airnotifier.module';
|
import { AddonMessageOutputAirnotifierModule } from '@addon/messageoutput/airnotifier/airnotifier.module';
|
||||||
import { AddonMessagesModule } from '@addon/messages/messages.module';
|
import { AddonMessagesModule } from '@addon/messages/messages.module';
|
||||||
|
@ -182,6 +183,7 @@ export const CORE_PROVIDERS: any[] = [
|
||||||
AddonModQuizModule,
|
AddonModQuizModule,
|
||||||
AddonModUrlModule,
|
AddonModUrlModule,
|
||||||
AddonModSurveyModule,
|
AddonModSurveyModule,
|
||||||
|
AddonModImscpModule,
|
||||||
AddonMessageOutputModule,
|
AddonMessageOutputModule,
|
||||||
AddonMessageOutputAirnotifierModule,
|
AddonMessageOutputAirnotifierModule,
|
||||||
AddonMessagesModule,
|
AddonMessagesModule,
|
||||||
|
|
|
@ -42,6 +42,7 @@ import { CoreDynamicComponent } from './dynamic-component/dynamic-component';
|
||||||
import { CoreSendMessageFormComponent } from './send-message-form/send-message-form';
|
import { CoreSendMessageFormComponent } from './send-message-form/send-message-form';
|
||||||
import { CoreTimerComponent } from './timer/timer';
|
import { CoreTimerComponent } from './timer/timer';
|
||||||
import { CoreRecaptchaComponent, CoreRecaptchaModalComponent } from './recaptcha/recaptcha';
|
import { CoreRecaptchaComponent, CoreRecaptchaModalComponent } from './recaptcha/recaptcha';
|
||||||
|
import { CoreNavigationBarComponent } from './navigation-bar/navigation-bar';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
|
@ -70,7 +71,8 @@ import { CoreRecaptchaComponent, CoreRecaptchaModalComponent } from './recaptcha
|
||||||
CoreSendMessageFormComponent,
|
CoreSendMessageFormComponent,
|
||||||
CoreTimerComponent,
|
CoreTimerComponent,
|
||||||
CoreRecaptchaComponent,
|
CoreRecaptchaComponent,
|
||||||
CoreRecaptchaModalComponent
|
CoreRecaptchaModalComponent,
|
||||||
|
CoreNavigationBarComponent
|
||||||
],
|
],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
CoreContextMenuPopoverComponent,
|
CoreContextMenuPopoverComponent,
|
||||||
|
@ -106,7 +108,8 @@ import { CoreRecaptchaComponent, CoreRecaptchaModalComponent } from './recaptcha
|
||||||
CoreDynamicComponent,
|
CoreDynamicComponent,
|
||||||
CoreSendMessageFormComponent,
|
CoreSendMessageFormComponent,
|
||||||
CoreTimerComponent,
|
CoreTimerComponent,
|
||||||
CoreRecaptchaComponent
|
CoreRecaptchaComponent,
|
||||||
|
CoreNavigationBarComponent
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class CoreComponentsModule {}
|
export class CoreComponentsModule {}
|
||||||
|
|
|
@ -4,6 +4,7 @@ core-iframe {
|
||||||
}
|
}
|
||||||
iframe {
|
iframe {
|
||||||
border: 0;
|
border: 0;
|
||||||
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.core-loading-container {
|
.core-loading-container {
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Component, Input, Output, OnInit, ViewChild, ElementRef, EventEmitter } from '@angular/core';
|
import { Component, Input, Output, OnInit, ViewChild, ElementRef, EventEmitter, OnChanges, SimpleChange } from '@angular/core';
|
||||||
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
|
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
|
||||||
import { Platform } from 'ionic-angular';
|
import { Platform } from 'ionic-angular';
|
||||||
import { CoreFileProvider } from '@providers/file';
|
import { CoreFileProvider } from '@providers/file';
|
||||||
|
@ -29,7 +29,7 @@ import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||||
selector: 'core-iframe',
|
selector: 'core-iframe',
|
||||||
templateUrl: 'iframe.html'
|
templateUrl: 'iframe.html'
|
||||||
})
|
})
|
||||||
export class CoreIframeComponent implements OnInit {
|
export class CoreIframeComponent implements OnInit, OnChanges {
|
||||||
|
|
||||||
@ViewChild('iframe') iframe: ElementRef;
|
@ViewChild('iframe') iframe: ElementRef;
|
||||||
@Input() src: string;
|
@Input() src: string;
|
||||||
|
@ -56,7 +56,6 @@ export class CoreIframeComponent implements OnInit {
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
const iframe: HTMLIFrameElement = this.iframe && this.iframe.nativeElement;
|
const iframe: HTMLIFrameElement = this.iframe && this.iframe.nativeElement;
|
||||||
|
|
||||||
this.safeUrl = this.sanitizer.bypassSecurityTrustResourceUrl(this.src);
|
|
||||||
this.iframeWidth = this.domUtils.formatPixelsSize(this.iframeWidth) || '100%';
|
this.iframeWidth = this.domUtils.formatPixelsSize(this.iframeWidth) || '100%';
|
||||||
this.iframeHeight = this.domUtils.formatPixelsSize(this.iframeHeight) || '100%';
|
this.iframeHeight = this.domUtils.formatPixelsSize(this.iframeHeight) || '100%';
|
||||||
|
|
||||||
|
@ -82,6 +81,15 @@ export class CoreIframeComponent implements OnInit {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect changes on input properties.
|
||||||
|
*/
|
||||||
|
ngOnChanges(changes: {[name: string]: SimpleChange }): void {
|
||||||
|
if (changes.src) {
|
||||||
|
this.safeUrl = this.sanitizer.bypassSecurityTrustResourceUrl(changes.src.currentValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given an element, return the content window and document.
|
* Given an element, return the content window and document.
|
||||||
*
|
*
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
<ion-grid no-padding *ngIf="previous || info || next">
|
||||||
|
<ion-row>
|
||||||
|
<ion-col text-left>
|
||||||
|
<a ion-button icon-only clear color="info" *ngIf="previous" (click)="action.emit(previous)" [title]="'core.previous' | translate">
|
||||||
|
<ion-icon name="arrow-dropleft-circle"></ion-icon>
|
||||||
|
</a>
|
||||||
|
</ion-col>
|
||||||
|
<ion-col text-center>
|
||||||
|
<a ion-button icon-only clear color="info" *ngIf="info" (click)="showInfo()" [title]="title">
|
||||||
|
<ion-icon name="information-circle"></ion-icon>
|
||||||
|
</a>
|
||||||
|
</ion-col>
|
||||||
|
<ion-col text-right>
|
||||||
|
<a ion-button icon-only clear color="info" *ngIf="next" (click)="action.emit(next)" [title]="'core.next' | translate">
|
||||||
|
<ion-icon name="arrow-dropright-circle"></ion-icon>
|
||||||
|
</a>
|
||||||
|
</ion-col>
|
||||||
|
</ion-row>
|
||||||
|
</ion-grid>
|
|
@ -0,0 +1,47 @@
|
||||||
|
// (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, EventEmitter, Input, Output } from '@angular/core';
|
||||||
|
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component to show a "bar" with arrows to navigate forward/backward and a "info" icon to display more data.
|
||||||
|
*
|
||||||
|
* This directive will show two arrows at the left and right of the screen to navigate to previous/next item when clicked.
|
||||||
|
* If no previous/next item is defined, that arrow won't be shown. It will also show a button to show more info.
|
||||||
|
*
|
||||||
|
* Example usage:
|
||||||
|
* <core-navigation-bar [previous]="prevItem" [next]="nextItem" (action)="goTo($event)"></core-navigation-bar>
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'core-navigation-bar',
|
||||||
|
templateUrl: 'navigation-bar.html',
|
||||||
|
})
|
||||||
|
export class CoreNavigationBarComponent {
|
||||||
|
@Input() previous?: any; // Previous item. If not defined, the previous arrow won't be shown.
|
||||||
|
@Input() next?: any; // Next item. If not defined, the next arrow won't be shown.
|
||||||
|
@Input() info?: string; // Info to show when clicking the info button. If not defined, the info button won't be shown.
|
||||||
|
@Input() title?: string; // Title to show when seeing the info (new page).
|
||||||
|
@Input() component?: string; // Component the bar belongs to.
|
||||||
|
@Input() componentId?: number; // Component ID.
|
||||||
|
@Output() action?: EventEmitter<any>; // Function to call when an arrow is clicked. Will receive as a param the item to load.
|
||||||
|
|
||||||
|
constructor(private textUtils: CoreTextUtilsProvider) {
|
||||||
|
this.action = new EventEmitter<any>();
|
||||||
|
}
|
||||||
|
|
||||||
|
showInfo(): void {
|
||||||
|
this.textUtils.expandText(this.title, this.info, this.component, this.componentId);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue