From 9b414581049a3c6587894a5a5426eae7c21b4ce4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Mon, 23 Nov 2020 10:22:47 +0100 Subject: [PATCH] MOBILE-3594 sitehome: Add sitehome tab to home page --- src/core/features/features.module.ts | 2 + .../search-box/core-search-box.html | 4 +- .../features/settings/pages/site/site.html | 4 +- src/core/features/sitehome/lang/en.json | 4 + .../features/sitehome/pages/index/index.html | 85 ++++++++ .../sitehome/pages/index/index.page.module.ts | 47 +++++ .../sitehome/pages/index/index.page.ts | 155 ++++++++++++++ .../sitehome/services/handlers/index.link.ts | 75 +++++++ .../services/handlers/sitehome.home.ts | 66 ++++++ .../features/sitehome/services/sitehome.ts | 196 ++++++++++++++++++ .../features/sitehome/sitehome-init.module.ts | 53 +++++ 11 files changed, 687 insertions(+), 4 deletions(-) create mode 100644 src/core/features/sitehome/lang/en.json create mode 100644 src/core/features/sitehome/pages/index/index.html create mode 100644 src/core/features/sitehome/pages/index/index.page.module.ts create mode 100644 src/core/features/sitehome/pages/index/index.page.ts create mode 100644 src/core/features/sitehome/services/handlers/index.link.ts create mode 100644 src/core/features/sitehome/services/handlers/sitehome.home.ts create mode 100644 src/core/features/sitehome/services/sitehome.ts create mode 100644 src/core/features/sitehome/sitehome-init.module.ts diff --git a/src/core/features/features.module.ts b/src/core/features/features.module.ts index e81e4d728..29874d84d 100644 --- a/src/core/features/features.module.ts +++ b/src/core/features/features.module.ts @@ -20,6 +20,7 @@ import { CoreEmulatorModule } from './emulator/emulator.module'; import { CoreFileUploaderInitModule } from './fileuploader/fileuploader-init.module'; import { CoreLoginModule } from './login/login.module'; import { CoreSettingsInitModule } from './settings/settings-init.module'; +import { CoreSiteHomeInitModule } from './sitehome/sitehome-init.module'; @NgModule({ imports: [ @@ -29,6 +30,7 @@ import { CoreSettingsInitModule } from './settings/settings-init.module'; CoreCoursesModule, CoreSettingsInitModule, CoreFileUploaderInitModule, + CoreSiteHomeInitModule, ], }) export class CoreFeaturesModule {} diff --git a/src/core/features/search/components/search-box/core-search-box.html b/src/core/features/search/components/search-box/core-search-box.html index 6989a6039..5d49f4c0c 100644 --- a/src/core/features/search/components/search-box/core-search-box.html +++ b/src/core/features/search/components/search-box/core-search-box.html @@ -17,8 +17,8 @@ - + {{item.searchedtext}} diff --git a/src/core/features/settings/pages/site/site.html b/src/core/features/settings/pages/site/site.html index b10ce9c28..b20ac3539 100644 --- a/src/core/features/settings/pages/site/site.html +++ b/src/core/features/settings/pages/site/site.html @@ -28,7 +28,7 @@ + [class.core-split-item-selected]="'CoreSharedFilesListPage' == selectedPage" detail>

{{ 'core.sharedfiles.sharedfiles' | translate }}

@@ -37,7 +37,7 @@
diff --git a/src/core/features/sitehome/lang/en.json b/src/core/features/sitehome/lang/en.json new file mode 100644 index 000000000..acf7f742f --- /dev/null +++ b/src/core/features/sitehome/lang/en.json @@ -0,0 +1,4 @@ +{ + "sitehome": "Site home", + "sitenews": "Site announcements" +} diff --git a/src/core/features/sitehome/pages/index/index.html b/src/core/features/sitehome/pages/index/index.html new file mode 100644 index 000000000..f4492cf3b --- /dev/null +++ b/src/core/features/sitehome/pages/index/index.html @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

{{ 'core.courses.availablecourses' | translate}}

+
+
+
+ + + + News (TODO) + + + + + + + + +

{{ 'core.courses.categories' | translate}}

+
+
+
+ + + + + +

{{ 'core.courses.mycourses' | translate}}

+
+
+ + + + +

{{ 'core.courses.searchcourses' | translate}}

+
+
diff --git a/src/core/features/sitehome/pages/index/index.page.module.ts b/src/core/features/sitehome/pages/index/index.page.module.ts new file mode 100644 index 000000000..59e2a1986 --- /dev/null +++ b/src/core/features/sitehome/pages/index/index.page.module.ts @@ -0,0 +1,47 @@ +// (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 { RouterModule, Routes } from '@angular/router'; +import { IonicModule } from '@ionic/angular'; +import { TranslateModule } from '@ngx-translate/core'; + +import { CoreDirectivesModule } from '@directives/directives.module'; +import { CoreComponentsModule } from '@components/components.module'; + +import { CoreSiteHomeIndexPage } from './index.page'; + +const routes: Routes = [ + { + path: '', + component: CoreSiteHomeIndexPage, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + CommonModule, + IonicModule, + TranslateModule.forChild(), + CoreDirectivesModule, + CoreComponentsModule, + ], + declarations: [ + CoreSiteHomeIndexPage, + ], + exports: [RouterModule], +}) +export class CoreSiteHomeIndexPageModule {} diff --git a/src/core/features/sitehome/pages/index/index.page.ts b/src/core/features/sitehome/pages/index/index.page.ts new file mode 100644 index 000000000..482d234d4 --- /dev/null +++ b/src/core/features/sitehome/pages/index/index.page.ts @@ -0,0 +1,155 @@ +// (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, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { IonRefresher } from '@ionic/angular'; + +import { CoreSite, CoreSiteConfig } from '@classes/site'; +import { CoreCourse, CoreCourseSection } from '@features/course/services/course'; +import { CoreDomUtils } from '@services/utils/dom'; +import { CoreSites } from '@services/sites'; +import { CoreSiteHome } from '@features/sitehome/services/sitehome'; +// import { CoreCourseHelperProvider } from '@features/course/services/helper'; + +/** + * Page that displays site home index. + */ +@Component({ + selector: 'page-core-sitehome-index', + templateUrl: 'index.html', +}) +export class CoreSiteHomeIndexPage implements OnInit { + + // @todo @Input() downloadEnabled: boolean; + // @todo @ViewChild(CoreBlockCourseBlocksComponent) courseBlocksComponent: CoreBlockCourseBlocksComponent; + + dataLoaded = false; + section?: CoreCourseSection & { + hasContent?: boolean; + }; + + hasContent = false; + items: string[] = []; + siteHomeId?: number; + currentSite?: CoreSite; + + constructor( + protected route: ActivatedRoute, + // @todo private prefetchDelegate: CoreCourseModulePrefetchDelegate, + ) { + + } + + /** + * Page being initialized. + */ + ngOnInit(): void { + const navParams = this.route.snapshot.queryParams; + + this.currentSite = CoreSites.instance.getCurrentSite()!; + this.siteHomeId = this.currentSite.getSiteHomeId(); + + const module = navParams['module']; + if (module) { + // @todo const modParams = navParams.get('modParams'); + // courseHelper.openModule(module, this.siteHomeId, undefined, modParams); + } + + this.loadContent().finally(() => { + this.dataLoaded = true; + }); + } + + /** + * Convenience function to fetch the data. + * + * @return Promise resolved when done. + */ + protected async loadContent(): Promise { + this.hasContent = false; + + const config = this.currentSite!.getStoredConfig() || { numsections: 1, frontpageloggedin: undefined }; + + this.items = await CoreSiteHome.instance.getFrontPageItems(config.frontpageloggedin); + this.hasContent = this.items.length > 0; + + try { + const sections = await CoreCourse.instance.getSections(this.siteHomeId!, false, true); + + // Check "Include a topic section" setting from numsections. + this.section = config.numsections ? sections.find((section) => section.section == 1) : undefined; + if (this.section) { + this.section.hasContent = false; + /* @todo this.section.hasContent = this.courseHelper.sectionHasContent(this.section); + this.hasContent = this.courseHelper.addHandlerDataForModules( + [this.section], + this.siteHomeId, + undefined, + undefined, + true, + ) || this.hasContent;*/ + } + + // Add log in Moodle. + CoreCourse.instance.logView( + this.siteHomeId!, + undefined, + undefined, + this.currentSite!.getInfo()?.sitename, + ).catch(() => { + // Ignore errors. + }); + } catch (error) { + CoreDomUtils.instance.showErrorModalDefault(error, 'core.course.couldnotloadsectioncontent', true); + } + } + + /** + * Refresh the data. + * + * @param refresher Refresher. + */ + doRefresh(refresher?: CustomEvent): void { + const promises: Promise[] = []; + + promises.push(CoreCourse.instance.invalidateSections(this.siteHomeId!)); + promises.push(this.currentSite!.invalidateConfig().then(async () => { + // Config invalidated, fetch it again. + const config: CoreSiteConfig = await this.currentSite!.getConfig(); + this.currentSite!.setConfig(config); + + return; + })); + + if (this.section && this.section.modules) { + // Invalidate modules prefetch data. + // @todo promises.push(this.prefetchDelegate.invalidateModules(this.section.modules, this.siteHomeId)); + } + + // @todo promises.push(this.courseBlocksComponent.invalidateBlocks()); + + Promise.all(promises).finally(async () => { + const p2: Promise[] = []; + + p2.push(this.loadContent()); + // @todo p2.push(this.courseBlocksComponent.loadContent()); + + await Promise.all(p2).finally(() => { + refresher?.detail.complete(); + }); + }); + } + +} diff --git a/src/core/features/sitehome/services/handlers/index.link.ts b/src/core/features/sitehome/services/handlers/index.link.ts new file mode 100644 index 000000000..aa97435c4 --- /dev/null +++ b/src/core/features/sitehome/services/handlers/index.link.ts @@ -0,0 +1,75 @@ +// (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 { Params } from '@angular/router'; +import { CoreSites } from '@services/sites'; +import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler'; +import { CoreContentLinksHelper } from '@features/contentlinks/services/contentlinks.helper'; +import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks.delegate'; +import { CoreSiteHome } from '../sitehome'; + +/** + * Handler to treat links to site home index. + */ +Injectable(); +export class CoreSiteHomeIndexLinkHandler extends CoreContentLinksHandlerBase { + + name = 'CoreSiteHomeIndexLinkHandler'; + featureName = 'CoreMainMenuDelegate_CoreSiteHome'; + pattern = /\/course\/view\.php.*([?&]id=\d+)/; + + /** + * Get the list of actions for a link (url). + * + * @param siteIds List of sites the URL belongs to. + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param courseId Course ID related to the URL. Optional but recommended. + * @return List of (or promise resolved with list of) actions. + */ + getActions(): CoreContentLinksAction[] | Promise { + return [{ + action: (siteId: string): void => { + CoreContentLinksHelper.instance.goInSite('sitehome', [], siteId); + }, + }]; + } + + /** + * Check if the handler is enabled for a certain site (site + user) and a URL. + * If not defined, defaults to true. + * + * @param siteId The site ID. + * @param url The URL to treat. + * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param courseId Course ID related to the URL. Optional but recommended. + * @return Whether the handler is enabled for the URL and site. + */ + async isEnabled(siteId: string, url: string, params: Params, courseId?: number): Promise { + courseId = parseInt(params.id, 10); + if (!courseId) { + return false; + } + + const site = await CoreSites.instance.getSite(siteId); + if (courseId != site.getSiteHomeId()) { + // The course is not site home. + return false; + } + + return CoreSiteHome.instance.isAvailable(siteId).then(() => true).catch(() => false); + } + +} diff --git a/src/core/features/sitehome/services/handlers/sitehome.home.ts b/src/core/features/sitehome/services/handlers/sitehome.home.ts new file mode 100644 index 000000000..ae53ae5a1 --- /dev/null +++ b/src/core/features/sitehome/services/handlers/sitehome.home.ts @@ -0,0 +1,66 @@ +// (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 { CoreHomeHandler, CoreHomeHandlerToDisplay } from '@features/mainmenu/services/home.delegate'; +import { CoreSiteHome } from '../sitehome'; + +/** + * Handler to add Home into main menu. + */ +Injectable(); +export class CoreSiteHomeHomeHandler implements CoreHomeHandler { + + name = 'CoreSiteHomeDashboard'; + priority = 1200; + + /** + * Check if the handler is enabled on a site level. + * + * @return Whether or not the handler is enabled on a site level. + */ + isEnabled(): Promise { + return this.isEnabledForSite(); + } + + /** + * Check if the handler is enabled on a certain site. + * + * @param siteId Site ID. If not defined, current site. + * @return Whether or not the handler is enabled on a site level. + */ + async isEnabledForSite(siteId?: string): Promise { + return CoreSiteHome.instance.isAvailable(siteId); + } + + /** + * Returns the data needed to render the handler. + * + * @return Data needed to render the handler. + */ + getDisplayData(): CoreHomeHandlerToDisplay { + const site = CoreSites.instance.getCurrentSite(); + const displaySiteHome = site?.getInfo() && site?.getInfo()?.userhomepage === 0; + + return { + title: 'core.sitehome.sitehome', + page: 'sitehome', + class: 'core-sitehome-dashboard-handler', + icon: 'fas-home', + selectPriority: displaySiteHome ? 1100 : 900, + }; + } + +} diff --git a/src/core/features/sitehome/services/sitehome.ts b/src/core/features/sitehome/services/sitehome.ts new file mode 100644 index 000000000..6714dfd6e --- /dev/null +++ b/src/core/features/sitehome/services/sitehome.ts @@ -0,0 +1,196 @@ +// (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 { makeSingleton } from '@singletons/core.singletons'; +import { CoreCourse, CoreCourseSection } from '../../course/services/course'; +import { CoreCourses } from '../../courses/services/courses'; + +/** + * Items with index 1 and 3 were removed on 2.5 and not being supported in the app. + */ +export enum FrontPageItemNames { + NEWS_ITEMS = 0, + LIST_OF_CATEGORIES = 2, + COMBO_LIST = 3, + ENROLLED_COURSES = 5, + LIST_OF_COURSE = 6, + COURSE_SEARCH_BOX = 7, +} + +/** + * Service that provides some features regarding site home. + */ +@Injectable({ + providedIn: 'root', +}) +export class CoreSiteHomeProvider { + + /** + * Get the news forum for the Site Home. + * + * @param siteHomeId Site Home ID. + * @return Promise resolved with the forum if found, rejected otherwise. + */ + getNewsForum(): void { + // @todo params and logic. + } + + /** + * Invalidate the WS call to get the news forum for the Site Home. + * + * @param siteHomeId Site Home ID. + * @return Promise resolved when invalidated. + */ + invalidateNewsForum(): void { + // @todo params and logic. + } + + /** + * Returns whether or not the frontpage is available for the current site. + * + * @param siteId The site ID. If not defined, current site. + * @return Promise resolved with boolean: whether it's available. + */ + async isAvailable(siteId?: string): Promise { + try { + const site = await CoreSites.instance.getSite(siteId); + + // First check if it's disabled. + if (this.isDisabledInSite(site)) { + return false; + } + + // Use a WS call to check if there's content in the site home. + const siteHomeId = site.getSiteHomeId(); + const preSets: CoreSiteWSPreSets = { emergencyCache: false }; + + try { + const sections: CoreCourseSection[] = + await CoreCourse.instance.getSections(siteHomeId, false, true, preSets, site.id); + + if (!sections || !sections.length) { + throw Error('No sections found'); + } + + const hasContent = sections.some((section) => section.summary || (section.modules && section.modules.length)); + + if (hasContent) { + // There's a section with content. + return true; + } + } catch { + // Ignore errors. + } + + const config = site.getStoredConfig(); + if (config && config.frontpageloggedin) { + const items = await this.getFrontPageItems(config.frontpageloggedin); + + // There are items to show. + return items.length > 0; + } + } catch { + // Ignore errors. + } + + return false; + } + + /** + * 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 { + 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_CoreSiteHome'); + } + + /** + * Get the nams of the valid frontpage items. + * + * @param frontpageItemIds CSV string with indexes of site home components. + * @return Valid names for each item. + */ + async getFrontPageItems(frontpageItemIds?: string): Promise { + if (!frontpageItemIds) { + return []; + } + + const items = frontpageItemIds.split(','); + + const filteredItems: string[] = []; + + for (const item of items) { + let itemNumber = parseInt(item, 10); + + let add = false; + switch (itemNumber) { + case FrontPageItemNames['NEWS_ITEMS']: + // @todo + add = true; + break; + case FrontPageItemNames['LIST_OF_CATEGORIES']: + case FrontPageItemNames['COMBO_LIST']: + case FrontPageItemNames['LIST_OF_COURSE']: + add = CoreCourses.instance.isGetCoursesByFieldAvailable(); + if (add && itemNumber == FrontPageItemNames['COMBO_LIST']) { + itemNumber = FrontPageItemNames['LIST_OF_CATEGORIES']; + } + break; + case FrontPageItemNames['ENROLLED_COURSES']: + if (!CoreCourses.instance.isMyCoursesDisabledInSite()) { + const courses = await CoreCourses.instance.getUserCourses(); + + add = courses.length > 0; + } + break; + case FrontPageItemNames['COURSE_SEARCH_BOX']: + add = !CoreCourses.instance.isSearchCoursesDisabledInSite(); + break; + default: + break; + } + + // Do not add an item twice. + if (add && filteredItems.indexOf(FrontPageItemNames[itemNumber]) < 0) { + filteredItems.push(FrontPageItemNames[itemNumber]); + } + } + + return filteredItems; + } + +} + +export class CoreSiteHome extends makeSingleton(CoreSiteHomeProvider) {} + diff --git a/src/core/features/sitehome/sitehome-init.module.ts b/src/core/features/sitehome/sitehome-init.module.ts new file mode 100644 index 000000000..7739e17aa --- /dev/null +++ b/src/core/features/sitehome/sitehome-init.module.ts @@ -0,0 +1,53 @@ +// (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 { Routes } from '@angular/router'; + +import { CoreSiteHomeIndexLinkHandler } from './services/handlers/index.link'; +import { CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks.delegate'; +import { CoreSiteHomeHomeHandler } from './services/handlers/sitehome.home'; +import { CoreHomeDelegate } from '@features/mainmenu/services/home.delegate'; +import { CoreHomeRoutingModule } from '@features/mainmenu/pages/home/home-routing.module'; + +const routes: Routes = [ + { + path: 'sitehome', + loadChildren: () => + import('@features/sitehome/pages/index/index.page.module').then(m => m.CoreSiteHomeIndexPageModule), + }, +]; + +@NgModule({ + imports: [CoreHomeRoutingModule.forChild(routes)], + exports: [CoreHomeRoutingModule], + providers: [ + CoreSiteHomeIndexLinkHandler, + CoreSiteHomeHomeHandler, + ], +}) +export class CoreSiteHomeInitModule { + + constructor( + contentLinksDelegate: CoreContentLinksDelegate, + homeDelegate: CoreHomeDelegate, + siteHomeIndexLinkHandler: CoreSiteHomeIndexLinkHandler, + siteHomeDashboardHandler: CoreSiteHomeHomeHandler, + ) { + contentLinksDelegate.registerHandler(siteHomeIndexLinkHandler); + homeDelegate.registerHandler(siteHomeDashboardHandler); + + } + +}