MOBILE-2539 url: Support appearance settings
parent
cba29471d8
commit
f090f8f33a
|
@ -12,14 +12,29 @@
|
|||
|
||||
<core-course-module-description *ngIf="mode != 'iframe'" [description]="description" [component]="component" [componentId]="componentId"></core-course-module-description>
|
||||
|
||||
<ion-item text-wrap>
|
||||
<h2>{{ 'addon.mod_url.pointingtourl' | translate }}</h2>
|
||||
<p>{{ url }}</p>
|
||||
</ion-item>
|
||||
<div padding>
|
||||
<a ion-button block (click)="go()" icon-start>
|
||||
<ion-icon name="link" start></ion-icon>
|
||||
{{ 'addon.mod_url.accessurl' | translate }}
|
||||
</a>
|
||||
<div *ngIf="shouldEmbed" class="addon-mod_url-embedded-url">
|
||||
<img *ngIf="isImage" title="{{name}}" [src]="url">
|
||||
<video *ngIf="isVideo" title="{{name}}" controls>
|
||||
<source [src]="url" [type]="mimetype">
|
||||
</video>
|
||||
<audio *ngIf="isAudio" title="{{name}}" controls>
|
||||
<source [src]="url" [type]="mimetype">
|
||||
</audio>
|
||||
<core-iframe *ngIf="!isImage && !isVideo && !isAudio" [src]="url"></core-iframe>
|
||||
</div>
|
||||
|
||||
<core-iframe *ngIf="shouldIframe" [src]="url"></core-iframe>
|
||||
|
||||
<ion-list *ngIf="!shouldEmbed && !shouldIframe">
|
||||
<ion-item text-wrap>
|
||||
<h2>{{ 'addon.mod_url.pointingtourl' | translate }}</h2>
|
||||
<p>{{ url }}</p>
|
||||
</ion-item>
|
||||
<div padding>
|
||||
<a ion-button block (click)="go()" icon-start>
|
||||
<ion-icon name="link" start></ion-icon>
|
||||
{{ 'addon.mod_url.accessurl' | translate }}
|
||||
</a>
|
||||
</div>
|
||||
</ion-list>
|
||||
</core-loading>
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
ion-app.app-root addon-mod-url-index {
|
||||
|
||||
.addon-mod_url-embedded-url {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
|
@ -13,10 +13,13 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { Component, Injector } from '@angular/core';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreMimetypeUtilsProvider } from '@providers/utils/mimetype';
|
||||
import { CoreCourseProvider } from '@core/course/providers/course';
|
||||
import { CoreCourseModuleMainResourceComponent } from '@core/course/classes/main-resource-component';
|
||||
import { AddonModUrlProvider } from '../../providers/url';
|
||||
import { AddonModUrlHelperProvider } from '../../providers/helper';
|
||||
import { CoreConstants } from '@core/constants';
|
||||
|
||||
/**
|
||||
* Component that displays a url.
|
||||
|
@ -30,9 +33,17 @@ export class AddonModUrlIndexComponent extends CoreCourseModuleMainResourceCompo
|
|||
|
||||
canGetUrl: boolean;
|
||||
url: string;
|
||||
name: string;
|
||||
shouldEmbed = false;
|
||||
shouldIframe = false;
|
||||
isImage = false;
|
||||
isAudio = false;
|
||||
isVideo = false;
|
||||
mimetype: string;
|
||||
|
||||
constructor(injector: Injector, private urlProvider: AddonModUrlProvider, private courseProvider: CoreCourseProvider,
|
||||
private urlHelper: AddonModUrlHelperProvider) {
|
||||
private urlHelper: AddonModUrlHelperProvider, private mimeUtils: CoreMimetypeUtilsProvider,
|
||||
private sitesProvider: CoreSitesProvider) {
|
||||
super(injector);
|
||||
}
|
||||
|
||||
|
@ -65,6 +76,7 @@ export class AddonModUrlIndexComponent extends CoreCourseModuleMainResourceCompo
|
|||
protected fetchContent(refresh?: boolean): Promise<any> {
|
||||
let canGetUrl = this.canGetUrl,
|
||||
mod,
|
||||
url,
|
||||
promise;
|
||||
|
||||
// Fetch the module data.
|
||||
|
@ -79,7 +91,10 @@ export class AddonModUrlIndexComponent extends CoreCourseModuleMainResourceCompo
|
|||
|
||||
// Fallback in case is not prefetched or not available.
|
||||
return this.courseProvider.getModule(this.module.id, this.courseId, undefined, false, false, undefined, 'url');
|
||||
}).then((url) => {
|
||||
}).then((urlData) => {
|
||||
url = urlData;
|
||||
|
||||
this.name = url.name || this.module.name;
|
||||
this.description = url.intro || url.description;
|
||||
this.dataRetrieved.emit(url);
|
||||
|
||||
|
@ -101,9 +116,49 @@ export class AddonModUrlIndexComponent extends CoreCourseModuleMainResourceCompo
|
|||
}).then(() => {
|
||||
// Always use the URL from the module because it already includes the parameters.
|
||||
this.url = mod.contents && mod.contents[0] && mod.contents[0].fileurl ? mod.contents[0].fileurl : undefined;
|
||||
|
||||
if (canGetUrl) {
|
||||
return this.calculateDisplayOptions(url);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the display options to determine how the URL should be rendered.
|
||||
*
|
||||
* @param {any} url Object with the URL data.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
protected calculateDisplayOptions(url: any): Promise<any> {
|
||||
const displayType = this.urlProvider.getFinalDisplayType(url);
|
||||
|
||||
this.shouldEmbed = displayType == CoreConstants.RESOURCELIB_DISPLAY_EMBED;
|
||||
this.shouldIframe = displayType == CoreConstants.RESOURCELIB_DISPLAY_FRAME;
|
||||
|
||||
if (this.shouldEmbed) {
|
||||
const extension = this.mimeUtils.guessExtensionFromUrl(url.externalurl);
|
||||
|
||||
this.mimetype = this.mimeUtils.getMimeType(extension);
|
||||
this.isImage = this.mimeUtils.isExtensionInGroup(extension, ['web_image']);
|
||||
this.isAudio = this.mimeUtils.isExtensionInGroup(extension, ['web_audio']);
|
||||
this.isVideo = this.mimeUtils.isExtensionInGroup(extension, ['web_video']);
|
||||
}
|
||||
|
||||
if (this.shouldIframe || (this.shouldEmbed && !this.isImage && !this.isAudio && !this.isVideo)) {
|
||||
// Will be displayed in an iframe. Check if we need to auto-login.
|
||||
const currentSite = this.sitesProvider.getCurrentSite();
|
||||
|
||||
if (currentSite && currentSite.containsUrl(this.url)) {
|
||||
// Format the URL to add auto-login.
|
||||
return currentSite.getAutoLoginUrl(this.url, false).then((url) => {
|
||||
this.url = url;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a file.
|
||||
*/
|
||||
|
|
|
@ -14,11 +14,13 @@
|
|||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { NavController, NavOptions } from 'ionic-angular';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { AddonModUrlIndexComponent } from '../components/index/index';
|
||||
import { CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '@core/course/providers/module-delegate';
|
||||
import { CoreCourseProvider } from '@core/course/providers/course';
|
||||
import { AddonModUrlProvider } from './url';
|
||||
import { AddonModUrlHelperProvider } from './helper';
|
||||
import { CoreConstants } from '@core/constants';
|
||||
|
||||
/**
|
||||
* Handler to support url modules.
|
||||
|
@ -29,7 +31,7 @@ export class AddonModUrlModuleHandler implements CoreCourseModuleHandler {
|
|||
modName = 'url';
|
||||
|
||||
constructor(private courseProvider: CoreCourseProvider, private urlProvider: AddonModUrlProvider,
|
||||
private urlHelper: AddonModUrlHelperProvider) { }
|
||||
private urlHelper: AddonModUrlHelperProvider, private domUtils: CoreDomUtilsProvider) { }
|
||||
|
||||
/**
|
||||
* Check if the handler is enabled on a site level.
|
||||
|
@ -49,33 +51,60 @@ export class AddonModUrlModuleHandler implements CoreCourseModuleHandler {
|
|||
* @return {CoreCourseModuleHandlerData} Data to render the module.
|
||||
*/
|
||||
getData(module: any, courseId: number, sectionId: number): CoreCourseModuleHandlerData {
|
||||
// tslint:disable: no-this-assignment
|
||||
const handler = this;
|
||||
const handlerData = {
|
||||
icon: this.courseProvider.getModuleIconSrc(this.modName),
|
||||
title: module.name,
|
||||
class: 'addon-mod_url-handler',
|
||||
showDownloadButton: false,
|
||||
action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions): void {
|
||||
navCtrl.push('AddonModUrlIndexPage', {module: module, courseId: courseId}, options);
|
||||
// Check if we need to open the URL directly.
|
||||
let promise;
|
||||
|
||||
if (handler.urlProvider.isGetUrlWSAvailable()) {
|
||||
const modal = handler.domUtils.showModalLoading();
|
||||
|
||||
promise = handler.urlProvider.getUrl(courseId, module.id).catch(() => {
|
||||
// Ignore errors.
|
||||
}).then((url) => {
|
||||
modal.dismiss();
|
||||
|
||||
const displayType = handler.urlProvider.getFinalDisplayType(url);
|
||||
|
||||
return displayType == CoreConstants.RESOURCELIB_DISPLAY_OPEN ||
|
||||
displayType == CoreConstants.RESOURCELIB_DISPLAY_POPUP;
|
||||
});
|
||||
} else {
|
||||
promise = Promise.resolve(false);
|
||||
}
|
||||
|
||||
return promise.then((shouldOpen) => {
|
||||
if (shouldOpen) {
|
||||
handler.openUrl(module, courseId);
|
||||
} else {
|
||||
navCtrl.push('AddonModUrlIndexPage', {module: module, courseId: courseId}, options);
|
||||
}
|
||||
});
|
||||
},
|
||||
buttons: [ {
|
||||
hidden: !(module.contents && module.contents[0] && module.contents[0].fileurl),
|
||||
hidden: true, // Hide it until we calculate if it should be displayed or not.
|
||||
icon: 'link',
|
||||
label: 'core.openinbrowser',
|
||||
action: (event: Event, navCtrl: NavController, module: any, courseId: number): void => {
|
||||
this.hideLinkButton(module, courseId).then((hide) => {
|
||||
if (!hide) {
|
||||
this.urlProvider.logView(module.instance).then(() => {
|
||||
this.courseProvider.checkModuleCompletion(courseId, module.completionstatus);
|
||||
});
|
||||
this.urlHelper.open(module.contents[0].fileurl);
|
||||
}
|
||||
});
|
||||
handler.openUrl(module, courseId);
|
||||
}
|
||||
} ]
|
||||
};
|
||||
|
||||
this.hideLinkButton(module, courseId).then((hideButton) => {
|
||||
handlerData.buttons[0].hidden = hideButton;
|
||||
|
||||
if (module.contents && module.contents[0]) {
|
||||
// Calculate the icon to use.
|
||||
handlerData.icon = this.urlProvider.guessIcon(module.contents[0].fileurl) ||
|
||||
this.courseProvider.getModuleIconSrc(this.modName);
|
||||
}
|
||||
});
|
||||
|
||||
return handlerData;
|
||||
|
@ -91,7 +120,24 @@ export class AddonModUrlModuleHandler implements CoreCourseModuleHandler {
|
|||
protected hideLinkButton(module: any, courseId: number): Promise<boolean> {
|
||||
return this.courseProvider.loadModuleContents(module, courseId, undefined, false, false, undefined, this.modName)
|
||||
.then(() => {
|
||||
return !(module.contents && module.contents[0] && module.contents[0].fileurl);
|
||||
|
||||
if (!module.contents || !module.contents[0] || !module.contents[0].fileurl) {
|
||||
// No module contents, hide the button.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!this.urlProvider.isGetUrlWSAvailable()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the URL data.
|
||||
return this.urlProvider.getUrl(courseId, module.id).then((url) => {
|
||||
const displayType = this.urlProvider.getFinalDisplayType(url);
|
||||
|
||||
// Don't display the button if the URL should be embedded.
|
||||
return displayType == CoreConstants.RESOURCELIB_DISPLAY_EMBED ||
|
||||
displayType == CoreConstants.RESOURCELIB_DISPLAY_FRAME;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -106,4 +152,17 @@ export class AddonModUrlModuleHandler implements CoreCourseModuleHandler {
|
|||
getMainComponent(course: any, module: any): any {
|
||||
return AddonModUrlIndexComponent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the URL.
|
||||
*
|
||||
* @param {any} module The module object.
|
||||
* @param {number} courseId The course ID.
|
||||
*/
|
||||
protected openUrl(module: any, courseId: number): void {
|
||||
this.urlProvider.logView(module.instance).then(() => {
|
||||
this.courseProvider.checkModuleCompletion(courseId, module.completionstatus);
|
||||
});
|
||||
this.urlHelper.open(module.contents[0].fileurl);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,8 +15,10 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { CoreLoggerProvider } from '@providers/logger';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreMimetypeUtilsProvider } from '@providers/utils/mimetype';
|
||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||
import { CoreCourseProvider } from '@core/course/providers/course';
|
||||
import { CoreConstants } from '@core/constants';
|
||||
|
||||
/**
|
||||
* Service that provides some features for urls.
|
||||
|
@ -29,10 +31,62 @@ export class AddonModUrlProvider {
|
|||
protected logger;
|
||||
|
||||
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private courseProvider: CoreCourseProvider,
|
||||
private utils: CoreUtilsProvider) {
|
||||
private utils: CoreUtilsProvider, private mimeUtils: CoreMimetypeUtilsProvider) {
|
||||
this.logger = logger.getInstance('AddonModUrlProvider');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the final display type for a certain URL. Based on Moodle's url_get_final_display_type.
|
||||
*
|
||||
* @param {any} url URL data.
|
||||
* @return {number} Final display type.
|
||||
*/
|
||||
getFinalDisplayType(url: any): number {
|
||||
if (!url) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const extension = this.mimeUtils.guessExtensionFromUrl(url.externalurl);
|
||||
|
||||
// PDFs can be embedded in web, but not in the Mobile app.
|
||||
if (url.display == CoreConstants.RESOURCELIB_DISPLAY_EMBED && extension == 'pdf') {
|
||||
return CoreConstants.RESOURCELIB_DISPLAY_DOWNLOAD;
|
||||
}
|
||||
|
||||
if (url.display != CoreConstants.RESOURCELIB_DISPLAY_AUTO) {
|
||||
return url.display;
|
||||
}
|
||||
|
||||
// Detect links to local moodle pages.
|
||||
const currentSite = this.sitesProvider.getCurrentSite();
|
||||
if (currentSite && currentSite.containsUrl(url.externalurl)) {
|
||||
if (url.externalurl.indexOf('file.php') == -1 && url.externalurl.indexOf('.php') != -1) {
|
||||
// Most probably our moodle page with navigation.
|
||||
return CoreConstants.RESOURCELIB_DISPLAY_OPEN;
|
||||
}
|
||||
}
|
||||
|
||||
const download = ['application/zip', 'application/x-tar', 'application/g-zip', 'application/pdf', 'text/html'];
|
||||
let mimetype = this.mimeUtils.getMimeType(extension);
|
||||
|
||||
if (url.externalurl.indexOf('.php') != -1 || url.externalurl.substr(-1) === '/' ||
|
||||
(url.externalurl.indexOf('//') != -1 && url.externalurl.match(/\//g).length == 2)) {
|
||||
// Seems to be a web, use HTML mimetype.
|
||||
mimetype = 'text/html';
|
||||
}
|
||||
|
||||
if (download.indexOf(mimetype) != -1) {
|
||||
return CoreConstants.RESOURCELIB_DISPLAY_DOWNLOAD;
|
||||
}
|
||||
|
||||
if (this.mimeUtils.canBeEmbedded(extension)) {
|
||||
return CoreConstants.RESOURCELIB_DISPLAY_EMBED;
|
||||
}
|
||||
|
||||
// Let the browser deal with it somehow.
|
||||
return CoreConstants.RESOURCELIB_DISPLAY_OPEN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cache key for url data WS calls.
|
||||
*
|
||||
|
@ -88,6 +142,33 @@ export class AddonModUrlProvider {
|
|||
return this.getUrlDataByKey(courseId, 'coursemodule', cmId, siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Guess the icon for a certain URL. Based on Moodle's url_guess_icon.
|
||||
*
|
||||
* @param {string} url URL to check.
|
||||
* @return {string} Icon, empty if it should use the default icon.
|
||||
*/
|
||||
guessIcon(url: string): string {
|
||||
url = url || '';
|
||||
|
||||
const matches = url.match(/\//g),
|
||||
extension = this.mimeUtils.getFileExtension(url);
|
||||
|
||||
if (!matches || matches.length < 3 || url.substr(-1) === '/' || extension == 'php') {
|
||||
// Use default icon.
|
||||
return '';
|
||||
}
|
||||
|
||||
const icon = this.mimeUtils.getFileIcon(url);
|
||||
|
||||
// We do not want to return those icon types, the module icon is more appropriate.
|
||||
if (icon === this.mimeUtils.getFileIconForType('unknown') || icon === this.mimeUtils.getFileIconForType('html')) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate the prefetched content.
|
||||
*
|
||||
|
|
|
@ -47,4 +47,13 @@ export class CoreConstants {
|
|||
static NOT_DOWNLOADED = 'notdownloaded';
|
||||
static OUTDATED = 'outdated';
|
||||
static NOT_DOWNLOADABLE = 'notdownloadable';
|
||||
|
||||
// Constants from Moodle's resourcelib.
|
||||
static RESOURCELIB_DISPLAY_AUTO = 0; // Try the best way.
|
||||
static RESOURCELIB_DISPLAY_EMBED = 1; // Display using object tag.
|
||||
static RESOURCELIB_DISPLAY_FRAME = 2; // Display inside frame.
|
||||
static RESOURCELIB_DISPLAY_NEW = 3; // Display normal link in new window.
|
||||
static RESOURCELIB_DISPLAY_DOWNLOAD = 4; // Force download of file instead of display.
|
||||
static RESOURCELIB_DISPLAY_OPEN = 5; // Open directly.
|
||||
static RESOURCELIB_DISPLAY_POPUP = 6; // Open in "emulated" pop-up without navigation.
|
||||
}
|
||||
|
|
|
@ -186,7 +186,7 @@ export class CoreMimetypeUtilsProvider {
|
|||
}
|
||||
}
|
||||
|
||||
return 'assets/img/files/' + icon + '-64.png';
|
||||
return this.getFileIconForType(icon);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -198,6 +198,16 @@ export class CoreMimetypeUtilsProvider {
|
|||
return 'assets/img/files/folder-64.png';
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a type (audio, video, html, ...), return its file icon path.
|
||||
*
|
||||
* @param {string} type The type to get the icon.
|
||||
* @return {string} The icon path.
|
||||
*/
|
||||
getFileIconForType(type: string): string {
|
||||
return 'assets/img/files/' + type + '-64.png';
|
||||
}
|
||||
|
||||
/**
|
||||
* Guess the extension of a file from its URL.
|
||||
* This is very weak and unreliable.
|
||||
|
|
Loading…
Reference in New Issue