Merge pull request #1909 from sammarshallou/MOBILE-2935

MOBILE-2935 Support site plugins for blocks on dashboard page
main
Juan Leyva 2019-05-15 18:15:45 +02:00 committed by GitHub
commit 94c8d251a9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 229 additions and 6 deletions

View File

@ -12,9 +12,11 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
import { Component, Input, OnInit, Injector, ViewChild } from '@angular/core'; import { Component, Input, OnInit, Injector, ViewChild, OnDestroy } from '@angular/core';
import { CoreBlockDelegate } from '../../providers/delegate'; import { CoreBlockDelegate } from '../../providers/delegate';
import { CoreDynamicComponent } from '@components/dynamic-component/dynamic-component'; import { CoreDynamicComponent } from '@components/dynamic-component/dynamic-component';
import { Subscription } from 'rxjs';
import { CoreEventsProvider } from '@providers/events';
/** /**
* Component to render a block. * Component to render a block.
@ -23,7 +25,7 @@ import { CoreDynamicComponent } from '@components/dynamic-component/dynamic-comp
selector: 'core-block', selector: 'core-block',
templateUrl: 'core-block.html' templateUrl: 'core-block.html'
}) })
export class CoreBlockComponent implements OnInit { export class CoreBlockComponent implements OnInit, OnDestroy {
@ViewChild(CoreDynamicComponent) dynamicComponent: CoreDynamicComponent; @ViewChild(CoreDynamicComponent) dynamicComponent: CoreDynamicComponent;
@Input() block: any; // The block to render. @Input() block: any; // The block to render.
@ -37,7 +39,10 @@ export class CoreBlockComponent implements OnInit {
class: string; // CSS class to apply to the block. class: string; // CSS class to apply to the block.
loaded = false; loaded = false;
constructor(protected injector: Injector, protected blockDelegate: CoreBlockDelegate) { } blockSubscription: Subscription;
constructor(protected injector: Injector, protected blockDelegate: CoreBlockDelegate,
protected eventsProvider: CoreEventsProvider) { }
/** /**
* Component being initialized. * Component being initialized.
@ -50,9 +55,28 @@ export class CoreBlockComponent implements OnInit {
} }
// Get the data to render the block. // Get the data to render the block.
this.initBlock();
}
/**
* 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).
*/
initBlock(): void {
this.blockDelegate.getBlockDisplayData(this.injector, this.block, this.contextLevel, this.instanceId).then((data) => { this.blockDelegate.getBlockDisplayData(this.injector, this.block, this.contextLevel, this.instanceId).then((data) => {
if (!data) { if (!data) {
// Block not supported, don't render it. // 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 = this.blockDelegate.blocksUpdateObservable.subscribe({
next: (): void => {
this.blockSubscription.unsubscribe();
delete this.blockSubscription;
this.initBlock();
}
});
return; return;
} }
@ -73,6 +97,16 @@ export class CoreBlockComponent implements OnInit {
}); });
} }
/**
* On destroy of the component, clear up any subscriptions.
*/
ngOnDestroy(): void {
if (this.blockSubscription) {
this.blockSubscription.unsubscribe();
delete this.blockSubscription;
}
}
/** /**
* Refresh the data. * Refresh the data.
* *

View File

@ -19,6 +19,8 @@ import { CoreSitesProvider } from '@providers/sites';
import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate'; import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate';
import { CoreBlockDefaultHandler } from './default-block-handler'; import { CoreBlockDefaultHandler } from './default-block-handler';
import { CoreSite } from '@classes/site'; import { CoreSite } from '@classes/site';
import { CoreSitePluginsProvider } from '@core/siteplugins/providers/siteplugins';
import { Subject } from 'rxjs';
/** /**
* Interface that all blocks must implement. * Interface that all blocks must implement.
@ -83,9 +85,12 @@ export class CoreBlockDelegate extends CoreDelegate {
protected featurePrefix = 'CoreBlockDelegate_'; protected featurePrefix = 'CoreBlockDelegate_';
blocksUpdateObservable: Subject<void>;
constructor(logger: CoreLoggerProvider, sitesProvider: CoreSitesProvider, eventsProvider: CoreEventsProvider, constructor(logger: CoreLoggerProvider, sitesProvider: CoreSitesProvider, eventsProvider: CoreEventsProvider,
protected defaultHandler: CoreBlockDefaultHandler) { protected defaultHandler: CoreBlockDefaultHandler, protected sitePluginsProvider: CoreSitePluginsProvider) {
super('CoreBlockDelegate', logger, sitesProvider, eventsProvider); super('CoreBlockDelegate', logger, sitesProvider, eventsProvider);
this.blocksUpdateObservable = new Subject<void>();
} }
/** /**
@ -157,4 +162,26 @@ export class CoreBlockDelegate extends CoreDelegate {
protected isFeatureDisabled(handler: CoreDelegateHandler, site: CoreSite): boolean { protected isFeatureDisabled(handler: CoreDelegateHandler, site: CoreSite): boolean {
return this.areBlocksDisabledInSite(site) || super.isFeatureDisabled(handler, site); return this.areBlocksDisabledInSite(site) || super.isFeatureDisabled(handler, site);
} }
/**
* Gets the handler name for a given block name.
*
* @param {string} name Block name e.g. 'activity_modules'
* @return {string} Full name of corresponding handler
*/
getHandlerName(name: string): string {
if (!this.isBlockSupported(name)) {
return '';
}
return this.getHandler(name, true).name;
}
/**
* Called when there are new block handlers available. Informs anyone who subscribed to the
* observable.
*/
updateData(): void {
this.blocksUpdateObservable.next();
}
} }

View File

@ -78,6 +78,7 @@ import { CoreBlockComponentsModule } from '@core/block/components/components.mod
import { CoreCourseUnsupportedModuleComponent } from '@core/course/components/unsupported-module/unsupported-module'; import { CoreCourseUnsupportedModuleComponent } from '@core/course/components/unsupported-module/unsupported-module';
import { CoreCourseFormatSingleActivityComponent } from '@core/course/formats/singleactivity/components/singleactivity'; import { CoreCourseFormatSingleActivityComponent } from '@core/course/formats/singleactivity/components/singleactivity';
import { CoreSitePluginsModuleIndexComponent } from '@core/siteplugins/components/module-index/module-index'; import { CoreSitePluginsModuleIndexComponent } from '@core/siteplugins/components/module-index/module-index';
import { CoreSitePluginsBlockComponent } from '@core/siteplugins/components/block/block';
import { CoreSitePluginsCourseOptionComponent } from '@core/siteplugins/components/course-option/course-option'; import { CoreSitePluginsCourseOptionComponent } from '@core/siteplugins/components/course-option/course-option';
import { CoreSitePluginsCourseFormatComponent } from '@core/siteplugins/components/course-format/course-format'; import { CoreSitePluginsCourseFormatComponent } from '@core/siteplugins/components/course-format/course-format';
import { CoreSitePluginsQuestionComponent } from '@core/siteplugins/components/question/question'; import { CoreSitePluginsQuestionComponent } from '@core/siteplugins/components/question/question';
@ -269,6 +270,7 @@ export class CoreCompileProvider {
instance['CoreCourseUnsupportedModuleComponent'] = CoreCourseUnsupportedModuleComponent; instance['CoreCourseUnsupportedModuleComponent'] = CoreCourseUnsupportedModuleComponent;
instance['CoreCourseFormatSingleActivityComponent'] = CoreCourseFormatSingleActivityComponent; instance['CoreCourseFormatSingleActivityComponent'] = CoreCourseFormatSingleActivityComponent;
instance['CoreSitePluginsModuleIndexComponent'] = CoreSitePluginsModuleIndexComponent; instance['CoreSitePluginsModuleIndexComponent'] = CoreSitePluginsModuleIndexComponent;
instance['CoreSitePluginsBlockComponent'] = CoreSitePluginsBlockComponent;
instance['CoreSitePluginsCourseOptionComponent'] = CoreSitePluginsCourseOptionComponent; instance['CoreSitePluginsCourseOptionComponent'] = CoreSitePluginsCourseOptionComponent;
instance['CoreSitePluginsCourseFormatComponent'] = CoreSitePluginsCourseFormatComponent; instance['CoreSitePluginsCourseFormatComponent'] = CoreSitePluginsCourseFormatComponent;
instance['CoreSitePluginsQuestionComponent'] = CoreSitePluginsQuestionComponent; instance['CoreSitePluginsQuestionComponent'] = CoreSitePluginsQuestionComponent;

View File

@ -0,0 +1,60 @@
// (C) Copyright 2015 Martin Dougiamas
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injector } from '@angular/core';
import { CoreSitePluginsBaseHandler } from './base-handler';
import { CoreBlockHandler, CoreBlockHandlerData } from '@core/block/providers/delegate';
import { CoreSitePluginsBlockComponent } from '@core/siteplugins/components/block/block';
/**
* Handler to support a block using a site plugin.
*/
export class CoreSitePluginsBlockHandler extends CoreSitePluginsBaseHandler implements CoreBlockHandler {
constructor(name: string, public blockName: string, protected handlerSchema: any, protected initResult: any) {
super(name);
}
/**
* Gets display data for this block. The class and title can be provided either by data from
* the handler schema (mobile.php) or using default values.
*
* @param {Injector} injector Injector
* @param {any} block Block data
* @param {string} contextLevel Context level (not used)
* @param {number} instanceId Instance if (not used)
* @return {CoreBlockHandlerData|Promise<CoreBlockHandlerData>} Data or promise resolved with the data
*/
getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number):
CoreBlockHandlerData | Promise<CoreBlockHandlerData> {
let title,
className;
if (this.handlerSchema.displaydata && this.handlerSchema.displaydata.title) {
title = this.handlerSchema.displaydata.title;
} else {
title = 'plugins.block_' + block.name + '.pluginname';
}
if (this.handlerSchema.displaydata && this.handlerSchema.displaydata.class) {
className = this.handlerSchema.displaydata.class;
} else {
className = 'block_' + block.name;
}
return {
title: title,
class: className,
component: CoreSitePluginsBlockComponent
};
}
}

