MOBILE-4109 book: Disable unactive pages
parent
56c4877b2e
commit
5370217058
|
@ -31,10 +31,10 @@
|
|||
</ion-card>
|
||||
|
||||
<core-swipe-slides [manager]="manager" [options]="slidesOpts">
|
||||
<ng-template let-chapter="item">
|
||||
<ng-template let-chapter="item" let-active="active">
|
||||
<div class="ion-padding">
|
||||
<core-format-text [component]="component" [componentId]="cmId" [text]="chapter.content" contextLevel="module"
|
||||
[contextInstanceId]="cmId" [courseId]="courseId"></core-format-text>
|
||||
[contextInstanceId]="cmId" [courseId]="courseId" [disabled]="!active"></core-format-text>
|
||||
<div class="ion-margin-top" *ngIf="chapter.tags?.length > 0">
|
||||
<strong>{{ 'core.tag.tags' | translate }}: </strong>
|
||||
<core-tag-list [tags]="chapter.tags"></core-tag-list>
|
||||
|
|
|
@ -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.
|
||||
|
||||
/**
|
||||
* Wrapper class to control the interactivity of an element.
|
||||
*/
|
||||
export abstract class ElementController {
|
||||
|
||||
protected enabled: boolean;
|
||||
|
||||
constructor(enabled: boolean) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable element.
|
||||
*/
|
||||
enable(): void {
|
||||
if (this.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.enabled = true;
|
||||
|
||||
this.onEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable element.
|
||||
*/
|
||||
disable(): void {
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.enabled = false;
|
||||
|
||||
this.onDisabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update underlying element to enable interactivity.
|
||||
*/
|
||||
abstract onEnabled(): void;
|
||||
|
||||
/**
|
||||
* Update underlying element to disable interactivity.
|
||||
*/
|
||||
abstract onDisabled(): void;
|
||||
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
// (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 { ElementController } from './ElementController';
|
||||
|
||||
export type FrameElement = HTMLIFrameElement | HTMLFrameElement | HTMLObjectElement | HTMLEmbedElement;
|
||||
|
||||
/**
|
||||
* Wrapper class to control the interactivity of a frame element.
|
||||
*/
|
||||
export class FrameElementController extends ElementController {
|
||||
|
||||
private frame: FrameElement;
|
||||
private placeholder: Node;
|
||||
|
||||
constructor(element: FrameElement, enabled: boolean) {
|
||||
super(enabled);
|
||||
|
||||
this.frame = element;
|
||||
this.placeholder = document.createComment('disabled frame placeholder');
|
||||
|
||||
enabled || this.onDisabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
onEnabled(): void {
|
||||
this.placeholder.parentElement?.replaceChild(this.frame, this.placeholder);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
onDisabled(): void {
|
||||
this.frame.parentElement?.replaceChild(this.placeholder, this.frame);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
// (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 { CoreUtils } from '@services/utils/utils';
|
||||
import { ElementController } from './ElementController';
|
||||
|
||||
/**
|
||||
* Wrapper class to control the interactivity of a media element.
|
||||
*/
|
||||
export class MediaElementController extends ElementController {
|
||||
|
||||
private media: HTMLMediaElement;
|
||||
private autoplay: boolean;
|
||||
private playing?: boolean;
|
||||
private playListener?: () => void;
|
||||
private pauseListener?: () => void;
|
||||
|
||||
constructor(media: HTMLMediaElement, enabled: boolean) {
|
||||
super(enabled);
|
||||
|
||||
this.media = media;
|
||||
this.autoplay = media.autoplay;
|
||||
|
||||
media.autoplay = false;
|
||||
|
||||
enabled && this.onEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
onEnabled(): void {
|
||||
const ready = this.playing ?? this.autoplay
|
||||
? this.media.play()
|
||||
: Promise.resolve();
|
||||
|
||||
ready
|
||||
.then(() => this.addPlaybackEventListeners())
|
||||
.catch(error => CoreUtils.logUnhandledError('Error enabling media element', error));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async onDisabled(): Promise<void> {
|
||||
this.removePlaybackEventListeners();
|
||||
|
||||
this.media.pause();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start listening playback events.
|
||||
*/
|
||||
private addPlaybackEventListeners(): void {
|
||||
this.media.addEventListener('play', this.playListener = () => this.playing = true);
|
||||
this.media.addEventListener('pause', this.pauseListener = () => this.playing = false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop listening playback events.
|
||||
*/
|
||||
private removePlaybackEventListeners(): void {
|
||||
this.playListener && this.media.removeEventListener('play', this.playListener);
|
||||
this.pauseListener && this.media.removeEventListener('pause', this.pauseListener);
|
||||
|
||||
delete this.playListener;
|
||||
delete this.pauseListener;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
<ion-slides *ngIf="loaded" (ionSlideWillChange)="slideWillChange()" (ionSlideDidChange)="slideDidChange()" [options]="options">
|
||||
<ion-slide *ngFor="let item of items">
|
||||
<ng-container *ngIf="template" [ngTemplateOutlet]="template" [ngTemplateOutletContext]="{item: item}"></ng-container>
|
||||
<ion-slide *ngFor="let item of items; index as index">
|
||||
<ng-container *ngIf="template" [ngTemplateOutlet]="template" [ngTemplateOutletContext]="{item: item, active: isActive(index)}">
|
||||
</ng-container>
|
||||
</ion-slide>
|
||||
</ion-slides>
|
||||
|
|
|
@ -45,6 +45,7 @@ export class CoreSwipeSlidesComponent<Item = unknown> implements OnChanges, OnDe
|
|||
protected unsubscribe?: () => void;
|
||||
protected resizeListener: CoreEventObserver;
|
||||
protected updateSlidesPromise?: Promise<void>;
|
||||
protected activeSlideIndexes: number[] = [];
|
||||
|
||||
constructor(
|
||||
elementRef: ElementRef<HTMLElement>,
|
||||
|
@ -74,6 +75,16 @@ export class CoreSwipeSlidesComponent<Item = unknown> implements OnChanges, OnDe
|
|||
return !!this.manager?.getSource().isLoaded();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the slide with the given index is active.
|
||||
*
|
||||
* @param index Slide index.
|
||||
* @return Whether the slide is active.
|
||||
*/
|
||||
isActive(index: number): boolean {
|
||||
return this.activeSlideIndexes.includes(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize some properties based on the manager.
|
||||
*/
|
||||
|
@ -101,13 +112,15 @@ export class CoreSwipeSlidesComponent<Item = unknown> implements OnChanges, OnDe
|
|||
}
|
||||
|
||||
// Validate that the initial index is inside the valid range.
|
||||
const initialIndex = CoreMath.clamp(this.options.initialSlide as number, 0, items.length - 1);
|
||||
const initialIndex = CoreMath.clamp(this.options.initialSlide, 0, items.length - 1);
|
||||
|
||||
const initialItemData = {
|
||||
index: initialIndex,
|
||||
item: items[initialIndex],
|
||||
};
|
||||
|
||||
this.activeSlideIndexes = [initialIndex];
|
||||
|
||||
manager.setSelectedItem(items[initialIndex]);
|
||||
this.onWillChange.emit(initialItemData);
|
||||
this.onDidChange.emit(initialItemData);
|
||||
|
@ -205,6 +218,7 @@ export class CoreSwipeSlidesComponent<Item = unknown> implements OnChanges, OnDe
|
|||
return;
|
||||
}
|
||||
|
||||
this.activeSlideIndexes.push(currentItemData.index);
|
||||
this.manager?.setSelectedItem(currentItemData.item);
|
||||
|
||||
this.onWillChange.emit(currentItemData);
|
||||
|
@ -219,9 +233,13 @@ export class CoreSwipeSlidesComponent<Item = unknown> implements OnChanges, OnDe
|
|||
async slideDidChange(): Promise<void> {
|
||||
const currentItemData = await this.getCurrentSlideItemData();
|
||||
if (!currentItemData) {
|
||||
this.activeSlideIndexes = [];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.activeSlideIndexes = [currentItemData.index];
|
||||
|
||||
this.onDidChange.emit(currentItemData);
|
||||
|
||||
await this.applyScrollOnChange();
|
||||
|
@ -304,6 +322,7 @@ export class CoreSwipeSlidesComponent<Item = unknown> implements OnChanges, OnDe
|
|||
* @todo Change unknown with the right type once Swiper library is used.
|
||||
*/
|
||||
export type CoreSwipeSlidesOptions = Record<string, unknown> & {
|
||||
initialSlide?: number;
|
||||
scrollOnChange?: 'top' | 'none'; // Scroll behaviour on change slide. By default, none.
|
||||
};
|
||||
|
||||
|
|
|
@ -51,6 +51,9 @@ import { CoreDom } from '@singletons/dom';
|
|||
import { CoreEvents } from '@singletons/events';
|
||||
import { CoreRefreshContext, CORE_REFRESH_CONTEXT } from '@/core/utils/refresh-context';
|
||||
import { CorePlatform } from '@services/platform';
|
||||
import { ElementController } from '@classes/element-controllers/ElementController';
|
||||
import { MediaElementController } from '@classes/element-controllers/MediaElementController';
|
||||
import { FrameElementController } from '@classes/element-controllers/FrameElementController';
|
||||
|
||||
/**
|
||||
* Directive to format text rendered. It renders the HTML and treats all links and media, using CoreLinkDirective
|
||||
|
@ -84,6 +87,7 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncCompo
|
|||
@Input() captureLinks?: boolean; // Whether links should tried to be opened inside the app. Defaults to true.
|
||||
@Input() openLinksInApp?: boolean; // Whether links should be opened in InAppBrowser.
|
||||
@Input() hideIfEmpty = false; // If true, the tag will contain nothing if text is empty.
|
||||
@Input() disabled?: boolean; // If disabled, autoplay elements will be disabled.
|
||||
|
||||
@Input() fullOnClick?: boolean | string; // @deprecated on 4.0 Won't do anything.
|
||||
@Input() fullTitle?: string; // @deprecated on 4.0 Won't do anything.
|
||||
|
@ -96,6 +100,7 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncCompo
|
|||
@Output() onClick: EventEmitter<void> = new EventEmitter(); // Called when clicked.
|
||||
|
||||
protected element: HTMLElement;
|
||||
protected elementControllers: ElementController[] = [];
|
||||
protected emptyText = '';
|
||||
protected domPromises: CoreCancellablePromise<void>[] = [];
|
||||
protected domElementPromise?: CoreCancellablePromise<void>;
|
||||
|
@ -127,6 +132,14 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncCompo
|
|||
ngOnChanges(changes: { [name: string]: SimpleChange }): void {
|
||||
if (changes.text || changes.filter || changes.contextLevel || changes.contextInstanceId) {
|
||||
this.formatAndRenderContents();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ('disabled' in changes) {
|
||||
const disabled = changes['disabled'].currentValue;
|
||||
|
||||
this.elementControllers.forEach(controller => disabled ? controller.disable() : controller.enable());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -352,6 +365,8 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncCompo
|
|||
// Move the children to the current element to be able to calculate the height.
|
||||
CoreDomUtils.moveChildren(result.div, this.element);
|
||||
|
||||
this.elementControllers = result.elementControllers;
|
||||
|
||||
await CoreUtils.nextTick();
|
||||
|
||||
// Use collapsible-item directive instead.
|
||||
|
@ -432,13 +447,14 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncCompo
|
|||
|
||||
div.innerHTML = formatted;
|
||||
|
||||
this.treatHTMLElements(div, site);
|
||||
const elementControllers = this.treatHTMLElements(div, site);
|
||||
|
||||
return {
|
||||
div,
|
||||
filters,
|
||||
options,
|
||||
siteId,
|
||||
elementControllers,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -449,7 +465,7 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncCompo
|
|||
* @param site Site instance.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected treatHTMLElements(div: HTMLElement, site?: CoreSite): void {
|
||||
protected treatHTMLElements(div: HTMLElement, site?: CoreSite): ElementController[] {
|
||||
const images = Array.from(div.querySelectorAll('img'));
|
||||
const anchors = Array.from(div.querySelectorAll('a'));
|
||||
const audios = Array.from(div.querySelectorAll('audio'));
|
||||
|
@ -498,16 +514,22 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncCompo
|
|||
});
|
||||
}
|
||||
|
||||
audios.forEach((audio) => {
|
||||
const audioControllers = audios.map(audio => {
|
||||
this.treatMedia(audio);
|
||||
|
||||
return new MediaElementController(audio, !this.disabled);
|
||||
});
|
||||
|
||||
videos.forEach((video) => {
|
||||
const videoControllers = videos.map(video => {
|
||||
this.treatMedia(video, true);
|
||||
|
||||
return new MediaElementController(video, !this.disabled);
|
||||
});
|
||||
|
||||
iframes.forEach((iframe) => {
|
||||
const iframeControllers = iframes.map(iframe => {
|
||||
promises.push(this.treatIframe(iframe, site));
|
||||
|
||||
return new FrameElementController(iframe, !this.disabled);
|
||||
});
|
||||
|
||||
svgImages.forEach((image) => {
|
||||
|
@ -539,8 +561,10 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncCompo
|
|||
});
|
||||
|
||||
// Handle all kind of frames.
|
||||
frames.forEach((frame: HTMLFrameElement | HTMLObjectElement | HTMLEmbedElement) => {
|
||||
const frameControllers = frames.map((frame: HTMLFrameElement | HTMLObjectElement | HTMLEmbedElement) => {
|
||||
CoreIframeUtils.treatFrame(frame, false);
|
||||
|
||||
return new FrameElementController(frame, !this.disabled);
|
||||
});
|
||||
|
||||
CoreDomUtils.handleBootstrapTooltips(div);
|
||||
|
@ -562,6 +586,13 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncCompo
|
|||
|
||||
// Run asynchronous operations in the background to avoid blocking rendering.
|
||||
Promise.all(promises).catch(error => CoreUtils.logUnhandledError('Error treating format-text elements', error));
|
||||
|
||||
return [
|
||||
...videoControllers,
|
||||
...audioControllers,
|
||||
...iframeControllers,
|
||||
...frameControllers,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -903,6 +934,7 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncCompo
|
|||
type FormatContentsResult = {
|
||||
div: HTMLElement;
|
||||
filters: CoreFilterFilter[];
|
||||
elementControllers: ElementController[];
|
||||
options: CoreFilterFormatTextOptions;
|
||||
siteId?: string;
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue