diff --git a/scripts/langindex.json b/scripts/langindex.json index 4cdb865ae..592d78cb5 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -20,6 +20,7 @@ "addon.block_myoverview.nocoursespast": "block_myoverview", "addon.block_myoverview.past": "block_myoverview", "addon.block_myoverview.title": "block_myoverview", + "addon.block_sitemainmenu.pluginname": "block_site_main_menu", "addon.block_timeline.duedate": "block_timeline", "addon.block_timeline.next30days": "block_timeline", "addon.block_timeline.next3months": "block_timeline", diff --git a/src/addon/block/sitemainmenu/components/components.module.ts b/src/addon/block/sitemainmenu/components/components.module.ts new file mode 100644 index 000000000..a121183a5 --- /dev/null +++ b/src/addon/block/sitemainmenu/components/components.module.ts @@ -0,0 +1,45 @@ +// (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 { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { IonicModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { AddonBlockSiteMainMenuComponent } from './sitemainmenu/sitemainmenu'; +import { CoreComponentsModule } from '@components/components.module'; +import { CoreDirectivesModule } from '@directives/directives.module'; +import { CoreCourseComponentsModule } from '@core/course/components/components.module'; + +@NgModule({ + declarations: [ + AddonBlockSiteMainMenuComponent + ], + imports: [ + CommonModule, + IonicModule, + TranslateModule.forChild(), + CoreComponentsModule, + CoreDirectivesModule, + CoreCourseComponentsModule + ], + providers: [ + ], + exports: [ + AddonBlockSiteMainMenuComponent + ], + entryComponents: [ + AddonBlockSiteMainMenuComponent + ] +}) +export class AddonBlockSiteMainMenuComponentsModule {} diff --git a/src/addon/block/sitemainmenu/components/sitemainmenu/addon-block-sitemainmenu.html b/src/addon/block/sitemainmenu/components/sitemainmenu/addon-block-sitemainmenu.html new file mode 100644 index 000000000..5941df804 --- /dev/null +++ b/src/addon/block/sitemainmenu/components/sitemainmenu/addon-block-sitemainmenu.html @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/addon/block/sitemainmenu/components/sitemainmenu/sitemainmenu.ts b/src/addon/block/sitemainmenu/components/sitemainmenu/sitemainmenu.ts new file mode 100644 index 000000000..02c044771 --- /dev/null +++ b/src/addon/block/sitemainmenu/components/sitemainmenu/sitemainmenu.ts @@ -0,0 +1,114 @@ +// (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, OnInit, Injector } from '@angular/core'; +import { CoreSitesProvider } from '@providers/sites'; +import { CoreCourseProvider } from '@core/course/providers/course'; +import { CoreCourseHelperProvider } from '@core/course/providers/helper'; +import { CoreSiteHomeProvider } from '@core/sitehome/providers/sitehome'; +import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module-prefetch-delegate'; +import { CoreBlockBaseComponent } from '@core/block/classes/base-block-component'; + +/** + * Component to render a site main menu block. + */ +@Component({ + selector: 'addon-block-sitemainmenu', + templateUrl: 'addon-block-sitemainmenu.html' +}) +export class AddonBlockSiteMainMenuComponent extends CoreBlockBaseComponent implements OnInit { + block: any; + siteHomeId: number; + + protected fetchContentDefaultError = 'Error getting main menu data.'; + + constructor(injector: Injector, protected sitesProvider: CoreSitesProvider, protected courseProvider: CoreCourseProvider, + protected courseHelper: CoreCourseHelperProvider, protected siteHomeProvider: CoreSiteHomeProvider, + protected prefetchDelegate: CoreCourseModulePrefetchDelegate) { + + super(injector, 'AddonBlockSiteMainMenuComponent'); + + this.siteHomeId = sitesProvider.getCurrentSite().getSiteHomeId(); + } + + /** + * Component being initialized. + */ + ngOnInit(): void { + super.ngOnInit(); + } + + /** + * Perform the invalidate content function. + * + * @return {Promise} Resolved when done. + */ + protected invalidateContent(): Promise { + const promises = []; + + promises.push(this.courseProvider.invalidateSections(this.siteHomeId)); + promises.push(this.siteHomeProvider.invalidateNewsForum(this.siteHomeId)); + + if (this.block && this.block.modules) { + // Invalidate modules prefetch data. + promises.push(this.prefetchDelegate.invalidateModules(this.block.modules, this.siteHomeId)); + } + + return Promise.all(promises); + } + + /** + * Fetch the data to render the block. + * + * @return {Promise} Promise resolved when done. + */ + protected fetchContent(): Promise { + return this.courseProvider.getSections(this.siteHomeId, false, true).then((sections) => { + this.block = sections[0]; + + if (this.block) { + this.block.hasContent = this.courseHelper.sectionHasContent(this.block); + this.courseHelper.addHandlerDataForModules([this.block], this.siteHomeId); + + // Check if Site Home displays announcements. If so, remove it from the main menu block. + const currentSite = this.sitesProvider.getCurrentSite(), + config = currentSite ? currentSite.getStoredConfig() || {} : {}; + let hasNewsItem = false; + + if (config.frontpageloggedin) { + const items = config.frontpageloggedin.split(','); + + hasNewsItem = items.find((item) => { return item == '0'; }); + } + + if (hasNewsItem && this.block.modules) { + // Remove forum activity (news one only) from the main menu block to prevent duplicates. + return this.siteHomeProvider.getNewsForum(this.siteHomeId).then((forum) => { + // Search the module that belongs to site news. + for (let i = 0; i < this.block.modules.length; i++) { + const module = this.block.modules[i]; + + if (module.modname == 'forum' && module.instance == forum.id) { + this.block.modules.splice(i, 1); + break; + } + } + }).catch(() => { + // Ignore errors. + }); + } + } + }); + } +} diff --git a/src/addon/block/sitemainmenu/lang/en.json b/src/addon/block/sitemainmenu/lang/en.json new file mode 100644 index 000000000..0f3aca2ff --- /dev/null +++ b/src/addon/block/sitemainmenu/lang/en.json @@ -0,0 +1,3 @@ +{ + "pluginname": "Main menu" +} diff --git a/src/addon/block/sitemainmenu/providers/block-handler.ts b/src/addon/block/sitemainmenu/providers/block-handler.ts new file mode 100644 index 000000000..09a94796d --- /dev/null +++ b/src/addon/block/sitemainmenu/providers/block-handler.ts @@ -0,0 +1,58 @@ +// (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 { Injectable, Injector } from '@angular/core'; +import { CoreBlockHandler, CoreBlockHandlerData } from '@core/block/providers/delegate'; +import { AddonBlockSiteMainMenuComponent } from '../components/sitemainmenu/sitemainmenu'; + +/** + * Course nav handler. + */ +@Injectable() +export class AddonBlockSiteMainMenuHandler implements CoreBlockHandler { + name = 'AddonBlockSiteMainMenuHandler'; + blockName = 'site_main_menu'; + + constructor() { + // Nothing to do. + } + + /** + * Check if the handler is enabled on a site level. + * + * @return {boolean} Whether or not the handler is enabled on a site level. + */ + isEnabled(): boolean | Promise { + return true; + } + + /** + * Returns the data needed to render the block. + * + * @param {Injector} injector Injector. + * @param {any} block The block to render. + * @param {string} contextLevel The context where the block will be used. + * @param {number} instanceId The instance ID associated with the context level. + * @return {CoreBlockHandlerData|Promise} Data or promise resolved with the data. + */ + getDisplayData?(injector: Injector, block: any, contextLevel: string, instanceId: number) + : CoreBlockHandlerData | Promise { + + return { + title: 'addon.block_sitemainmenu.pluginname', + class: 'addon-block-sitemainmenu', + component: AddonBlockSiteMainMenuComponent + }; + } +} diff --git a/src/addon/block/sitemainmenu/sitemainmenu.module.ts b/src/addon/block/sitemainmenu/sitemainmenu.module.ts new file mode 100644 index 000000000..ace9f339e --- /dev/null +++ b/src/addon/block/sitemainmenu/sitemainmenu.module.ts @@ -0,0 +1,44 @@ +// (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 { NgModule } from '@angular/core'; +import { IonicModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoreComponentsModule } from '@components/components.module'; +import { CoreDirectivesModule } from '@directives/directives.module'; +import { AddonBlockSiteMainMenuComponentsModule } from './components/components.module'; +import { CoreBlockDelegate } from '@core/block/providers/delegate'; +import { AddonBlockSiteMainMenuHandler } from './providers/block-handler'; + +@NgModule({ + declarations: [ + ], + imports: [ + IonicModule, + CoreComponentsModule, + CoreDirectivesModule, + AddonBlockSiteMainMenuComponentsModule, + TranslateModule.forChild() + ], + exports: [ + ], + providers: [ + AddonBlockSiteMainMenuHandler + ] +}) +export class AddonBlockSiteMainMenuModule { + constructor(blockDelegate: CoreBlockDelegate, blockHandler: AddonBlockSiteMainMenuHandler) { + blockDelegate.registerHandler(blockHandler); + } +} diff --git a/src/app/app.module.ts b/src/app/app.module.ts index a76d66cba..9c4344d25 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -85,6 +85,7 @@ import { AddonCourseCompletionModule } from '@addon/coursecompletion/coursecompl import { AddonUserProfileFieldModule } from '@addon/userprofilefield/userprofilefield.module'; import { AddonFilesModule } from '@addon/files/files.module'; import { AddonBlockMyOverviewModule } from '@addon/block/myoverview/myoverview.module'; +import { AddonBlockSiteMainMenuModule } from '@addon/block/sitemainmenu/sitemainmenu.module'; import { AddonBlockTimelineModule } from '@addon/block/timeline/timeline.module'; import { AddonModAssignModule } from '@addon/mod/assign/assign.module'; import { AddonModBookModule } from '@addon/mod/book/book.module'; @@ -197,6 +198,7 @@ export const CORE_PROVIDERS: any[] = [ AddonUserProfileFieldModule, AddonFilesModule, AddonBlockMyOverviewModule, + AddonBlockSiteMainMenuModule, AddonBlockTimelineModule, AddonModAssignModule, AddonModBookModule, diff --git a/src/core/block/classes/base-block-component.ts b/src/core/block/classes/base-block-component.ts index 1fe1dc326..56cbad4f6 100644 --- a/src/core/block/classes/base-block-component.ts +++ b/src/core/block/classes/base-block-component.ts @@ -49,13 +49,9 @@ export class CoreBlockBaseComponent implements OnInit { */ doRefresh(refresher?: any, done?: () => void, showErrors: boolean = false): Promise { if (this.loaded) { - return this.invalidateContent().catch(() => { - // Ignore errors. - }).then(() => { - return this.refreshContent(showErrors).finally(() => { - refresher && refresher.complete(); - done && done(); - }); + return this.refreshContent(showErrors).finally(() => { + refresher && refresher.complete(); + done && done(); }); } diff --git a/src/core/block/components/block/block.ts b/src/core/block/components/block/block.ts index c27ddf181..4d6b67dc0 100644 --- a/src/core/block/components/block/block.ts +++ b/src/core/block/components/block/block.ts @@ -12,8 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, Input, OnInit, Injector } from '@angular/core'; +import { Component, Input, OnInit, Injector, ViewChild } from '@angular/core'; import { CoreBlockDelegate } from '../../providers/delegate'; +import { CoreDynamicComponent } from '@components/dynamic-component/dynamic-component'; /** * Component to render a block. @@ -23,6 +24,8 @@ import { CoreBlockDelegate } from '../../providers/delegate'; templateUrl: 'core-block.html' }) export class CoreBlockComponent implements OnInit { + @ViewChild(CoreDynamicComponent) dynamicComponent: CoreDynamicComponent; + @Input() block: any; // 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. @@ -69,4 +72,33 @@ export class CoreBlockComponent implements OnInit { this.loaded = true; }); } + + /** + * Refresh the data. + * + * @param {any} [refresher] Refresher. Please pass this only if the refresher should finish when this function finishes. + * @param {Function} [done] Function to call when done. + * @param {boolean} [showErrors=false] If show errors to the user of hide them. + * @return {Promise} Promise resolved when done. + */ + doRefresh(refresher?: any, done?: () => void, showErrors: boolean = false): Promise { + if (this.dynamicComponent) { + return Promise.resolve(this.dynamicComponent.callComponentFunction('doRefresh', [refresher, done, showErrors])); + } + + return Promise.resolve(); + } + + /** + * Invalidate some data. + * + * @return {Promise} Promise resolved when done. + */ + invalidate(): Promise { + if (this.dynamicComponent) { + return Promise.resolve(this.dynamicComponent.callComponentFunction('invalidateContent')); + } + + return Promise.resolve(); + } } diff --git a/src/core/sitehome/components/components.module.ts b/src/core/sitehome/components/components.module.ts index d0fd2ddf8..55f9916d9 100644 --- a/src/core/sitehome/components/components.module.ts +++ b/src/core/sitehome/components/components.module.ts @@ -19,6 +19,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { CoreComponentsModule } from '@components/components.module'; import { CoreDirectivesModule } from '@directives/directives.module'; import { CoreCourseComponentsModule } from '@core/course/components/components.module'; +import { CoreBlockComponentsModule } from '@core/block/components/components.module'; import { CoreSiteHomeIndexComponent } from './index/index'; import { CoreSiteHomeAllCourseListComponent } from './all-course-list/all-course-list'; import { CoreSiteHomeCategoriesComponent } from './categories/categories'; @@ -41,7 +42,8 @@ import { CoreSiteHomeNewsComponent } from './news/news'; TranslateModule.forChild(), CoreComponentsModule, CoreDirectivesModule, - CoreCourseComponentsModule + CoreCourseComponentsModule, + CoreBlockComponentsModule ], exports: [ CoreSiteHomeIndexComponent, diff --git a/src/core/sitehome/components/index/core-sitehome-index.html b/src/core/sitehome/components/index/core-sitehome-index.html index ba1c2df9a..600834cd1 100644 --- a/src/core/sitehome/components/index/core-sitehome-index.html +++ b/src/core/sitehome/components/index/core-sitehome-index.html @@ -22,16 +22,11 @@ - - - - - - - - + + + - + diff --git a/src/core/sitehome/components/index/index.ts b/src/core/sitehome/components/index/index.ts index 17837f3ab..36ae14d5a 100644 --- a/src/core/sitehome/components/index/index.ts +++ b/src/core/sitehome/components/index/index.ts @@ -12,13 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, ViewChildren, QueryList } from '@angular/core'; import { CoreSitesProvider } from '@providers/sites'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreCourseHelperProvider } from '@core/course/providers/helper'; import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module-prefetch-delegate'; -import { CoreSiteHomeProvider } from '../../providers/sitehome'; +import { CoreBlockDelegate } from '@core/block/providers/delegate'; +import { CoreBlockComponent } from '@core/block/components/block/block'; /** * Component that displays site home index. @@ -28,18 +29,19 @@ import { CoreSiteHomeProvider } from '../../providers/sitehome'; templateUrl: 'core-sitehome-index.html', }) export class CoreSiteHomeIndexComponent implements OnInit { + @ViewChildren(CoreBlockComponent) blocksComponents: QueryList; + dataLoaded = false; section: any; - mainMenuBlock: any; hasContent: boolean; + hasSupportedBlock: boolean; items: any[] = []; siteHomeId: number; - - protected sectionsLoaded: any[]; + blocks: any[]; constructor(private domUtils: CoreDomUtilsProvider, private sitesProvider: CoreSitesProvider, private courseProvider: CoreCourseProvider, private courseHelper: CoreCourseHelperProvider, - private prefetchDelegate: CoreCourseModulePrefetchDelegate, private siteHomeProvider: CoreSiteHomeProvider) { + private prefetchDelegate: CoreCourseModulePrefetchDelegate, private blockDelegate: CoreBlockDelegate) { this.siteHomeId = sitesProvider.getCurrentSite().getSiteHomeId(); } @@ -69,16 +71,22 @@ export class CoreSiteHomeIndexComponent implements OnInit { }); })); - if (this.sectionsLoaded) { + if (this.section && this.section.modules) { // Invalidate modules prefetch data. - const modules = this.courseProvider.getSectionsModules(this.sectionsLoaded); - promises.push(this.prefetchDelegate.invalidateModules(modules, this.siteHomeId)); + promises.push(this.prefetchDelegate.invalidateModules(this.section.modules, this.siteHomeId)); } if (this.courseProvider.canGetCourseBlocks()) { promises.push(this.courseProvider.invalidateCourseBlocks(this.siteHomeId)); } + // Invalidate the blocks. + this.blocksComponents.forEach((blockComponent) => { + promises.push(blockComponent.invalidate().catch(() => { + // Ignore errors. + })); + }); + Promise.all(promises).finally(() => { this.loadContent().finally(() => { refresher.complete(); @@ -92,7 +100,6 @@ export class CoreSiteHomeIndexComponent implements OnInit { * @return {Promise} Promise resolved when done. */ protected loadContent(): Promise { - let hasNewsItem = false; this.hasContent = false; const config = this.sitesProvider.getCurrentSite().getStoredConfig() || { numsections: 1 }; @@ -120,80 +127,49 @@ export class CoreSiteHomeIndexComponent implements OnInit { return; } - if (item == 'news') { - hasNewsItem = true; - } - this.hasContent = true; this.items.push(item); }); } return this.courseProvider.getSections(this.siteHomeId, false, true).then((sections) => { - const promises = []; - - this.sectionsLoaded = Array.from(sections); // Check "Include a topic section" setting from numsections. - this.section = config.numsections && sections.length > 0 ? sections.pop() : false; + this.section = config.numsections ? sections[1] : false; if (this.section) { this.section.hasContent = this.courseHelper.sectionHasContent(this.section); this.hasContent = this.courseHelper.addHandlerDataForModules([this.section], this.siteHomeId) || this.hasContent; } - const mainMenuBlock = sections.length > 0 ? sections.pop() : false; - this.mainMenuBlock = false; - - if (mainMenuBlock) { - // Check if the block can be viewed. - let promise; - - if (this.courseProvider.canGetCourseBlocks()) { - promise = this.courseProvider.getCourseBlocks(this.siteHomeId).then((blocks) => { - // Search if the main menu block is enabled. - return !!blocks.find((block) => { return block.name == 'site_main_menu'; }); - }).catch(() => { - return true; - }); - } else { - // We don't know if it can be viewed, so always display it. - promise = Promise.resolve(true); - } - - promises.push(promise.then((canView) => { - if (canView) { - // User can view the block, display it and calculate its data. - this.mainMenuBlock = mainMenuBlock; - this.mainMenuBlock.hasContent = this.courseHelper.sectionHasContent(this.mainMenuBlock); - this.hasContent = this.courseHelper.addHandlerDataForModules([mainMenuBlock], this.siteHomeId) || - this.hasContent; - - if (hasNewsItem && this.mainMenuBlock.modules) { - // Remove forum activity (news one only) from the main menu block to prevent duplicates. - return this.siteHomeProvider.getNewsForum(this.siteHomeId).then((forum) => { - // Search the module that belongs to site news. - for (let i = 0; i < this.mainMenuBlock.modules.length; i++) { - const module = this.mainMenuBlock.modules[i]; - - if (module.modname == 'forum' && module.instance == forum.id) { - this.mainMenuBlock.modules.splice(i, 1); - break; - } - } - }).catch(() => { - // Ignore errors. - }); - } - } - })); - } - // Add log in Moodle. this.courseProvider.logView(this.siteHomeId).catch(() => { // Ignore errors. }); - return Promise.all(promises); + // Get site home blocks. + const canGetBlocks = this.courseProvider.canGetCourseBlocks(), + promise = canGetBlocks ? this.courseProvider.getCourseBlocks(this.siteHomeId) : Promise.reject(null); + + return promise.then((blocks) => { + this.blocks = blocks; + this.hasSupportedBlock = this.blockDelegate.hasSupportedBlock(blocks); + + }).catch((error) => { + if (canGetBlocks) { + this.domUtils.showErrorModal(error); + } + + // Cannot get the blocks, just show site main menu if needed. + if (sections[0] && this.courseHelper.sectionHasContent(sections[0])) { + this.blocks.push({ + name: 'site_main_menu' + }); + this.hasSupportedBlock = true; + } else { + this.blocks = []; + this.hasSupportedBlock = false; + } + }); }).catch((error) => { this.domUtils.showErrorModalDefault(error, 'core.course.couldnotloadsectioncontent', true); }); diff --git a/src/core/sitehome/providers/sitehome.ts b/src/core/sitehome/providers/sitehome.ts index 651df4a89..2d5fa3aaf 100644 --- a/src/core/sitehome/providers/sitehome.ts +++ b/src/core/sitehome/providers/sitehome.ts @@ -49,6 +49,16 @@ export class CoreSiteHomeProvider { }); } + /** + * Invalidate the WS call to get the news forum for the Site Home. + * + * @param {number} siteHomeId Site Home ID. + * @return {Promise} Promise resolved when invalidated. + */ + invalidateNewsForum(siteHomeId: number): Promise { + return this.forumProvider.invalidateForumData(siteHomeId); + } + /** * Returns whether or not the frontpage is available for the current site. *