View File

@ -0,0 +1,68 @@
// (C) Copyright 2015 Martin Dougiamas
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, OnChanges, Input, ViewChild, Injector } from '@angular/core';
import { CoreSitePluginsProvider } from '../../providers/siteplugins';
import { CoreSitePluginsPluginContentComponent } from '../plugin-content/plugin-content';
import { CoreBlockBaseComponent } from '@core/block/classes/base-block-component';
import { CoreBlockDelegate } from '@core/block/providers/delegate';
/**
* Component that displays the index of a course format site plugin.
*/
@Component({
selector: 'core-site-plugins-block',
templateUrl: 'core-siteplugins-block.html',
})
export class CoreSitePluginsBlockComponent extends CoreBlockBaseComponent implements OnChanges {
@Input() block: any;
@Input() contextLevel: number;
@Input() instanceId: number;
@ViewChild(CoreSitePluginsPluginContentComponent) content: CoreSitePluginsPluginContentComponent;
component: string;
method: string;
args: any;
initResult: any;
constructor(protected injector: Injector, protected sitePluginsProvider: CoreSitePluginsProvider,
protected blockDelegate: CoreBlockDelegate) {
super(injector, 'CoreSitePluginsBlockComponent');
}
/**
* Detect changes on input properties.
*/
ngOnChanges(): void {
if (!this.component) {
// Initialize the data.
const handlerName = this.blockDelegate.getHandlerName(this.block.name);
const handler = this.sitePluginsProvider.getSitePluginHandler(handlerName);
if (handler) {
this.component = handler.plugin.component;
this.method = handler.handlerSchema.method;
this.args = { };
this.initResult = handler.initResult;
}
}
}
/**
* Pass on content invalidation by refreshing content in the plugin content component.
*/
protected invalidateContent(): Promise<any> {
return Promise.resolve(this.content.refreshContent());
}
}

View File

@ -0,0 +1 @@
<core-site-plugins-plugin-content *ngIf="component && method" [component]="component" [method]="method" [args]="args" [initResult]="initResult" [data]="data"></core-site-plugins-plugin-content>

View File

