Merge pull request #2694 from crazyserver/MOBILE-3646
MOBILE-3646 imscp Add imscp activity modulemain
commit
fd314eb4d8
|
@ -15,7 +15,7 @@
|
||||||
<nav>
|
<nav>
|
||||||
<ion-list>
|
<ion-list>
|
||||||
<ion-item class="ion-text-wrap" *ngFor="let chapter of chapters" (click)="loadChapter(chapter.id)"
|
<ion-item class="ion-text-wrap" *ngFor="let chapter of chapters" (click)="loadChapter(chapter.id)"
|
||||||
[class.core-nav-item-selected]="selected == chapter.id"
|
[class.core-selected-item]="selected == chapter.id"
|
||||||
[class.item-dimmed]="chapter.hidden">
|
[class.item-dimmed]="chapter.hidden">
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<p [class.ion-padding-left]="addPadding && chapter.level == 1 ? true : null">
|
<p [class.ion-padding-left]="addPadding && chapter.level == 1 ? true : null">
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
// (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 { NgModule } from '@angular/core';
|
||||||
|
|
||||||
|
import { CoreSharedModule } from '@/core/shared.module';
|
||||||
|
import { CoreCourseComponentsModule } from '@features/course/components/components.module';
|
||||||
|
|
||||||
|
import { AddonModImscpIndexComponent } from './index';
|
||||||
|
import { AddonModImscpTocComponent } from './toc/toc';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
AddonModImscpIndexComponent,
|
||||||
|
AddonModImscpTocComponent,
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
CoreSharedModule,
|
||||||
|
CoreCourseComponentsModule,
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
AddonModImscpIndexComponent,
|
||||||
|
AddonModImscpTocComponent,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class AddonModImscpComponentsModule {}
|
|
@ -0,0 +1,43 @@
|
||||||
|
<!-- Buttons to add to the header. -->
|
||||||
|
<core-navbar-buttons slot="end">
|
||||||
|
<ion-button (click)="showToc()">
|
||||||
|
<ion-icon name="fas-bookmark" slot="icon-only"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
<core-context-menu>
|
||||||
|
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate"
|
||||||
|
[href]="externalUrl" iconAction="fas-external-link-alt">
|
||||||
|
</core-context-menu-item>
|
||||||
|
<core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
|
||||||
|
(action)="expandDescription()" iconAction="fas-arrow-right">
|
||||||
|
</core-context-menu-item>
|
||||||
|
<core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}"
|
||||||
|
iconAction="far-newspaper" (action)="gotoBlog()">
|
||||||
|
</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($event)"
|
||||||
|
[iconAction]="prefetchStatusIcon" [closeOnClick]="false">
|
||||||
|
</core-context-menu-item>
|
||||||
|
<core-context-menu-item *ngIf="size" [priority]="500" [content]="'core.clearstoreddata' | translate:{$a: size}"
|
||||||
|
iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
|
||||||
|
</core-context-menu-item>
|
||||||
|
</core-context-menu>
|
||||||
|
</core-navbar-buttons>
|
||||||
|
|
||||||
|
<!-- Content. -->
|
||||||
|
<core-loading [hideUntil]="loaded" class="core-loading-center safe-area-page">
|
||||||
|
|
||||||
|
<ion-card class="core-warning-card" *ngIf="warning">
|
||||||
|
<ion-icon name="fas-exclamation-triangle" slot="start"></ion-icon>
|
||||||
|
<span [innerHTML]="warning"></span>
|
||||||
|
</ion-card>
|
||||||
|
|
||||||
|
<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" contextLevel="module"
|
||||||
|
[contextInstanceId]="module?.id" [courseId]="courseId">
|
||||||
|
</core-navigation-bar>
|
||||||
|
<core-iframe [src]="src"></core-iframe>
|
||||||
|
</div>
|
||||||
|
</core-loading>
|
|
@ -0,0 +1,10 @@
|
||||||
|
.addon-mod-imscp-container {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
core-iframe {
|
||||||
|
flex-basis: 100%;
|
||||||
|
}
|
|
@ -0,0 +1,176 @@
|
||||||
|
// (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 { Component, OnInit, Optional } from '@angular/core';
|
||||||
|
import { CoreSilentError } from '@classes/errors/silenterror';
|
||||||
|
import {
|
||||||
|
CoreCourseModuleMainResourceComponent,
|
||||||
|
CoreCourseResourceDownloadResult,
|
||||||
|
} from '@features/course/classes/main-resource-component';
|
||||||
|
import { CoreCourseContentsPage } from '@features/course/pages/contents/contents';
|
||||||
|
import { CoreCourse } from '@features/course/services/course';
|
||||||
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
|
import { ModalController } from '@singletons';
|
||||||
|
import { AddonModImscpProvider, AddonModImscp, AddonModImscpTocItem } from '../../services/imscp';
|
||||||
|
import { AddonModImscpTocComponent } from '../toc/toc';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component that displays a IMSCP.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'addon-mod-imscp-index',
|
||||||
|
templateUrl: 'addon-mod-imscp-index.html',
|
||||||
|
styleUrls: ['index.scss'],
|
||||||
|
})
|
||||||
|
export class AddonModImscpIndexComponent extends CoreCourseModuleMainResourceComponent implements OnInit {
|
||||||
|
|
||||||
|
component = AddonModImscpProvider.COMPONENT;
|
||||||
|
|
||||||
|
items: AddonModImscpTocItem[] = [];
|
||||||
|
currentItem?: string;
|
||||||
|
src = '';
|
||||||
|
warning = '';
|
||||||
|
|
||||||
|
// Initialize empty previous/next to prevent showing arrows for an instant before they're hidden.
|
||||||
|
previousItem = '';
|
||||||
|
nextItem = '';
|
||||||
|
|
||||||
|
|
||||||
|
constructor(@Optional() courseContentsPage?: CoreCourseContentsPage) {
|
||||||
|
super('AddonModImscpIndexComponent', courseContentsPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component being initialized.
|
||||||
|
*/
|
||||||
|
async ngOnInit(): Promise<void> {
|
||||||
|
super.ngOnInit();
|
||||||
|
|
||||||
|
await this.loadContent();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await AddonModImscp.logView(this.module!.instance!, this.module!.name);
|
||||||
|
CoreCourse.checkModuleCompletion(this.courseId!, this.module!.completiondata);
|
||||||
|
} catch {
|
||||||
|
// Ignore errors.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform the invalidate content function.
|
||||||
|
*
|
||||||
|
* @return Resolved when done.
|
||||||
|
*/
|
||||||
|
protected async invalidateContent(): Promise<void> {
|
||||||
|
await AddonModImscp.invalidateContent(this.module!.id, this.courseId!);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download imscp contents.
|
||||||
|
*
|
||||||
|
* @param refresh Whether we're refreshing data.
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
protected async fetchContent(refresh = false): Promise<void> {
|
||||||
|
let downloadResult: CoreCourseResourceDownloadResult;
|
||||||
|
const promises: Promise<void>[] = [];
|
||||||
|
|
||||||
|
promises.push(AddonModImscp.getImscp(this.courseId!, this.module!.id).then((imscp) => {
|
||||||
|
this.description = imscp.intro;
|
||||||
|
this.dataRetrieved.emit(imscp);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}));
|
||||||
|
|
||||||
|
promises.push(this.downloadResourceIfNeeded(refresh).then((result) => {
|
||||||
|
downloadResult = result;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}));
|
||||||
|
|
||||||
|
try {
|
||||||
|
await Promise.all(promises);
|
||||||
|
|
||||||
|
this.items = AddonModImscp.createItemList(this.module!.contents);
|
||||||
|
|
||||||
|
if (this.items.length && typeof this.currentItem == 'undefined') {
|
||||||
|
this.currentItem = this.items[0].href;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.loadItem(this.currentItem);
|
||||||
|
} catch (error) {
|
||||||
|
CoreDomUtils.showErrorModalDefault(error, 'addon.mod_imscp.deploymenterror', true);
|
||||||
|
|
||||||
|
throw new CoreSilentError(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.warning = downloadResult!.failed ? this.getErrorDownloadingSomeFilesMessage(downloadResult!.error!) : '';
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
this.fillContextMenu(refresh);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads an item.
|
||||||
|
*
|
||||||
|
* @param itemId Item ID.
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
async loadItem(itemId?: string): Promise<void> {
|
||||||
|
const src = await AddonModImscp.getIframeSrc(this.module!, itemId);
|
||||||
|
this.currentItem = itemId;
|
||||||
|
this.previousItem = itemId ? AddonModImscp.getPreviousItem(this.items, itemId) : '';
|
||||||
|
this.nextItem = itemId ? AddonModImscp.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.
|
||||||
|
*/
|
||||||
|
async showToc(): Promise<void> {
|
||||||
|
// Create the toc modal.
|
||||||
|
const modal = await ModalController.create({
|
||||||
|
component: AddonModImscpTocComponent,
|
||||||
|
componentProps: {
|
||||||
|
items: this.items,
|
||||||
|
selected: this.currentItem,
|
||||||
|
},
|
||||||
|
cssClass: 'core-modal-lateral',
|
||||||
|
showBackdrop: true,
|
||||||
|
backdropDismiss: true,
|
||||||
|
// @todo enterAnimation: 'core-modal-lateral-transition',
|
||||||
|
// @todo leaveAnimation: 'core-modal-lateral-transition',
|
||||||
|
});
|
||||||
|
|
||||||
|
await modal.present();
|
||||||
|
|
||||||
|
const result = await modal.onDidDismiss();
|
||||||
|
|
||||||
|
if (result.data) {
|
||||||
|
this.loadItem(result.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-buttons slot="start">
|
||||||
|
<ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
|
||||||
|
</ion-buttons>
|
||||||
|
<ion-title>{{ 'addon.mod_imscp.toc' | translate }}</ion-title>
|
||||||
|
<ion-buttons slot="end">
|
||||||
|
<ion-button (click)="closeModal()" [attr.aria-label]="'core.close' | translate">
|
||||||
|
<ion-icon name="fas-times" slot="icon-only"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
</ion-buttons>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
<ion-content>
|
||||||
|
<nav>
|
||||||
|
<ion-list>
|
||||||
|
<ion-item *ngFor="let item of items" (click)="loadItem(item.href)"
|
||||||
|
[class.core-selected-item]="selected == item.href">
|
||||||
|
<ion-label [class.core-bold]="!item.href">
|
||||||
|
<span class="ion-padding-left" *ngFor="let i of getNumberForPadding(item.level)"></span>{{item.title}}
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
</ion-list>
|
||||||
|
</nav>
|
||||||
|
</ion-content>
|
|
@ -0,0 +1,57 @@
|
||||||
|
// (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 { Component, Input } from '@angular/core';
|
||||||
|
import { ModalController } from '@singletons';
|
||||||
|
import { AddonModImscpTocItem } from '../../services/imscp';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modal to display the TOC of a imscp.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'addon-mod-imscp-toc',
|
||||||
|
templateUrl: 'toc.html',
|
||||||
|
})
|
||||||
|
export class AddonModImscpTocComponent {
|
||||||
|
|
||||||
|
@Input() items: AddonModImscpTocItem[] = [];
|
||||||
|
@Input() selected?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function called when an item is clicked.
|
||||||
|
*
|
||||||
|
* @param id ID of the clicked item.
|
||||||
|
*/
|
||||||
|
loadItem(id: string): void {
|
||||||
|
ModalController.dismiss(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get dummy array for padding.
|
||||||
|
*
|
||||||
|
* @param n Array length.
|
||||||
|
* @return Dummy array with n elements.
|
||||||
|
*/
|
||||||
|
getNumberForPadding(n: number): number[] {
|
||||||
|
return new Array(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close modal.
|
||||||
|
*/
|
||||||
|
closeModal(): void {
|
||||||
|
ModalController.dismiss();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
// (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 { NgModule } from '@angular/core';
|
||||||
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
|
||||||
|
import { CoreSharedModule } from '@/core/shared.module';
|
||||||
|
import { AddonModImscpComponentsModule } from './components/components.module';
|
||||||
|
import { AddonModImscpIndexPage } from './pages/index/index.page';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{
|
||||||
|
path: ':courseId/:cmId',
|
||||||
|
component: AddonModImscpIndexPage,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
RouterModule.forChild(routes),
|
||||||
|
CoreSharedModule,
|
||||||
|
AddonModImscpComponentsModule,
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
AddonModImscpIndexPage,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class AddonModImscpLazyModule {}
|
|
@ -0,0 +1,56 @@
|
||||||
|
// (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 { APP_INITIALIZER, NgModule } from '@angular/core';
|
||||||
|
import { Routes } from '@angular/router';
|
||||||
|
import { CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate';
|
||||||
|
import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate';
|
||||||
|
import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate';
|
||||||
|
import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module';
|
||||||
|
import { CorePluginFileDelegate } from '@services/plugin-file-delegate';
|
||||||
|
import { AddonModImscpComponentsModule } from './components/components.module';
|
||||||
|
import { AddonModImscpIndexLinkHandler } from './services/handlers/index-link';
|
||||||
|
import { AddonModImscpListLinkHandler } from './services/handlers/list-link';
|
||||||
|
import { AddonModImscpModuleHandler, AddonModImscpModuleHandlerService } from './services/handlers/module';
|
||||||
|
import { AddonModImscpPluginFileHandler } from './services/handlers/pluginfile';
|
||||||
|
import { AddonModImscpPrefetchHandler } from './services/handlers/prefetch';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{
|
||||||
|
path: AddonModImscpModuleHandlerService.PAGE_NAME,
|
||||||
|
loadChildren: () => import('./imscp-lazy.module').then(m => m.AddonModImscpLazyModule),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
CoreMainMenuTabRoutingModule.forChild(routes),
|
||||||
|
AddonModImscpComponentsModule,
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: APP_INITIALIZER,
|
||||||
|
multi: true,
|
||||||
|
deps: [],
|
||||||
|
useFactory: () => () => {
|
||||||
|
CoreCourseModuleDelegate.registerHandler(AddonModImscpModuleHandler.instance);
|
||||||
|
CoreContentLinksDelegate.registerHandler(AddonModImscpIndexLinkHandler.instance);
|
||||||
|
CoreContentLinksDelegate.registerHandler(AddonModImscpListLinkHandler.instance);
|
||||||
|
CoreCourseModulePrefetchDelegate.registerHandler(AddonModImscpPrefetchHandler.instance);
|
||||||
|
CorePluginFileDelegate.registerHandler(AddonModImscpPluginFileHandler.instance);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class AddonModImscpModule {}
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"deploymenterror": "Content package error!",
|
||||||
|
"modulenameplural": "IMS content packages",
|
||||||
|
"showmoduledescription": "Show description",
|
||||||
|
"toc": "TOC"
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-buttons slot="start">
|
||||||
|
<ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
|
||||||
|
</ion-buttons>
|
||||||
|
<ion-title>
|
||||||
|
<core-format-text [text]="title" contextLevel="module" [contextInstanceId]="module?.id" [courseId]="courseId">
|
||||||
|
</core-format-text>
|
||||||
|
</ion-title>
|
||||||
|
|
||||||
|
<ion-buttons slot="end">
|
||||||
|
<!-- The buttons defined by the component will be added in here. -->
|
||||||
|
</ion-buttons>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
<ion-content>
|
||||||
|
<addon-mod-imscp-index [module]="module" [courseId]="courseId" (dataRetrieved)="updateData($event)"></addon-mod-imscp-index>
|
||||||
|
</ion-content>
|
|
@ -0,0 +1,30 @@
|
||||||
|
// (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 { Component, ViewChild } from '@angular/core';
|
||||||
|
import { CoreCourseModuleMainActivityPage } from '@features/course/classes/main-activity-page';
|
||||||
|
import { AddonModImscpIndexComponent } from '../../components/index/index';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Imscp that displays a IMSCP.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'page-addon-mod-imscp-index',
|
||||||
|
templateUrl: 'index.html',
|
||||||
|
})
|
||||||
|
export class AddonModImscpIndexPage extends CoreCourseModuleMainActivityPage<AddonModImscpIndexComponent> {
|
||||||
|
|
||||||
|
@ViewChild(AddonModImscpIndexComponent) activityComponent?: AddonModImscpIndexComponent;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
// (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 { Injectable } from '@angular/core';
|
||||||
|
import { CoreContentLinksModuleIndexHandler } from '@features/contentlinks/classes/module-index-handler';
|
||||||
|
import { makeSingleton } from '@singletons';
|
||||||
|
import { AddonModImscp } from '../imscp';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler to treat links to IMSCP.
|
||||||
|
*/
|
||||||
|
@Injectable( { providedIn: 'root' })
|
||||||
|
export class AddonModImscpIndexLinkHandlerService extends CoreContentLinksModuleIndexHandler {
|
||||||
|
|
||||||
|
name = 'AddonModImscpLinkHandler';
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super('AddonModImscp', 'imscp', 'i');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
isEnabled(siteId: string): Promise<boolean> {
|
||||||
|
return AddonModImscp.isPluginEnabled(siteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
export const AddonModImscpIndexLinkHandler = makeSingleton(AddonModImscpIndexLinkHandlerService);
|
|
@ -0,0 +1,40 @@
|
||||||
|
// (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 { Injectable } from '@angular/core';
|
||||||
|
import { CoreContentLinksModuleListHandler } from '@features/contentlinks/classes/module-list-handler';
|
||||||
|
import { makeSingleton } from '@singletons';
|
||||||
|
import { AddonModImscp } from '../imscp';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler to treat links to IMSCP list page.
|
||||||
|
*/
|
||||||
|
@Injectable( { providedIn: 'root' })
|
||||||
|
export class AddonModImscpListLinkHandlerService extends CoreContentLinksModuleListHandler {
|
||||||
|
|
||||||
|
name = 'AddonModImscpListLinkHandler';
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super('AddonModImscp', 'imscp');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
isEnabled(siteId: string): Promise<boolean> {
|
||||||
|
return AddonModImscp.isPluginEnabled(siteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
export const AddonModImscpListLinkHandler = makeSingleton(AddonModImscpListLinkHandlerService);
|
|
@ -0,0 +1,83 @@
|
||||||
|
// (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 { Injectable, Type } from '@angular/core';
|
||||||
|
import { CoreCourse, CoreCourseAnyModuleData } from '@features/course/services/course';
|
||||||
|
import { CoreCourseModule } from '@features/course/services/course-helper';
|
||||||
|
import { CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '@features/course/services/module-delegate';
|
||||||
|
import { CoreNavigationOptions, CoreNavigator } from '@services/navigator';
|
||||||
|
import { makeSingleton } from '@singletons';
|
||||||
|
import { AddonModImscpIndexComponent } from '../../components/index';
|
||||||
|
import { AddonModImscp } from '../imscp';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler to support IMSCP modules.
|
||||||
|
*/
|
||||||
|
@Injectable( { providedIn: 'root' })
|
||||||
|
export class AddonModImscpModuleHandlerService implements CoreCourseModuleHandler {
|
||||||
|
|
||||||
|
static readonly PAGE_NAME = 'mod_imscp';
|
||||||
|
|
||||||
|
name = 'AddonModImscp';
|
||||||
|
modName = 'imscp';
|
||||||
|
|
||||||
|
supportedFeatures = {
|
||||||
|
[CoreConstants.FEATURE_MOD_ARCHETYPE]: CoreConstants.MOD_ARCHETYPE_RESOURCE,
|
||||||
|
[CoreConstants.FEATURE_GROUPS]: false,
|
||||||
|
[CoreConstants.FEATURE_GROUPINGS]: false,
|
||||||
|
[CoreConstants.FEATURE_MOD_INTRO]: true,
|
||||||
|
[CoreConstants.FEATURE_COMPLETION_TRACKS_VIEWS]: true,
|
||||||
|
[CoreConstants.FEATURE_GRADE_HAS_GRADE]: false,
|
||||||
|
[CoreConstants.FEATURE_GRADE_OUTCOMES]: false,
|
||||||
|
[CoreConstants.FEATURE_BACKUP_MOODLE2]: true,
|
||||||
|
[CoreConstants.FEATURE_SHOW_DESCRIPTION]: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
isEnabled(): Promise<boolean> {
|
||||||
|
return AddonModImscp.isPluginEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
getData(module: CoreCourseAnyModuleData): CoreCourseModuleHandlerData {
|
||||||
|
return {
|
||||||
|
icon: CoreCourse.getModuleIconSrc(this.modName, 'modicon' in module ? module.modicon : undefined),
|
||||||
|
title: module.name,
|
||||||
|
class: 'addon-mod_imscp-handler',
|
||||||
|
showDownloadButton: true,
|
||||||
|
action(event: Event, module: CoreCourseModule, courseId: number, options?: CoreNavigationOptions): void {
|
||||||
|
options = options || {};
|
||||||
|
options.params = options.params || {};
|
||||||
|
Object.assign(options.params, { module });
|
||||||
|
const routeParams = '/' + courseId + '/' + module.id;
|
||||||
|
|
||||||
|
CoreNavigator.navigateToSitePath(AddonModImscpModuleHandlerService.PAGE_NAME + routeParams, options);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async getMainComponent(): Promise<Type<unknown> | undefined> {
|
||||||
|
return AddonModImscpIndexComponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
export const AddonModImscpModuleHandler = makeSingleton(AddonModImscpModuleHandlerService);
|
|
@ -0,0 +1,62 @@
|
||||||
|
// (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 { Injectable } from '@angular/core';
|
||||||
|
import { CorePluginFileHandler } from '@services/plugin-file-delegate';
|
||||||
|
import { makeSingleton } from '@singletons';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler to treat links to IMSCP.
|
||||||
|
*/
|
||||||
|
@Injectable( { providedIn: 'root' })
|
||||||
|
export class AddonModImscpPluginFileHandlerService implements CorePluginFileHandler {
|
||||||
|
|
||||||
|
name = 'AddonModImscpPluginFileHandler';
|
||||||
|
component = 'mod_imscp';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
getComponentRevisionRegExp(args: string[]): RegExp | undefined {
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
getComponentRevisionReplace(args: string[]): string {
|
||||||
|
// Component + Filearea + Revision
|
||||||
|
return '/mod_imscp/' + args[2] + '/0/';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async isEnabled(): Promise<boolean> {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
export const AddonModImscpPluginFileHandler = makeSingleton(AddonModImscpPluginFileHandlerService);
|
|
@ -0,0 +1,102 @@
|
||||||
|
// (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 { Injectable } from '@angular/core';
|
||||||
|
import { CoreCourseResourcePrefetchHandlerBase } from '@features/course/classes/resource-prefetch-handler';
|
||||||
|
import {
|
||||||
|
CoreCourse,
|
||||||
|
CoreCourseAnyModuleData,
|
||||||
|
CoreCourseModuleContentFile,
|
||||||
|
CoreCourseWSModule,
|
||||||
|
} from '@features/course/services/course';
|
||||||
|
import { CoreFilepool } from '@services/filepool';
|
||||||
|
import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
|
||||||
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
|
import { CoreWSExternalFile } from '@services/ws';
|
||||||
|
import { makeSingleton } from '@singletons';
|
||||||
|
import { AddonModImscp, AddonModImscpProvider } from '../imscp';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler to prefetch IMSCPs.
|
||||||
|
*/
|
||||||
|
@Injectable( { providedIn: 'root' })
|
||||||
|
export class AddonModImscpPrefetchHandlerService extends CoreCourseResourcePrefetchHandlerBase {
|
||||||
|
|
||||||
|
name = 'AddonModImscp';
|
||||||
|
modName = 'imscp';
|
||||||
|
component = AddonModImscpProvider.COMPONENT;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async downloadOrPrefetch(module: CoreCourseWSModule, courseId: number, prefetch?: boolean): Promise<void> {
|
||||||
|
const siteId = CoreSites.getCurrentSiteId();
|
||||||
|
|
||||||
|
const dirPath = await CoreFilepool.getPackageDirPathByUrl(siteId, module.url!);
|
||||||
|
const promises: Promise<unknown>[] = [];
|
||||||
|
|
||||||
|
promises.push(super.downloadOrPrefetch(module, courseId, prefetch, dirPath));
|
||||||
|
promises.push(AddonModImscp.getImscp(courseId, module.id, {
|
||||||
|
readingStrategy: CoreSitesReadingStrategy.OnlyNetwork,
|
||||||
|
siteId,
|
||||||
|
}));
|
||||||
|
|
||||||
|
await Promise.all(promises);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async getIntroFiles(module: CoreCourseAnyModuleData, courseId: number): Promise<CoreWSExternalFile[]> {
|
||||||
|
// If not found, use undefined so module description is used.
|
||||||
|
const imscp = await CoreUtils.ignoreErrors(AddonModImscp.getImscp(courseId, module.id));
|
||||||
|
|
||||||
|
return this.getIntroFilesFromInstance(module, imscp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async invalidateContent(moduleId: number, courseId: number): Promise<void> {
|
||||||
|
return AddonModImscp.invalidateContent(moduleId, courseId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async invalidateModule(module: CoreCourseAnyModuleData, courseId: number): Promise<void> {
|
||||||
|
const promises: Promise<unknown>[] = [];
|
||||||
|
|
||||||
|
promises.push(AddonModImscp.invalidateImscpData(courseId));
|
||||||
|
promises.push(CoreCourse.invalidateModule(module.id));
|
||||||
|
|
||||||
|
await Promise.all(promises);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
isEnabled(): Promise<boolean> {
|
||||||
|
return AddonModImscp.isPluginEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
isFileDownloadable(file: CoreCourseModuleContentFile): boolean {
|
||||||
|
return AddonModImscp.isFileDownloadable(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
export const AddonModImscpPrefetchHandler = makeSingleton(AddonModImscpPrefetchHandlerService);
|
|
@ -0,0 +1,399 @@
|
||||||
|
// (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 { Injectable } from '@angular/core';
|
||||||
|
import { CoreError } from '@classes/errors/error';
|
||||||
|
import { CoreSite, CoreSiteWSPreSets } from '@classes/site';
|
||||||
|
import { CoreCourse, CoreCourseModuleContentFile } from '@features/course/services/course';
|
||||||
|
import { CoreCourseModule } from '@features/course/services/course-helper';
|
||||||
|
import { CoreCourseLogHelper } from '@features/course/services/log-helper';
|
||||||
|
import { CoreApp } from '@services/app';
|
||||||
|
import { CoreFilepool } from '@services/filepool';
|
||||||
|
import { CoreSitesCommonWSOptions, CoreSites } from '@services/sites';
|
||||||
|
import { CoreTextUtils } from '@services/utils/text';
|
||||||
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
|
import { CoreWSExternalFile, CoreWSExternalWarning } from '@services/ws';
|
||||||
|
import { makeSingleton } from '@singletons';
|
||||||
|
|
||||||
|
const ROOT_CACHE_KEY = 'mmaModImscp:';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service that provides some features for IMSCP.
|
||||||
|
*/
|
||||||
|
@Injectable( { providedIn: 'root' })
|
||||||
|
export class AddonModImscpProvider {
|
||||||
|
|
||||||
|
static readonly COMPONENT = 'mmaModImscp';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the IMSCP toc as an array.
|
||||||
|
*
|
||||||
|
* @param contents The module contents.
|
||||||
|
* @return The toc.
|
||||||
|
*/
|
||||||
|
protected getToc(contents: CoreCourseModuleContentFile[]): AddonModImscpTocItemTree[] {
|
||||||
|
if (!contents || !contents.length) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return CoreTextUtils.parseJSON<AddonModImscpTocItemTree[]>(contents[0].content || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the imscp toc as an array of items (not nested) to build the navigation tree.
|
||||||
|
*
|
||||||
|
* @param contents The module contents.
|
||||||
|
* @return The toc as a list.
|
||||||
|
*/
|
||||||
|
createItemList(contents: CoreCourseModuleContentFile[]): AddonModImscpTocItem[] {
|
||||||
|
const items: AddonModImscpTocItem[] = [];
|
||||||
|
|
||||||
|
this.getToc(contents).forEach((item) => {
|
||||||
|
items.push({ href: item.href, title: item.title, level: item.level });
|
||||||
|
|
||||||
|
item.subitems.forEach((subitem) => {
|
||||||
|
items.push({ href: subitem.href, title: subitem.title, level: subitem.level });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the previous item to the given one.
|
||||||
|
*
|
||||||
|
* @param items The items list.
|
||||||
|
* @param itemId The current item.
|
||||||
|
* @return The previous item id.
|
||||||
|
*/
|
||||||
|
getPreviousItem(items: AddonModImscpTocItem[], itemId: string): string {
|
||||||
|
const position = this.getItemPosition(items, itemId);
|
||||||
|
|
||||||
|
if (position == -1) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
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 items The items list.
|
||||||
|
* @param itemId The current item.
|
||||||
|
* @return The next item id.
|
||||||
|
*/
|
||||||
|
getNextItem(items: AddonModImscpTocItem[], itemId: string): string {
|
||||||
|
const position = this.getItemPosition(items, itemId);
|
||||||
|
|
||||||
|
if (position == -1) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
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 items The items list.
|
||||||
|
* @param itemId The item to search.
|
||||||
|
* @return The item position.
|
||||||
|
*/
|
||||||
|
protected getItemPosition(items: AddonModImscpTocItem[], itemId: string): number {
|
||||||
|
return items.findIndex((item) => item.href == itemId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if we should ommit the file download.
|
||||||
|
*
|
||||||
|
* @param fileName The file name
|
||||||
|
* @return 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 courseId Course ID.
|
||||||
|
* @return Cache key.
|
||||||
|
*/
|
||||||
|
protected getImscpDataCacheKey(courseId: number): string {
|
||||||
|
return ROOT_CACHE_KEY + 'imscp:' + courseId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a imscp with key=value. If more than one is found, only the first will be returned.
|
||||||
|
*
|
||||||
|
* @param courseId Course ID.
|
||||||
|
* @param key Name of the property to check.
|
||||||
|
* @param value Value to search.
|
||||||
|
* @param options Other options.
|
||||||
|
* @return Promise resolved when the imscp is retrieved.
|
||||||
|
*/
|
||||||
|
protected async getImscpByKey(
|
||||||
|
courseId: number,
|
||||||
|
key: string,
|
||||||
|
value: number,
|
||||||
|
options: CoreSitesCommonWSOptions = {},
|
||||||
|
): Promise<AddonModImscpImscp> {
|
||||||
|
const site = await CoreSites.getSite(options.siteId);
|
||||||
|
|
||||||
|
const params: AddonModImscpGetImscpsByCoursesWSParams = {
|
||||||
|
courseids: [courseId],
|
||||||
|
};
|
||||||
|
|
||||||
|
const preSets: CoreSiteWSPreSets = {
|
||||||
|
cacheKey: this.getImscpDataCacheKey(courseId),
|
||||||
|
updateFrequency: CoreSite.FREQUENCY_RARELY,
|
||||||
|
component: AddonModImscpProvider.COMPONENT,
|
||||||
|
...CoreSites.getReadingStrategyPreSets(options.readingStrategy),
|
||||||
|
};
|
||||||
|
|
||||||
|
const response =
|
||||||
|
await site.read<AddonModImscpGetImscpsByCoursesWSResponse>('mod_imscp_get_imscps_by_courses', params, preSets);
|
||||||
|
|
||||||
|
const currentImscp = response.imscps.find((imscp) => imscp[key] == value);
|
||||||
|
if (currentImscp) {
|
||||||
|
return currentImscp;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new CoreError('Imscp not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a imscp by course module ID.
|
||||||
|
*
|
||||||
|
* @param courseId Course ID.
|
||||||
|
* @param cmId Course module ID.
|
||||||
|
* @param options Other options.
|
||||||
|
* @return Promise resolved when the imscp is retrieved.
|
||||||
|
*/
|
||||||
|
getImscp(courseId: number, cmId: number, options: CoreSitesCommonWSOptions = {}): Promise<AddonModImscpImscp> {
|
||||||
|
return this.getImscpByKey(courseId, 'coursemodule', cmId, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a filepath, get a certain fileurl from module contents.
|
||||||
|
*
|
||||||
|
* @param items Module contents.
|
||||||
|
* @param targetFilePath Path of the searched file.
|
||||||
|
* @return File URL.
|
||||||
|
*/
|
||||||
|
protected getFileUrlFromContents(items: CoreCourseModuleContentFile[], targetFilePath: string): string | undefined {
|
||||||
|
const item = items.find((item) => {
|
||||||
|
if (item.type != 'file') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const filePath = CoreTextUtils.concatenatePaths(item.filepath, item.filename);
|
||||||
|
const filePathAlt = filePath.charAt(0) === '/' ? filePath.substr(1) : '/' + filePath;
|
||||||
|
|
||||||
|
// Check if it's main file.
|
||||||
|
return filePath === targetFilePath || filePathAlt === targetFilePath;
|
||||||
|
});
|
||||||
|
|
||||||
|
return item?.fileurl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get src of a imscp item.
|
||||||
|
*
|
||||||
|
* @param module The module object.
|
||||||
|
* @param itemHref Href of item to get. If not defined, gets src of main item.
|
||||||
|
* @return Promise resolved with the item src.
|
||||||
|
*/
|
||||||
|
async getIframeSrc(module: CoreCourseModule, itemHref?: string): Promise<string> {
|
||||||
|
if (!itemHref) {
|
||||||
|
const toc = this.getToc(module.contents);
|
||||||
|
if (!toc.length) {
|
||||||
|
throw new CoreError('Empty TOC');
|
||||||
|
}
|
||||||
|
itemHref = toc[0].href;
|
||||||
|
}
|
||||||
|
|
||||||
|
const siteId = CoreSites.getCurrentSiteId();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const dirPath = await CoreFilepool.getPackageDirUrlByUrl(siteId, module!.url!);
|
||||||
|
|
||||||
|
return CoreTextUtils.concatenatePaths(dirPath, itemHref);
|
||||||
|
} catch (error) {
|
||||||
|
// Error getting directory, there was an error downloading or we're in browser. Return online URL if connected.
|
||||||
|
if (CoreApp.isOnline()) {
|
||||||
|
const indexUrl = this.getFileUrlFromContents(module.contents, itemHref);
|
||||||
|
|
||||||
|
if (indexUrl) {
|
||||||
|
const site = await CoreSites.getSite(siteId);
|
||||||
|
|
||||||
|
return site.checkAndFixPluginfileURL(indexUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidate the prefetched content.
|
||||||
|
*
|
||||||
|
* @param moduleId The module ID.
|
||||||
|
* @param courseId Course ID of the module.
|
||||||
|
* @param siteId Site ID. If not defined, current site.
|
||||||
|
* @return Promise resolved when the content is invalidated.
|
||||||
|
*/
|
||||||
|
async invalidateContent(moduleId: number, courseId: number, siteId?: string): Promise<void> {
|
||||||
|
siteId = siteId || CoreSites.getCurrentSiteId();
|
||||||
|
|
||||||
|
const promises: Promise<void>[] = [];
|
||||||
|
|
||||||
|
promises.push(this.invalidateImscpData(courseId, siteId));
|
||||||
|
promises.push(CoreFilepool.invalidateFilesByComponent(siteId, AddonModImscpProvider.COMPONENT, moduleId));
|
||||||
|
promises.push(CoreCourse.invalidateModule(moduleId, siteId));
|
||||||
|
|
||||||
|
await CoreUtils.allPromises(promises);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidates imscp data.
|
||||||
|
*
|
||||||
|
* @param courseId Course ID.
|
||||||
|
* @param siteId Site ID. If not defined, current site.
|
||||||
|
* @return Promise resolved when the data is invalidated.
|
||||||
|
*/
|
||||||
|
async invalidateImscpData(courseId: number, siteId?: string): Promise<void> {
|
||||||
|
const site = await CoreSites.getSite(siteId);
|
||||||
|
|
||||||
|
await 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 file File to check.
|
||||||
|
* @return True if downloadable, false otherwise.
|
||||||
|
*/
|
||||||
|
isFileDownloadable(file: CoreCourseModuleContentFile): boolean {
|
||||||
|
return file.type === 'file' && !this.checkSpecialFiles(file.filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return whether or not the plugin is enabled in a certain site.
|
||||||
|
*
|
||||||
|
* @param siteId Site ID. If not defined, current site.
|
||||||
|
* @return Promise resolved with true if plugin is enabled, rejected or resolved with false otherwise.
|
||||||
|
*/
|
||||||
|
async isPluginEnabled(siteId?: string): Promise<boolean> {
|
||||||
|
const site = await CoreSites.getSite(siteId);
|
||||||
|
|
||||||
|
return site.canDownloadFiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Report a IMSCP as being viewed.
|
||||||
|
*
|
||||||
|
* @param id Module ID.
|
||||||
|
* @param name Name of the imscp.
|
||||||
|
* @param siteId Site ID. If not defined, current site.
|
||||||
|
* @return Promise resolved when the WS call is successful.
|
||||||
|
*/
|
||||||
|
async logView(id: number, name?: string, siteId?: string): Promise<void> {
|
||||||
|
const params: AddonModImscpViewImscpWSParams = {
|
||||||
|
imscpid: id,
|
||||||
|
};
|
||||||
|
|
||||||
|
await CoreCourseLogHelper.logSingle(
|
||||||
|
'mod_imscp_view_imscp',
|
||||||
|
params,
|
||||||
|
AddonModImscpProvider.COMPONENT,
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
'imscp',
|
||||||
|
{},
|
||||||
|
siteId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
export const AddonModImscp = makeSingleton(AddonModImscpProvider);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Params of mod_imscp_view_imscp WS.
|
||||||
|
*/
|
||||||
|
type AddonModImscpViewImscpWSParams = {
|
||||||
|
imscpid: number; // Imscp instance id.
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IMSCP returned by mod_imscp_get_imscps_by_courses.
|
||||||
|
*/
|
||||||
|
export type AddonModImscpImscp = {
|
||||||
|
id: number; // IMSCP id.
|
||||||
|
coursemodule: number; // Course module id.
|
||||||
|
course: number; // Course id.
|
||||||
|
name: string; // Activity name.
|
||||||
|
intro?: string; // The IMSCP intro.
|
||||||
|
introformat?: number; // Intro format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
|
||||||
|
introfiles?: CoreWSExternalFile[];
|
||||||
|
revision?: number; // Revision.
|
||||||
|
keepold?: number; // Number of old IMSCP to keep.
|
||||||
|
structure?: string; // IMSCP structure.
|
||||||
|
timemodified?: string; // Time of last modification.
|
||||||
|
section?: number; // Course section id.
|
||||||
|
visible?: boolean; // If visible.
|
||||||
|
groupmode?: number; // Group mode.
|
||||||
|
groupingid?: number; // Group id.
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Params of mod_imscp_get_imscps_by_courses WS.
|
||||||
|
*/
|
||||||
|
type AddonModImscpGetImscpsByCoursesWSParams = {
|
||||||
|
courseids?: number[]; // Array of course ids.
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data returned by mod_imscp_get_imscps_by_courses WS.
|
||||||
|
*/
|
||||||
|
type AddonModImscpGetImscpsByCoursesWSResponse = {
|
||||||
|
imscps: AddonModImscpImscp[];
|
||||||
|
warnings?: CoreWSExternalWarning[];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export type AddonModImscpTocItem = {
|
||||||
|
href: string;
|
||||||
|
title: string;
|
||||||
|
level: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type AddonModImscpTocItemTree = AddonModImscpTocItem & {
|
||||||
|
subitems: AddonModImscpTocItem[];
|
||||||
|
};
|
|
@ -19,6 +19,7 @@ import { AddonModBookModule } from './book/book.module';
|
||||||
import { AddonModFolderModule } from './folder/folder.module';
|
import { AddonModFolderModule } from './folder/folder.module';
|
||||||
import { AddonModForumModule } from './forum/forum.module';
|
import { AddonModForumModule } from './forum/forum.module';
|
||||||
import { AddonModLabelModule } from './label/label.module';
|
import { AddonModLabelModule } from './label/label.module';
|
||||||
|
import { AddonModImscpModule } from './imscp/imscp.module';
|
||||||
import { AddonModLessonModule } from './lesson/lesson.module';
|
import { AddonModLessonModule } from './lesson/lesson.module';
|
||||||
import { AddonModPageModule } from './page/page.module';
|
import { AddonModPageModule } from './page/page.module';
|
||||||
import { AddonModQuizModule } from './quiz/quiz.module';
|
import { AddonModQuizModule } from './quiz/quiz.module';
|
||||||
|
@ -38,6 +39,7 @@ import { AddonModUrlModule } from './url/url.module';
|
||||||
AddonModLabelModule,
|
AddonModLabelModule,
|
||||||
AddonModResourceModule,
|
AddonModResourceModule,
|
||||||
AddonModFolderModule,
|
AddonModFolderModule,
|
||||||
|
AddonModImscpModule,
|
||||||
],
|
],
|
||||||
providers: [],
|
providers: [],
|
||||||
exports: [],
|
exports: [],
|
||||||
|
|
|
@ -12,6 +12,10 @@
|
||||||
clear: both;
|
clear: both;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.core-bold {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
.img-responsive {
|
.img-responsive {
|
||||||
display: block;
|
display: block;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
|
Loading…
Reference in New Issue