MOBILE-1874 sitehome: Move site main menu to a block

main
dpalou 2018-10-17 12:08:54 +02:00
parent 2e8424d3a2
commit 0b357db3b5
14 changed files with 369 additions and 84 deletions

View File

@ -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",

View File

@ -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 {}

View File

@ -0,0 +1,7 @@
<core-loading [hideUntil]="loaded" class="core-loading-center">
<ion-item text-wrap *ngIf="block.summary">
<core-format-text [text]="block.summary"></core-format-text>
</ion-item>
<core-course-module *ngFor="let module of block.modules" [module]="module" [courseId]="siteHomeId" [downloadEnabled]="true" [section]="block"></core-course-module>
</core-loading>

View File

@ -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<any>} Resolved when done.
*/
protected invalidateContent(): Promise<any> {
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<any>} Promise resolved when done.
*/
protected fetchContent(): Promise<any> {
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.
});
}
}
});
}
}

View File

@ -0,0 +1,3 @@
{
"pluginname": "Main menu"
}

View File

@ -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<boolean> {
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<CoreBlockHandlerData>} Data or promise resolved with the data.
*/
getDisplayData?(injector: Injector, block: any, contextLevel: string, instanceId: number)
: CoreBlockHandlerData | Promise<CoreBlockHandlerData> {
return {
title: 'addon.block_sitemainmenu.pluginname',
class: 'addon-block-sitemainmenu',
component: AddonBlockSiteMainMenuComponent
};
}
}

View File

@ -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);
}
}

View File

@ -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,

View File

@ -49,14 +49,10 @@ export class CoreBlockBaseComponent implements OnInit {
*/
doRefresh(refresher?: any, done?: () => void, showErrors: boolean = false): Promise<any> {
if (this.loaded) {
return this.invalidateContent().catch(() => {
// Ignore errors.
}).then(() => {
return this.refreshContent(showErrors).finally(() => {
refresher && refresher.complete();
done && done();
});
});
}
return Promise.resolve();

View File

@ -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<any>} Promise resolved when done.
*/
doRefresh(refresher?: any, done?: () => void, showErrors: boolean = false): Promise<any> {
if (this.dynamicComponent) {
return Promise.resolve(this.dynamicComponent.callComponentFunction('doRefresh', [refresher, done, showErrors]));
}
return Promise.resolve();
}
/**
* Invalidate some data.
*
* @return {Promise<any>} Promise resolved when done.
*/
invalidate(): Promise<any> {
if (this.dynamicComponent) {
return Promise.resolve(this.dynamicComponent.callComponentFunction('invalidateContent'));
}
return Promise.resolve();
}
}

View File

@ -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,

View File

@ -22,16 +22,11 @@
</ng-container>
</ng-container>
<!-- Main menu block. -->
<ng-container *ngIf="mainMenuBlock && mainMenuBlock.hasContent">
<ion-item-divider color="light" *ngIf="(section && section.hasContent) || items.length > 0"></ion-item-divider>
<ion-item text-wrap *ngIf="mainMenuBlock.summary">
<core-format-text [text]="mainMenuBlock.summary"></core-format-text>
</ion-item>
<core-course-module *ngFor="let module of mainMenuBlock.modules" [module]="module" [courseId]="siteHomeId" [downloadEnabled]="true" [section]="mainMenuBlock"></core-course-module>
<!-- Site home blocks. -->
<ng-container *ngFor="let block of blocks">
<core-block [block]="block" contextLevel="course" [instanceId]="siteHomeId"></core-block>
</ng-container>
</ion-list>
<core-empty-box *ngIf="!hasContent" icon="qr-scanner" [message]="'core.course.nocontentavailable' | translate"></core-empty-box>
<core-empty-box *ngIf="!hasContent && !hasSupportedBlock" icon="qr-scanner" [message]="'core.course.nocontentavailable' | translate"></core-empty-box>
</core-loading>

View File

@ -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<CoreBlockComponent>;
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<any>} Promise resolved when done.
*/
protected loadContent(): Promise<any> {
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);
});

View File

@ -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<any>} Promise resolved when invalidated.
*/
invalidateNewsForum(siteHomeId: number): Promise<any> {
return this.forumProvider.invalidateForumData(siteHomeId);
}
/**
* Returns whether or not the frontpage is available for the current site.
*