@ -29,11 +29,13 @@ import { CoreSitePluginsQuizAccessRuleComponent } from './quiz-access-rule/quiz-
import { CoreSitePluginsAssignFeedbackComponent } from './assign-feedback/assign-feedback'; import { CoreSitePluginsAssignFeedbackComponent } from './assign-feedback/assign-feedback';
import { CoreSitePluginsAssignSubmissionComponent } from './assign-submission/assign-submission'; import { CoreSitePluginsAssignSubmissionComponent } from './assign-submission/assign-submission';
import { CoreSitePluginsWorkshopAssessmentStrategyComponent } from './workshop-assessment-strategy/workshop-assessment-strategy'; import { CoreSitePluginsWorkshopAssessmentStrategyComponent } from './workshop-assessment-strategy/workshop-assessment-strategy';
import { CoreSitePluginsBlockComponent } from '@core/siteplugins/components/block/block';
@NgModule({ @NgModule({
declarations: [ declarations: [
CoreSitePluginsPluginContentComponent, CoreSitePluginsPluginContentComponent,
CoreSitePluginsModuleIndexComponent, CoreSitePluginsModuleIndexComponent,
CoreSitePluginsBlockComponent,
CoreSitePluginsCourseOptionComponent, CoreSitePluginsCourseOptionComponent,
CoreSitePluginsCourseFormatComponent, CoreSitePluginsCourseFormatComponent,
CoreSitePluginsUserProfileFieldComponent, CoreSitePluginsUserProfileFieldComponent,
@ -56,6 +58,7 @@ import { CoreSitePluginsWorkshopAssessmentStrategyComponent } from './workshop-a
exports: [ exports: [
CoreSitePluginsPluginContentComponent, CoreSitePluginsPluginContentComponent,
CoreSitePluginsModuleIndexComponent, CoreSitePluginsModuleIndexComponent,
CoreSitePluginsBlockComponent,
CoreSitePluginsCourseOptionComponent, CoreSitePluginsCourseOptionComponent,
CoreSitePluginsCourseFormatComponent, CoreSitePluginsCourseFormatComponent,
CoreSitePluginsUserProfileFieldComponent, CoreSitePluginsUserProfileFieldComponent,
@ -68,6 +71,7 @@ import { CoreSitePluginsWorkshopAssessmentStrategyComponent } from './workshop-a
], ],
entryComponents: [ entryComponents: [
CoreSitePluginsModuleIndexComponent, CoreSitePluginsModuleIndexComponent,
CoreSitePluginsBlockComponent,
CoreSitePluginsCourseOptionComponent, CoreSitePluginsCourseOptionComponent,
CoreSitePluginsCourseFormatComponent, CoreSitePluginsCourseFormatComponent,
CoreSitePluginsUserProfileFieldComponent, CoreSitePluginsUserProfileFieldComponent,

View File

@ -64,6 +64,8 @@ import { CoreSitePluginsQuizAccessRuleHandler } from '../classes/handlers/quiz-a
import { CoreSitePluginsAssignFeedbackHandler } from '../classes/handlers/assign-feedback-handler'; import { CoreSitePluginsAssignFeedbackHandler } from '../classes/handlers/assign-feedback-handler';
import { CoreSitePluginsAssignSubmissionHandler } from '../classes/handlers/assign-submission-handler'; import { CoreSitePluginsAssignSubmissionHandler } from '../classes/handlers/assign-submission-handler';
import { CoreSitePluginsWorkshopAssessmentStrategyHandler } from '../classes/handlers/workshop-assessment-strategy-handler'; import { CoreSitePluginsWorkshopAssessmentStrategyHandler } from '../classes/handlers/workshop-assessment-strategy-handler';
import { CoreBlockDelegate } from '@core/block/providers/delegate';
import { CoreSitePluginsBlockHandler } from '@core/siteplugins/classes/handlers/block-handler';
/** /**
* Helper service to provide functionalities regarding site plugins. It basically has the features to load and register site * Helper service to provide functionalities regarding site plugins. It basically has the features to load and register site
@ -92,7 +94,7 @@ export class CoreSitePluginsHelperProvider {
private assignSubmissionDelegate: AddonModAssignSubmissionDelegate, private translate: TranslateService, private assignSubmissionDelegate: AddonModAssignSubmissionDelegate, private translate: TranslateService,
private assignFeedbackDelegate: AddonModAssignFeedbackDelegate, private appProvider: CoreAppProvider, private assignFeedbackDelegate: AddonModAssignFeedbackDelegate, private appProvider: CoreAppProvider,
private workshopAssessmentStrategyDelegate: AddonWorkshopAssessmentStrategyDelegate, private workshopAssessmentStrategyDelegate: AddonWorkshopAssessmentStrategyDelegate,
private courseProvider: CoreCourseProvider) { private courseProvider: CoreCourseProvider, private blockDelegate: CoreBlockDelegate) {
this.logger = logger.getInstance('CoreSitePluginsHelperProvider'); this.logger = logger.getInstance('CoreSitePluginsHelperProvider');
@ -478,6 +480,10 @@ export class CoreSitePluginsHelperProvider {
promise = Promise.resolve(this.registerQuestionBehaviourHandler(plugin, handlerName, handlerSchema)); promise = Promise.resolve(this.registerQuestionBehaviourHandler(plugin, handlerName, handlerSchema));
break; break;
case 'CoreBlockDelegate':
promise = Promise.resolve(this.registerBlockHandler(plugin, handlerName, handlerSchema, result));
break;
case 'AddonMessageOutputDelegate': case 'AddonMessageOutputDelegate':
promise = Promise.resolve(this.registerMessageOutputHandler(plugin, handlerName, handlerSchema, result)); promise = Promise.resolve(this.registerMessageOutputHandler(plugin, handlerName, handlerSchema, result));
break; break;
@ -611,6 +617,27 @@ export class CoreSitePluginsHelperProvider {
}); });
} }
/**
* Given a handler in a plugin, register it in the block delegate.
*
* @param {any} plugin Data of the plugin.
* @param {string} handlerName Name of the handler in the plugin.
* @param {any} handlerSchema Data about the handler.
* @param {any} initResult Result of init function.
* @return {string|Promise<string>} A string (or a promise resolved with a string) to identify the handler.
*/
protected registerBlockHandler(plugin: any, handlerName: string, handlerSchema: any, initResult: any):
string | Promise<string> {
const uniqueName = this.sitePluginsProvider.getHandlerUniqueName(plugin, handlerName),
blockName = (handlerSchema.moodlecomponent || plugin.component).replace('block_', '');
this.blockDelegate.registerHandler(
new CoreSitePluginsBlockHandler(uniqueName, blockName, handlerSchema, initResult));
return uniqueName;
}
/** /**
* Given a handler in a plugin, register it in the course format delegate. * Given a handler in a plugin, register it in the course format delegate.
* *