MOBILE-3608 blocks: Add sitehome and dashboard blocks

main
Pau Ferrer Ocaña 2020-12-07 15:31:39 +01:00
parent 7d1d318afc
commit c3372e8076
8 changed files with 264 additions and 19 deletions

View File

@ -19,7 +19,7 @@ import { CoreMainMenuHomeRoutingModule } from '@features/mainmenu/pages/home/hom
import { CoreMainMenuHomeDelegate } from '@features/mainmenu/services/home-delegate';
import { CoreDashboardHomeHandler, CoreDashboardHomeHandlerService } from './services/handlers/dashboard-home';
import { CoreCoursesMyCoursesHomeHandler, CoreCoursesMyCoursesHomeHandlerService } from './services/handlers/my-courses.home';
import { CoreCoursesMyCoursesHomeHandler, CoreCoursesMyCoursesHomeHandlerService } from './services/handlers/my-courses-home';
const mainMenuHomeChildrenRoutes: Routes = [
{

View File

@ -12,8 +12,19 @@
</core-context-menu>
</core-navbar-buttons>
<ion-content>
<!-- @todo -->
<core-empty-box icon="fa-home" [message]="'core.courses.nocourses' | translate">
<div>Dashboard</div>
</core-empty-box>
<ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="refreshDashboard($event)">
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
</ion-refresher>
<core-loading [hideUntil]="loaded">
<ion-list>
<ng-container *ngFor="let block of blocks">
<core-block *ngIf="block.visible" [block]="block" contextLevel="user" [instanceId]="userId"
[extraData]="{'downloadEnabled': downloadEnabled}"></core-block>
</ng-container>
</ion-list>
<core-empty-box *ngIf="blocks.length == 0" icon="fas-th-large" [message]="'core.course.nocontentavailable' | translate">
</core-empty-box>
</core-loading>
</ion-content>

View File

@ -20,6 +20,7 @@ import { TranslateModule } from '@ngx-translate/core';
import { CoreComponentsModule } from '@components/components.module';
import { CoreDirectivesModule } from '@directives/directives.module';
import { CoreBlockComponentsModule } from '@features/block/components/components.module';
import { CoreCoursesDashboardPage } from './dashboard';
@ -38,6 +39,7 @@ const routes: Routes = [
TranslateModule.forChild(),
CoreComponentsModule,
CoreDirectivesModule,
CoreBlockComponentsModule,
],
declarations: [
CoreCoursesDashboardPage,

View File

@ -12,12 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, OnDestroy, OnInit } from '@angular/core';
import { NavController } from '@ionic/angular';
import { Component, OnDestroy, OnInit, QueryList, ViewChildren } from '@angular/core';
import { IonRefresher, NavController } from '@ionic/angular';
import { CoreCourses, CoreCoursesProvider } from '../../services/courses';
import { CoreEventObserver, CoreEvents } from '@singletons/events';
import { CoreSites } from '@services/sites';
import { CoreCoursesDashboard } from '@features/courses/services/dashboard';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreCourseBlock } from '@features/course/services/course';
import { CoreBlockComponent } from '@features/block/components/block/block';
/**
* Page that displays the dashboard page.
@ -29,16 +33,20 @@ import { CoreSites } from '@services/sites';
})
export class CoreCoursesDashboardPage implements OnInit, OnDestroy {
@ViewChildren(CoreBlockComponent) blocksComponents?: QueryList<CoreBlockComponent>;
searchEnabled = false;
downloadEnabled = false;
downloadCourseEnabled = false;
downloadCoursesEnabled = false;
downloadEnabledIcon = 'far-square';
userId?: number;
blocks: Partial<CoreCourseBlock>[] = [];
loaded = false;
protected updateSiteObserver?: CoreEventObserver;
siteName = 'Hello world';
constructor(
protected navCtrl: NavController,
) { }
@ -59,8 +67,82 @@ export class CoreCoursesDashboardPage implements OnInit, OnDestroy {
this.switchDownload(this.downloadEnabled && this.downloadCourseEnabled && this.downloadCoursesEnabled);
}, CoreSites.instance.getCurrentSiteId());
this.loadContent();
}
/**
* Convenience function to fetch the dashboard data.
*
* @return Promise resolved when done.
*/
protected async loadContent(): Promise<void> {
const available = await CoreCoursesDashboard.instance.isAvailable();
if (available) {
this.userId = CoreSites.instance.getCurrentSiteUserId();
try {
this.blocks = await CoreCoursesDashboard.instance.getDashboardBlocks();
} catch (error) {
CoreDomUtils.instance.showErrorModal(error);
// Cannot get the blocks, just show dashboard if needed.
this.loadFallbackBlocks();
}
} else if (!CoreCoursesDashboard.instance.isDisabledInSite()) {
// Not available, but not disabled either. Use fallback.
this.loadFallbackBlocks();
} else {
// Disabled.
this.blocks = [];
}
// this.dashboardEnabled = this.blockDelegate.hasSupportedBlock(this.blocks);
this.loaded = true;
}
/**
* Load fallback blocks to shown before 3.6 when dashboard blocks are not supported.
*/
protected loadFallbackBlocks(): void {
this.blocks = [
{
name: 'myoverview',
visible: true,
},
{
name: 'timeline',
visible: true,
},
];
}
/**
* Refresh the dashboard data.
*
* @param refresher Refresher.
*/
refreshDashboard(refresher: CustomEvent<IonRefresher>): void {
const promises: Promise<void>[] = [];
promises.push(CoreCoursesDashboard.instance.invalidateDashboardBlocks());
// Invalidate the blocks.
this.blocksComponents?.forEach((blockComponent) => {
promises.push(blockComponent.invalidate().catch(() => {
// Ignore errors.
}));
});
Promise.all(promises).finally(() => {
this.loadContent().finally(() => {
refresher?.detail.complete();
});
});
}
/**
* Toggle download enabled.
*/

View File

@ -0,0 +1,140 @@
// (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 { CoreSites } from '@services/sites';
import { CoreSite, CoreSiteWSPreSets } from '@classes/site';
import { CoreCourseBlock } from '@features/course/services/course';
import { CoreStatusWithWarningsWSResponse } from '@services/ws';
import { makeSingleton } from '@singletons';
const ROOT_CACHE_KEY = 'CoreCoursesDashboard:';
/**
* Service that provides some features regarding course overview.
*/
@Injectable({ providedIn: 'root' })
export class CoreCoursesDashboardProvider {
/**
* Get cache key for dashboard blocks WS calls.
*
* @param userId User ID. Default, 0 means current user.
* @return Cache key.
*/
protected getDashboardBlocksCacheKey(userId: number = 0): string {
return ROOT_CACHE_KEY + 'blocks:' + userId;
}
/**
* Get dashboard blocks.
*
* @param userId User ID. Default, current user.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with the list of blocks.
* @since 3.6
*/
async getDashboardBlocks(userId?: number, siteId?: string): Promise<CoreCourseBlock[]> {
const site = await CoreSites.instance.getSite(siteId);
const params: CoreBlockGetDashboardBlocksWSParams = {
returncontents: true,
};
const preSets: CoreSiteWSPreSets = {
cacheKey: this.getDashboardBlocksCacheKey(userId),
updateFrequency: CoreSite.FREQUENCY_RARELY,
};
if (userId) {
params.userid = userId;
}
const result = await site.read<CoreBlockGetDashboardBlocksWSResponse>('core_block_get_dashboard_blocks', params, preSets);
return result.blocks || [];
}
/**
* Invalidates dashboard blocks WS call.
*
* @param userId User ID. Default, current user.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when the data is invalidated.
*/
async invalidateDashboardBlocks(userId?: number, siteId?: string): Promise<void> {
const site = await CoreSites.instance.getSite(siteId);
return await site.invalidateWsCacheForKey(this.getDashboardBlocksCacheKey(userId));
}
/**
* Returns whether or not block based Dashboard is available for a certain site.
*
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with true if available, resolved with false or rejected otherwise.
* @since 3.6
*/
async isAvailable(siteId?: string): Promise<boolean> {
const site = await CoreSites.instance.getSite(siteId);
// First check if it's disabled.
if (this.isDisabledInSite(site)) {
return false;
}
return site.wsAvailable('core_block_get_dashboard_blocks');
}
/**
* Check if Site Home is 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 isDisabled(siteId?: string): Promise<boolean> {
const site = await CoreSites.instance.getSite(siteId);
return this.isDisabledInSite(site);
}
/**
* Check if Site Home is disabled in a certain site.
*
* @param site Site. If not defined, use current site.
* @return Whether it's disabled.
*/
isDisabledInSite(site?: CoreSite): boolean {
site = site || CoreSites.instance.getCurrentSite();
return !!site?.isFeatureDisabled('CoreMainMenuDelegate_CoreCoursesDashboard');
}
}
export class CoreCoursesDashboard extends makeSingleton(CoreCoursesDashboardProvider) {}
/**
* Params of core_block_get_dashboard_blocks WS.
*/
type CoreBlockGetDashboardBlocksWSParams = {
userid?: number; // User id (optional), default is current user.
returncontents?: boolean; // Whether to return the block contents.
};
/**
* Data returned by core_block_get_dashboard_blocks WS.
*/
type CoreBlockGetDashboardBlocksWSResponse = {
blocks: CoreCourseBlock[]; // List of blocks in the course.
warnings?: CoreStatusWithWarningsWSResponse[];
};

View File

@ -13,8 +13,10 @@
// limitations under the License.
import { Injectable } from '@angular/core';
import { CoreBlockDelegate } from '@features/block/services/block-delegate';
import { CoreMainMenuHomeHandler, CoreMainMenuHomeHandlerToDisplay } from '@features/mainmenu/services/home-delegate';
import { makeSingleton } from '@singletons';
import { CoreCoursesDashboard } from '../dashboard';
/**
* Handler to add dashboard into home page.
@ -42,10 +44,10 @@ export class CoreDashboardHomeHandlerService implements CoreMainMenuHomeHandler
* @param siteId Site ID. If not defined, current site.
* @return Whether or not the handler is enabled on a site level.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async isEnabledForSite(siteId?: string): Promise<boolean> {
// @todo return this.blockDelegate.hasSupportedBlock(this.blocks);
return true;
const blocks = await CoreCoursesDashboard.instance.getDashboardBlocks(undefined, siteId);
return CoreBlockDelegate.instance.hasSupportedBlock(blocks);
}
/**

View File

@ -13,8 +13,11 @@
// limitations under the License.
import { Injectable } from '@angular/core';
import { CoreBlockDelegate } from '@features/block/services/block-delegate';
import { CoreMainMenuHomeHandler, CoreMainMenuHomeHandlerToDisplay } from '@features/mainmenu/services/home-delegate';
import { CoreSiteHome } from '@features/sitehome/services/sitehome';
import { makeSingleton } from '@singletons';
import { CoreCoursesDashboard } from '../dashboard';
/**
* Handler to add my courses into home page.
@ -42,10 +45,10 @@ export class CoreCoursesMyCoursesHomeHandlerService implements CoreMainMenuHomeH
* @param siteId Site ID. If not defined, current site.
* @return Whether or not the handler is enabled on a site level.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async isEnabledForSite(siteId?: string): Promise<boolean> {
// @todo return !this.blockDelegate.hasSupportedBlock(this.blocks) && !CoreSiteHome.instance.isAvailable(siteId);
return true;
const blocks = await CoreCoursesDashboard.instance.getDashboardBlocks(undefined, siteId);
return !CoreBlockDelegate.instance.hasSupportedBlock(blocks)&& !CoreSiteHome.instance.isAvailable(siteId);
}
/**

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { IonRefresher, NavController } from '@ionic/angular';
@ -24,6 +24,7 @@ import { CoreSiteHome } from '@features/sitehome/services/sitehome';
import { CoreCourses, CoreCoursesProvider } from '@features//courses/services/courses';
import { CoreEventObserver, CoreEvents } from '@singletons/events';
import { CoreCourseHelper } from '@features/course/services/course-helper';
import { CoreBlockCourseBlocksComponent } from '@features/block/components/course-blocks/course-blocks';
/**
* Page that displays site home index.
@ -34,7 +35,7 @@ import { CoreCourseHelper } from '@features/course/services/course-helper';
})
export class CoreSiteHomeIndexPage implements OnInit, OnDestroy {
// @todo @ViewChild(CoreBlockCourseBlocksComponent) courseBlocksComponent: CoreBlockCourseBlocksComponent;
@ViewChild(CoreBlockCourseBlocksComponent) courseBlocksComponent?: CoreBlockCourseBlocksComponent;
dataLoaded = false;
section?: CoreCourseSection & {
@ -158,13 +159,17 @@ export class CoreSiteHomeIndexPage implements OnInit, OnDestroy {
// @todo promises.push(this.prefetchDelegate.invalidateModules(this.section.modules, this.siteHomeId));
}
// @todo promises.push(this.courseBlocksComponent.invalidateBlocks());
if (this.courseBlocksComponent) {
promises.push(this.courseBlocksComponent.invalidateBlocks());
}
Promise.all(promises).finally(async () => {
const p2: Promise<unknown>[] = [];
p2.push(this.loadContent());
// @todo p2.push(this.courseBlocksComponent.loadContent());
if (this.courseBlocksComponent) {
p2.push(this.courseBlocksComponent.loadContent());
}
await Promise.all(p2).finally(() => {
refresher?.detail.complete();