MOBILE-3608 blocks: Migrate basic block structure
parent
3356659a24
commit
7d1d318afc
|
@ -2,6 +2,10 @@
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:host-context([dir=rtl]).icon-flip-rtl {
|
||||||
|
transform: scaleX(-1);
|
||||||
|
}
|
||||||
|
|
||||||
:host-context(ion-item.md) ion-icon {
|
:host-context(ion-item.md) ion-icon {
|
||||||
&[slot] {
|
&[slot] {
|
||||||
font-size: 1.6em;
|
font-size: 1.6em;
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
// (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 { CoreBlockDefaultHandler } from './services/handlers/default-block';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
providers: [
|
||||||
|
CoreBlockDefaultHandler,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class CoreBlockModule {
|
||||||
|
}
|
|
@ -0,0 +1,143 @@
|
||||||
|
// (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 { OnInit, Input, Component, Optional, Inject } from '@angular/core';
|
||||||
|
import { CoreLogger } from '@singletons/logger';
|
||||||
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
|
import { CoreTextUtils } from '@services/utils/text';
|
||||||
|
import { CoreCourseBlock } from '../../course/services/course';
|
||||||
|
import { IonRefresher } from '@ionic/angular';
|
||||||
|
import { Params } from '@angular/router';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Template class to easily create components for blocks.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
template: '',
|
||||||
|
})
|
||||||
|
export abstract class CoreBlockBaseComponent implements OnInit {
|
||||||
|
|
||||||
|
@Input() title!: string; // The block title.
|
||||||
|
@Input() block!: CoreCourseBlock; // The block to render.
|
||||||
|
@Input() contextLevel!: string; // The context where the block will be used.
|
||||||
|
@Input() instanceId!: number; // The instance ID associated with the context level.
|
||||||
|
@Input() link?: string; // Link to go when clicked.
|
||||||
|
@Input() linkParams?: Params; // Link params to go when clicked.
|
||||||
|
|
||||||
|
loaded = false; // If the component has been loaded.
|
||||||
|
protected fetchContentDefaultError = ''; // Default error to show when loading contents.
|
||||||
|
|
||||||
|
protected logger: CoreLogger;
|
||||||
|
|
||||||
|
constructor(@Optional() @Inject('') loggerName: string = 'AddonBlockComponent') {
|
||||||
|
this.logger = CoreLogger.getInstance(loggerName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component being initialized.
|
||||||
|
*/
|
||||||
|
async ngOnInit(): Promise<void> {
|
||||||
|
if (this.block.configs && this.block.configs.length > 0) {
|
||||||
|
this.block.configs.map((config) => {
|
||||||
|
config.value = CoreTextUtils.instance.parseJSON(config.value);
|
||||||
|
|
||||||
|
return config;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.block.configsRecord = CoreUtils.instance.arrayToObject(this.block.configs, 'name');
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.loadContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh the data.
|
||||||
|
*
|
||||||
|
* @param refresher Refresher.
|
||||||
|
* @param done Function to call when done.
|
||||||
|
* @param showErrors If show errors to the user of hide them.
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
async doRefresh(refresher?: CustomEvent<IonRefresher>, done?: () => void, showErrors: boolean = false): Promise<void> {
|
||||||
|
if (this.loaded) {
|
||||||
|
return this.refreshContent(showErrors).finally(() => {
|
||||||
|
refresher?.detail.complete();
|
||||||
|
done && done();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform the refresh content function.
|
||||||
|
*
|
||||||
|
* @param showErrors Wether to show errors to the user or hide them.
|
||||||
|
* @return Resolved when done.
|
||||||
|
*/
|
||||||
|
protected async refreshContent(showErrors: boolean = false): Promise<void> {
|
||||||
|
// Wrap the call in a try/catch so the workflow isn't interrupted if an error occurs.
|
||||||
|
try {
|
||||||
|
await this.invalidateContent();
|
||||||
|
} catch (ex) {
|
||||||
|
// An error ocurred in the function, log the error and just resolve the promise so the workflow continues.
|
||||||
|
this.logger.error(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.loadContent(true, showErrors);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform the invalidate content function.
|
||||||
|
*
|
||||||
|
* @return Resolved when done.
|
||||||
|
*/
|
||||||
|
protected async invalidateContent(): Promise<void> {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the component contents and shows the corresponding error.
|
||||||
|
*
|
||||||
|
* @param refresh Whether we're refreshing data.
|
||||||
|
* @param showErrors Wether to show errors to the user or hide them.
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
protected async loadContent(refresh?: boolean, showErrors: boolean = false): Promise<void> {
|
||||||
|
// Wrap the call in a try/catch so the workflow isn't interrupted if an error occurs.
|
||||||
|
try {
|
||||||
|
await this.fetchContent(refresh);
|
||||||
|
} catch (error) {
|
||||||
|
// An error ocurred in the function, log the error and just resolve the promise so the workflow continues.
|
||||||
|
this.logger.error(error);
|
||||||
|
|
||||||
|
// Error getting data, fail.
|
||||||
|
CoreDomUtils.instance.showErrorModalDefault(error, this.fetchContentDefaultError, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download the component contents.
|
||||||
|
*
|
||||||
|
* @param refresh Whether we're refreshing data.
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
protected async fetchContent(refresh: boolean = false): Promise<void> {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
// (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 { CoreCourseBlock } from '@features/course/services/course';
|
||||||
|
import { CoreBlockPreRenderedComponent } from '../components/pre-rendered-block/pre-rendered-block';
|
||||||
|
import { CoreBlockHandler, CoreBlockHandlerData } from '../services/block-delegate';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base handler for blocks.
|
||||||
|
*
|
||||||
|
* This class is needed because parent classes cannot have @Injectable in Angular v6, so the default handler cannot be a
|
||||||
|
* parent class.
|
||||||
|
*/
|
||||||
|
export class CoreBlockBaseHandler implements CoreBlockHandler {
|
||||||
|
|
||||||
|
name = 'CoreBlockBase';
|
||||||
|
blockName = 'base';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the handler is enabled on a site level.
|
||||||
|
*
|
||||||
|
* @return True or promise resolved with true if enabled.
|
||||||
|
*/
|
||||||
|
async isEnabled(): Promise<boolean> {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the data needed to render the block.
|
||||||
|
*
|
||||||
|
* @param block The block to render.
|
||||||
|
* @param contextLevel The context where the block will be used.
|
||||||
|
* @param instanceId The instance ID associated with the context level.
|
||||||
|
* @return Data or promise resolved with the data.
|
||||||
|
*/
|
||||||
|
getDisplayData(
|
||||||
|
block: CoreCourseBlock, // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||||
|
contextLevel: string, // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||||
|
instanceId: number, // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||||
|
): CoreBlockHandlerData | Promise<CoreBlockHandlerData> {
|
||||||
|
|
||||||
|
// To be overridden.
|
||||||
|
return {
|
||||||
|
title: '',
|
||||||
|
class: '',
|
||||||
|
component: CoreBlockPreRenderedComponent,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
:host {
|
||||||
|
// @todo
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
core-loading.core-loading-center {
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
.core-loading-container {
|
||||||
|
margin-top: 10px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
core-empty-box .core-empty-box {
|
||||||
|
position: relative;
|
||||||
|
z-index: initial;
|
||||||
|
//@include position(initial, initial, null, initial);
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
ion-item-divider {
|
||||||
|
//@include padding-horizontal(null, 0px);
|
||||||
|
min-height: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ion-item-divider .core-button-spinner {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,163 @@
|
||||||
|
// (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, OnInit, ViewChild, OnDestroy, DoCheck, KeyValueDiffers, KeyValueDiffer, Type } from '@angular/core';
|
||||||
|
import { CoreBlockDelegate } from '../../services/block-delegate';
|
||||||
|
import { CoreDynamicComponent } from '@components/dynamic-component/dynamic-component';
|
||||||
|
import { Subscription } from 'rxjs';
|
||||||
|
import { CoreCourseBlock } from '@/core/features/course/services/course';
|
||||||
|
import { IonRefresher } from '@ionic/angular';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component to render a block.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'core-block',
|
||||||
|
templateUrl: 'core-block.html',
|
||||||
|
styleUrls: ['block.scss'],
|
||||||
|
})
|
||||||
|
export class CoreBlockComponent implements OnInit, OnDestroy, DoCheck {
|
||||||
|
|
||||||
|
@ViewChild(CoreDynamicComponent) dynamicComponent?: CoreDynamicComponent;
|
||||||
|
|
||||||
|
@Input() block!: CoreCourseBlock; // The block to render.
|
||||||
|
@Input() contextLevel!: string; // The context where the block will be used.
|
||||||
|
@Input() instanceId!: number; // The instance ID associated with the context level.
|
||||||
|
@Input() extraData: any; // Any extra data to be passed to the block.
|
||||||
|
|
||||||
|
componentClass?: Type<unknown>; // The class of the component to render.
|
||||||
|
data: any = {}; // Data to pass to the component.
|
||||||
|
class?: string; // CSS class to apply to the block.
|
||||||
|
loaded = false;
|
||||||
|
|
||||||
|
blockSubscription?: Subscription;
|
||||||
|
|
||||||
|
protected differ: KeyValueDiffer<unknown, unknown>; // To detect changes in the data input.
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
differs: KeyValueDiffers,
|
||||||
|
) {
|
||||||
|
this.differ = differs.find([]).create();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component being initialized.
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
if (!this.block) {
|
||||||
|
this.loaded = true;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.block.visible) {
|
||||||
|
// Get the data to render the block.
|
||||||
|
this.initBlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect and act upon changes that Angular can’t or won’t detect on its own (objects and arrays).
|
||||||
|
*/
|
||||||
|
ngDoCheck(): void {
|
||||||
|
if (this.data) {
|
||||||
|
// Check if there's any change in the extraData object.
|
||||||
|
const changes = this.differ.diff(this.extraData);
|
||||||
|
if (changes) {
|
||||||
|
this.data = Object.assign(this.data, this.extraData || {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get block display data and initialises the block once this is available. If the block is not
|
||||||
|
* supported at the moment, try again if the available blocks are updated (because it comes
|
||||||
|
* from a site plugin).
|
||||||
|
*/
|
||||||
|
async initBlock(): Promise<void> {
|
||||||
|
try {
|
||||||
|
const data = await CoreBlockDelegate.instance.getBlockDisplayData(this.block, this.contextLevel, this.instanceId);
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
// Block not supported, don't render it. But, site plugins might not have finished loading.
|
||||||
|
// Subscribe to the observable in block delegate that will tell us if blocks are updated.
|
||||||
|
// We can retry init later if that happens.
|
||||||
|
this.blockSubscription = CoreBlockDelegate.instance.blocksUpdateObservable.subscribe(
|
||||||
|
(): void => {
|
||||||
|
this.blockSubscription?.unsubscribe();
|
||||||
|
delete this.blockSubscription;
|
||||||
|
this.initBlock();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.class = data.class;
|
||||||
|
this.componentClass = data.component;
|
||||||
|
|
||||||
|
// Set up the data needed by the block component.
|
||||||
|
this.data = Object.assign({
|
||||||
|
title: data.title,
|
||||||
|
block: this.block,
|
||||||
|
contextLevel: this.contextLevel,
|
||||||
|
instanceId: this.instanceId,
|
||||||
|
link: data.link || null,
|
||||||
|
linkParams: data.linkParams || null,
|
||||||
|
}, this.extraData || {}, data.componentData || {});
|
||||||
|
} catch {
|
||||||
|
// Ignore errors.
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On destroy of the component, clear up any subscriptions.
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.blockSubscription?.unsubscribe();
|
||||||
|
delete this.blockSubscription;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh the data.
|
||||||
|
*
|
||||||
|
* @param refresher Refresher. Please pass this only if the refresher should finish when this function finishes.
|
||||||
|
* @param done Function to call when done.
|
||||||
|
* @param showErrors If show errors to the user of hide them.
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
async doRefresh(
|
||||||
|
refresher?: CustomEvent<IonRefresher>,
|
||||||
|
done?: () => void,
|
||||||
|
showErrors: boolean = false,
|
||||||
|
): Promise<void> {
|
||||||
|
if (this.dynamicComponent) {
|
||||||
|
await this.dynamicComponent.callComponentFunction('doRefresh', [refresher, done, showErrors]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidate some data.
|
||||||
|
*
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
async invalidate(): Promise<void> {
|
||||||
|
if (this.dynamicComponent) {
|
||||||
|
await this.dynamicComponent.callComponentFunction('invalidateContent');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
<!-- Only render the block if it's supported. -->
|
||||||
|
<div *ngIf="loaded && componentClass && block.visible" class="{{class}}">
|
||||||
|
<core-dynamic-component [component]="componentClass" [data]="data"></core-dynamic-component>
|
||||||
|
</div>
|
|
@ -0,0 +1,52 @@
|
||||||
|
// (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 { CommonModule } from '@angular/common';
|
||||||
|
import { IonicModule } from '@ionic/angular';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||||
|
import { CoreBlockComponent } from './block/block';
|
||||||
|
import { CoreBlockOnlyTitleComponent } from './only-title-block/only-title-block';
|
||||||
|
import { CoreBlockPreRenderedComponent } from './pre-rendered-block/pre-rendered-block';
|
||||||
|
import { CoreBlockCourseBlocksComponent } from './course-blocks/course-blocks';
|
||||||
|
import { CoreComponentsModule } from '@components/components.module';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
CoreBlockComponent,
|
||||||
|
CoreBlockOnlyTitleComponent,
|
||||||
|
CoreBlockPreRenderedComponent,
|
||||||
|
CoreBlockCourseBlocksComponent,
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
IonicModule,
|
||||||
|
CoreDirectivesModule,
|
||||||
|
TranslateModule.forChild(),
|
||||||
|
CoreComponentsModule,
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
CoreBlockComponent,
|
||||||
|
CoreBlockOnlyTitleComponent,
|
||||||
|
CoreBlockPreRenderedComponent,
|
||||||
|
CoreBlockCourseBlocksComponent,
|
||||||
|
],
|
||||||
|
entryComponents: [
|
||||||
|
CoreBlockOnlyTitleComponent,
|
||||||
|
CoreBlockPreRenderedComponent,
|
||||||
|
CoreBlockCourseBlocksComponent,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class CoreBlockComponentsModule {}
|
|
@ -0,0 +1,14 @@
|
||||||
|
<div class="core-course-blocks-content">
|
||||||
|
<ng-content></ng-content>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="blocks && blocks.length > 0 && !hideBlocks" [class.core-hide-blocks]="hideBottomBlocks" class="core-course-blocks-side">
|
||||||
|
<core-loading [hideUntil]="dataLoaded" class="core-loading-center">
|
||||||
|
<ion-list>
|
||||||
|
<!-- Course expand="block"s. -->
|
||||||
|
<ng-container *ngFor="let block of blocks">
|
||||||
|
<core-block *ngIf="block.visible" [block]="block" contextLevel="course" [instanceId]="courseId" [extraData]="{'downloadEnabled': downloadEnabled}"></core-block>
|
||||||
|
</ng-container>
|
||||||
|
</ion-list>
|
||||||
|
</core-loading>
|
||||||
|
</div>
|
|
@ -0,0 +1,58 @@
|
||||||
|
:host {
|
||||||
|
&.core-no-blocks .core-course-blocks-content {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.core-has-blocks {
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
|
||||||
|
.core-course-blocks-content {
|
||||||
|
box-shadow: none !important;
|
||||||
|
flex-grow: 1;
|
||||||
|
max-width: 100%;
|
||||||
|
// @todo @include core-split-area-start();
|
||||||
|
}
|
||||||
|
|
||||||
|
div.core-course-blocks-side {
|
||||||
|
max-width: var(--side-blocks-max-width);
|
||||||
|
min-width: var(--side-blocks-min-width);
|
||||||
|
box-shadow: -4px 0px 16px rgba(0, 0, 0, 0.18);
|
||||||
|
// @todo @include core-split-area-end();
|
||||||
|
}
|
||||||
|
|
||||||
|
.core-course-blocks-content,
|
||||||
|
div.core-course-blocks-side {
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.core-loading-center,
|
||||||
|
core-loading.core-loading-loaded {
|
||||||
|
position: initial;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 767.98px) {
|
||||||
|
// Disable scroll on individual columns.
|
||||||
|
div.core-course-blocks-side {
|
||||||
|
height: auto;
|
||||||
|
|
||||||
|
&.core-hide-blocks {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:host-context([dir="rtl"]).core-has-blocks {
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
div.core-course-blocks-side {
|
||||||
|
box-shadow: 4px 0px 16px rgba(0, 0, 0, 0.18);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,111 @@
|
||||||
|
// (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, ViewChildren, Input, OnInit, QueryList, ElementRef } from '@angular/core';
|
||||||
|
import { IonContent } from '@ionic/angular';
|
||||||
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
|
import { CoreCourse, CoreCourseBlock } from '@features/course/services/course';
|
||||||
|
import { CoreBlockHelper } from '../../services/block-helper';
|
||||||
|
import { CoreBlockComponent } from '../block/block';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component that displays the list of course blocks.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'core-block-course-blocks',
|
||||||
|
templateUrl: 'core-block-course-blocks.html',
|
||||||
|
styleUrls: ['course-blocks.scss'],
|
||||||
|
})
|
||||||
|
export class CoreBlockCourseBlocksComponent implements OnInit {
|
||||||
|
|
||||||
|
@Input() courseId!: number;
|
||||||
|
@Input() hideBlocks = false;
|
||||||
|
@Input() hideBottomBlocks = false;
|
||||||
|
@Input() downloadEnabled = false;
|
||||||
|
|
||||||
|
@ViewChildren(CoreBlockComponent) blocksComponents?: QueryList<CoreBlockComponent>;
|
||||||
|
|
||||||
|
dataLoaded = false;
|
||||||
|
blocks: CoreCourseBlock[] = [];
|
||||||
|
|
||||||
|
protected element: HTMLElement;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
element: ElementRef,
|
||||||
|
protected content: IonContent,
|
||||||
|
) {
|
||||||
|
this.element = element.nativeElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component being initialized.
|
||||||
|
*/
|
||||||
|
async ngOnInit(): Promise<void> {
|
||||||
|
this.element.classList.add('core-no-blocks');
|
||||||
|
this.loadContent().finally(() => {
|
||||||
|
this.dataLoaded = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidate blocks data.
|
||||||
|
*
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
async invalidateBlocks(): Promise<void> {
|
||||||
|
const promises: Promise<void>[] = [];
|
||||||
|
|
||||||
|
if (CoreBlockHelper.instance.canGetCourseBlocks()) {
|
||||||
|
promises.push(CoreCourse.instance.invalidateCourseBlocks(this.courseId));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invalidate the blocks.
|
||||||
|
this.blocksComponents?.forEach((blockComponent) => {
|
||||||
|
promises.push(blockComponent.invalidate().catch(() => {
|
||||||
|
// Ignore errors.
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all(promises);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience function to fetch the data.
|
||||||
|
*
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
async loadContent(): Promise<void> {
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.blocks = await CoreBlockHelper.instance.getCourseBlocks(this.courseId);
|
||||||
|
} catch (error) {
|
||||||
|
CoreDomUtils.instance.showErrorModal(error);
|
||||||
|
|
||||||
|
this.blocks = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const scrollElement = await this.content.getScrollElement();
|
||||||
|
if (!this.hideBlocks && this.blocks.length > 0) {
|
||||||
|
this.element.classList.add('core-has-blocks');
|
||||||
|
this.element.classList.remove('core-no-blocks');
|
||||||
|
|
||||||
|
scrollElement.classList.add('core-course-block-with-blocks');
|
||||||
|
} else {
|
||||||
|
this.element.classList.remove('core-has-blocks');
|
||||||
|
this.element.classList.add('core-no-blocks');
|
||||||
|
scrollElement.classList.remove('core-course-block-with-blocks');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
<ion-item-divider class="ion-text-wrap" (click)="gotoBlock()">
|
||||||
|
<ion-label><h2>{{ title | translate }}</h2></ion-label>
|
||||||
|
<ion-icon class="item-detail-icon" name="chevron-forward-outline" slot="end" flip-rtl></ion-icon>
|
||||||
|
</ion-item-divider>
|
|
@ -0,0 +1,5 @@
|
||||||
|
:host {
|
||||||
|
ion-item-divider {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
// (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 { OnInit, Component } from '@angular/core';
|
||||||
|
import { CoreBlockBaseComponent } from '../../classes/base-block-component';
|
||||||
|
import { CoreNavHelper } from '@services/nav-helper';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component to render blocks with only a title and link.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'core-block-only-title',
|
||||||
|
templateUrl: 'core-block-only-title.html',
|
||||||
|
styleUrls: ['only-title-block.scss'],
|
||||||
|
})
|
||||||
|
export class CoreBlockOnlyTitleComponent extends CoreBlockBaseComponent implements OnInit {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super('CoreBlockOnlyTitleComponent');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component being initialized.
|
||||||
|
*/
|
||||||
|
async ngOnInit(): Promise<void> {
|
||||||
|
await super.ngOnInit();
|
||||||
|
|
||||||
|
this.fetchContentDefaultError = 'Error getting ' + this.block.contents?.title + ' data.';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Go to the block page.
|
||||||
|
*/
|
||||||
|
gotoBlock(): void {
|
||||||
|
CoreNavHelper.instance.goInSite(this.link!, this.linkParams!, undefined, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
<ion-item-divider class="ion-text-wrap" *ngIf="title" sticky="true">
|
||||||
|
<ion-label>
|
||||||
|
<h2>
|
||||||
|
<core-format-text [text]="title | translate" contextLevel="block" [contextInstanceId]="block.instanceid"
|
||||||
|
[courseId]="courseId">
|
||||||
|
</core-format-text>
|
||||||
|
</h2>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item-divider>
|
||||||
|
<core-loading [hideUntil]="loaded" class="core-loading-center">
|
||||||
|
<ion-item *ngIf="block.contents?.content" class="ion-text-wrap core-block-content">
|
||||||
|
<ion-label>
|
||||||
|
<core-format-text [text]="block.contents?.content" contextLevel="block" [contextInstanceId]="block.instanceid"
|
||||||
|
[courseId]="courseId">
|
||||||
|
</core-format-text>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item *ngIf="block.contents?.footer" class="ion-text-wrap core-block-footer">
|
||||||
|
<ion-label>
|
||||||
|
<core-format-text [text]="block.contents?.footer" contextLevel="block" [contextInstanceId]="block.instanceid"
|
||||||
|
[courseId]="courseId">
|
||||||
|
</core-format-text>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
</core-loading>
|
|
@ -0,0 +1,44 @@
|
||||||
|
// (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 { OnInit, Component } from '@angular/core';
|
||||||
|
import { CoreBlockBaseComponent } from '../../classes/base-block-component';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component to render blocks with pre-rendered HTML.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'core-block-pre-rendered',
|
||||||
|
templateUrl: 'core-block-pre-rendered.html',
|
||||||
|
})
|
||||||
|
export class CoreBlockPreRenderedComponent extends CoreBlockBaseComponent implements OnInit {
|
||||||
|
|
||||||
|
courseId?: number;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super('CoreBlockPreRenderedComponent');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component being initialized.
|
||||||
|
*/
|
||||||
|
async ngOnInit(): Promise<void> {
|
||||||
|
await super.ngOnInit();
|
||||||
|
|
||||||
|
this.courseId = this.contextLevel == 'course' ? this.instanceId : undefined;
|
||||||
|
|
||||||
|
this.fetchContentDefaultError = 'Error getting ' + this.block.contents?.title + ' data.';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"blocks": "Blocks"
|
||||||
|
}
|
|
@ -0,0 +1,203 @@
|
||||||
|
// (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, Type } from '@angular/core';
|
||||||
|
import { CoreSites } from '@services/sites';
|
||||||
|
import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate';
|
||||||
|
import { CoreSite } from '@classes/site';
|
||||||
|
import { Subject } from 'rxjs';
|
||||||
|
import { CoreCourseBlock } from '@features/course/services/course';
|
||||||
|
import { Params } from '@angular/router';
|
||||||
|
import { makeSingleton } from '@singletons';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface that all blocks must implement.
|
||||||
|
*/
|
||||||
|
export interface CoreBlockHandler extends CoreDelegateHandler {
|
||||||
|
/**
|
||||||
|
* Name of the block the handler supports. E.g. 'activity_modules'.
|
||||||
|
*/
|
||||||
|
blockName: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the data needed to render the block.
|
||||||
|
*
|
||||||
|
* @param block The block to render.
|
||||||
|
* @param contextLevel The context where the block will be used.
|
||||||
|
* @param instanceId The instance ID associated with the context level.
|
||||||
|
* @return Data or promise resolved with the data.
|
||||||
|
*/
|
||||||
|
getDisplayData?(
|
||||||
|
block: CoreCourseBlock,
|
||||||
|
contextLevel: string,
|
||||||
|
instanceId: number,
|
||||||
|
): CoreBlockHandlerData | Promise<CoreBlockHandlerData>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data needed to render a block. It's returned by the handler.
|
||||||
|
*/
|
||||||
|
export interface CoreBlockHandlerData {
|
||||||
|
/**
|
||||||
|
* Title to display for the block.
|
||||||
|
*/
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to add to the displayed block.
|
||||||
|
*/
|
||||||
|
class?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The component to render the contents of the block.
|
||||||
|
* It's recommended to return the class of the component, but you can also return an instance of the component.
|
||||||
|
*/
|
||||||
|
component: Type<unknown>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data to pass to the component. All the properties in this object will be passed to the component as inputs.
|
||||||
|
*/
|
||||||
|
componentData?: Record<string | number, unknown>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Link to go when showing only title.
|
||||||
|
*/
|
||||||
|
link?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Params of the link.
|
||||||
|
*/
|
||||||
|
linkParams?: Params;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delegate to register block handlers.
|
||||||
|
*/
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class CoreBlockDelegateService extends CoreDelegate<CoreBlockHandler> {
|
||||||
|
|
||||||
|
protected handlerNameProperty = 'blockName';
|
||||||
|
|
||||||
|
protected featurePrefix = 'CoreBlockDelegate_';
|
||||||
|
|
||||||
|
blocksUpdateObservable: Subject<void>;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super('CoreBlockDelegate', true);
|
||||||
|
|
||||||
|
this.blocksUpdateObservable = new Subject<void>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if blocks are disabled in a certain site.
|
||||||
|
*
|
||||||
|
* @param site Site. If not defined, use current site.
|
||||||
|
* @return Whether it's disabled.
|
||||||
|
*/
|
||||||
|
areBlocksDisabledInSite(site?: CoreSite): boolean {
|
||||||
|
site = site || CoreSites.instance.getCurrentSite();
|
||||||
|
|
||||||
|
return !!site && site.isFeatureDisabled('NoDelegate_SiteBlocks');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if blocks are disabled in a certain site for courses.
|
||||||
|
*
|
||||||
|
* @param site Site. If not defined, use current site.
|
||||||
|
* @return Whether it's disabled.
|
||||||
|
*/
|
||||||
|
areBlocksDisabledInCourses(site?: CoreSite): boolean {
|
||||||
|
site = site || CoreSites.instance.getCurrentSite();
|
||||||
|
|
||||||
|
return !!site && site.isFeatureDisabled('NoDelegate_CourseBlocks');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if blocks are disabled in a certain site.
|
||||||
|
*
|
||||||
|
* @param siteId Site Id. If not defined, use current site.
|
||||||
|
* @return Promise resolved with true if disabled, rejected or resolved with false otherwise.
|
||||||
|
*/
|
||||||
|
async areBlocksDisabled(siteId?: string): Promise<boolean> {
|
||||||
|
const site = await CoreSites.instance.getSite(siteId);
|
||||||
|
|
||||||
|
return this.areBlocksDisabledInSite(site);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the display data for a certain block.
|
||||||
|
*
|
||||||
|
* @param injector Injector.
|
||||||
|
* @param block The block to render.
|
||||||
|
* @param contextLevel The context where the block will be used.
|
||||||
|
* @param instanceId The instance ID associated with the context level.
|
||||||
|
* @return Promise resolved with the display data.
|
||||||
|
*/
|
||||||
|
async getBlockDisplayData(
|
||||||
|
block: CoreCourseBlock,
|
||||||
|
contextLevel: string,
|
||||||
|
instanceId: number,
|
||||||
|
): Promise<CoreBlockHandlerData | undefined> {
|
||||||
|
return this.executeFunctionOnEnabled(
|
||||||
|
block.name,
|
||||||
|
'getDisplayData',
|
||||||
|
[block, contextLevel, instanceId],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if any of the blocks in a list is supported.
|
||||||
|
*
|
||||||
|
* @param blocks The list of blocks.
|
||||||
|
* @return Whether any of the blocks is supported.
|
||||||
|
*/
|
||||||
|
hasSupportedBlock(blocks: CoreCourseBlock[]): boolean {
|
||||||
|
blocks = blocks || [];
|
||||||
|
|
||||||
|
return !!blocks.find((block) => this.isBlockSupported(block.name));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a block is supported.
|
||||||
|
*
|
||||||
|
* @param name Block "name". E.g. 'activity_modules'.
|
||||||
|
* @return Whether it's supported.
|
||||||
|
*/
|
||||||
|
isBlockSupported(name: string): boolean {
|
||||||
|
return this.hasHandler(name, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if feature is enabled or disabled in the site, depending on the feature prefix and the handler name.
|
||||||
|
*
|
||||||
|
* @param handler Handler to check.
|
||||||
|
* @param site Site to check.
|
||||||
|
* @return Whether is enabled or disabled in site.
|
||||||
|
*/
|
||||||
|
protected isFeatureDisabled(handler: CoreBlockHandler, site: CoreSite): boolean {
|
||||||
|
return this.areBlocksDisabledInSite(site) || super.isFeatureDisabled(handler, site);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when there are new block handlers available. Informs anyone who subscribed to the
|
||||||
|
* observable.
|
||||||
|
*/
|
||||||
|
updateData(): void {
|
||||||
|
this.blocksUpdateObservable.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CoreBlockDelegate extends makeSingleton(CoreBlockDelegateService) {}
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
// (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 { CoreCourse, CoreCourseBlock } from '@features/course/services/course';
|
||||||
|
import { CoreBlockDelegate } from './block-delegate';
|
||||||
|
import { makeSingleton } from '@singletons';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service that provides helper functions for blocks.
|
||||||
|
*/
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class CoreBlockHelperProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return if it get course blocks options is enabled for the current site.
|
||||||
|
*
|
||||||
|
* @return true if enabled, false otherwise.
|
||||||
|
*/
|
||||||
|
canGetCourseBlocks(): boolean {
|
||||||
|
return CoreCourse.instance.canGetCourseBlocks() && !CoreBlockDelegate.instance.areBlocksDisabledInCourses();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the list of blocks for the selected course.
|
||||||
|
*
|
||||||
|
* @param courseId Course ID.
|
||||||
|
* @return List of supported blocks.
|
||||||
|
*/
|
||||||
|
async getCourseBlocks(courseId: number): Promise<CoreCourseBlock[]> {
|
||||||
|
const canGetBlocks = this.canGetCourseBlocks();
|
||||||
|
|
||||||
|
if (!canGetBlocks) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const blocks = await CoreCourse.instance.getCourseBlocks(courseId);
|
||||||
|
const hasSupportedBlock = CoreBlockDelegate.instance.hasSupportedBlock(blocks);
|
||||||
|
if (!hasSupportedBlock) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return blocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CoreBlockHelper extends makeSingleton(CoreBlockHelperProvider) {}
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
// (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 { CoreBlockBaseHandler } from '../../classes/base-block-handler';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default handler used when a block type doesn't have a specific implementation.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class CoreBlockDefaultHandler extends CoreBlockBaseHandler {
|
||||||
|
|
||||||
|
name = 'CoreBlockDefault';
|
||||||
|
blockName = 'default';
|
||||||
|
|
||||||
|
}
|
|
@ -31,8 +31,6 @@ export class CoreContentLinksModuleListHandler extends CoreContentLinksHandlerBa
|
||||||
/**
|
/**
|
||||||
* Construct the handler.
|
* Construct the handler.
|
||||||
*
|
*
|
||||||
* @param linkHelper The CoreContentLinksHelperProvider instance.
|
|
||||||
* @param translate The TranslateService instance.
|
|
||||||
* @param addon Name of the addon as it's registered in course delegate. It'll be used to check if it's disabled.
|
* @param addon Name of the addon as it's registered in course delegate. It'll be used to check if it's disabled.
|
||||||
* @param modName Name of the module (assign, book, ...).
|
* @param modName Name of the module (assign, book, ...).
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1301,6 +1301,11 @@ export type CoreCourseBlock = {
|
||||||
value: string; // JSON encoded representation of the config value.
|
value: string; // JSON encoded representation of the config value.
|
||||||
type: string; // Type (instance or plugin).
|
type: string; // Type (instance or plugin).
|
||||||
}[];
|
}[];
|
||||||
|
configsRecord?: Record<string, { // Block instance and plugin configuration settings.
|
||||||
|
name: string; // Name.
|
||||||
|
value: string; // JSON encoded representation of the config value.
|
||||||
|
type: string; // Type (instance or plugin).
|
||||||
|
}>;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
(ionRefresh)="doRefresh($event)">
|
(ionRefresh)="doRefresh($event)">
|
||||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||||
</ion-refresher>
|
</ion-refresher>
|
||||||
<!-- @todo <core-block-course-blocks [courseId]="siteHomeId" [downloadEnabled]="downloadEnabled">-->
|
<core-block-course-blocks [courseId]="siteHomeId" [downloadEnabled]="downloadEnabled">
|
||||||
<core-loading [hideUntil]="dataLoaded">
|
<core-loading [hideUntil]="dataLoaded">
|
||||||
<ion-list>
|
<ion-list>
|
||||||
<!-- Site home main contents. -->
|
<!-- Site home main contents. -->
|
||||||
|
@ -56,7 +56,7 @@
|
||||||
|
|
||||||
</core-empty-box>
|
</core-empty-box>
|
||||||
</core-loading>
|
</core-loading>
|
||||||
<!-- @todo </core-block-course-blocks> -->
|
</core-block-course-blocks>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|
||||||
<ng-template #allCourseList>
|
<ng-template #allCourseList>
|
||||||
|
|
|
@ -20,6 +20,7 @@ import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
|
||||||
import { CoreDirectivesModule } from '@directives/directives.module';
|
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||||
import { CoreComponentsModule } from '@components/components.module';
|
import { CoreComponentsModule } from '@components/components.module';
|
||||||
|
import { CoreBlockComponentsModule } from '@/core/features/block/components/components.module';
|
||||||
|
|
||||||
import { CoreSiteHomeIndexPage } from '.';
|
import { CoreSiteHomeIndexPage } from '.';
|
||||||
|
|
||||||
|
@ -38,6 +39,7 @@ const routes: Routes = [
|
||||||
TranslateModule.forChild(),
|
TranslateModule.forChild(),
|
||||||
CoreDirectivesModule,
|
CoreDirectivesModule,
|
||||||
CoreComponentsModule,
|
CoreComponentsModule,
|
||||||
|
CoreBlockComponentsModule,
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
CoreSiteHomeIndexPage,
|
CoreSiteHomeIndexPage,
|
||||||
|
|
|
@ -40,11 +40,6 @@ ion-icon {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[dir=rtl] ion-icon.icon-flip-rtl {
|
|
||||||
-webkit-transform: scale(-1, 1);
|
|
||||||
transform: scale(-1, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ionic alert.
|
// Ionic alert.
|
||||||
ion-alert.core-alert-network-error .alert-head {
|
ion-alert.core-alert-network-error .alert-head {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -77,7 +72,11 @@ ion-alert.core-nohead {
|
||||||
// Ionic item divider.
|
// Ionic item divider.
|
||||||
ion-item-divider {
|
ion-item-divider {
|
||||||
--background: var(--gray-lighter);
|
--background: var(--gray-lighter);
|
||||||
border: 0;
|
.item-detail-icon {
|
||||||
|
font-size: 20px;
|
||||||
|
opacity: 0.25;
|
||||||
|
padding-inline-end: 16px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ionic list.
|
// Ionic list.
|
||||||
|
|
|
@ -151,6 +151,11 @@
|
||||||
--background: var(--custom-progress-background, var(--gray-lighter));
|
--background: var(--custom-progress-background, var(--gray-lighter));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
core-block-course-blocks {
|
||||||
|
--side-blocks-max-width: var(--custom-side-blocks-max-width, 30%);
|
||||||
|
--side-blocks-min-width: var(--custom-side-blocks-min-width, 280px);
|
||||||
|
}
|
||||||
|
|
||||||
--selected-item-color: var(--custom-selected-item-color, var(--core-color));
|
--selected-item-color: var(--custom-selected-item-color, var(--core-color));
|
||||||
--selected-item-border-width: var(--custom-selected-item-border-width, 5px);
|
--selected-item-border-width: var(--custom-selected-item-border-width, 5px);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue