From a4046f5678c6cde80108e3ec6dd545adf8a10da9 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 3 Mar 2021 10:03:22 +0100 Subject: [PATCH] MOBILE-3664 site plugins: Implement pages --- .../pages/edit-event/edit-event.page.ts | 9 +- .../components/submission/submission.ts | 18 +-- src/addons/mod/assign/pages/edit/edit.ts | 9 +- .../submission-review/submission-review.ts | 5 +- .../mod/quiz/pages/player/player.page.ts | 9 +- src/core/features/features.module.ts | 2 + .../classes/handlers/course-option-handler.ts | 21 +-- .../classes/handlers/main-menu-handler.ts | 4 +- .../handlers/message-output-handler.ts | 4 +- .../classes/handlers/module-handler.ts | 14 +- .../classes/handlers/settings-handler.ts | 4 +- .../classes/handlers/user-handler.ts | 30 +++-- .../components/components.module.ts | 3 - .../only-title-block/only-title-block.ts | 33 +++-- .../plugin-content/plugin-content.ts | 31 +++-- .../core-siteplugins-course-option.html | 3 +- .../course-option/course-option.module.ts | 45 +++++++ .../course-option/course-option.ts | 72 +++++++++-- .../pages/module-index/module-index.html | 19 +++ .../pages/module-index/module-index.module.ts | 45 +++++++ .../pages/module-index/module-index.ts | 109 ++++++++++++++++ .../pages/plugin-page/plugin-page.html | 21 +++ .../pages/plugin-page/plugin-page.module.ts | 45 +++++++ .../pages/plugin-page/plugin-page.ts | 120 ++++++++++++++++++ .../siteplugins/siteplugins-lazy.module.ts | 32 +++++ .../siteplugins/siteplugins.module.ts | 54 ++++++++ src/core/pipes/pipes.module.ts | 3 + src/core/pipes/to-locale-string.ts | 66 ++++++++++ src/core/services/utils/utils.ts | 9 -- 29 files changed, 736 insertions(+), 103 deletions(-) rename src/core/features/siteplugins/{components => pages}/course-option/core-siteplugins-course-option.html (71%) create mode 100644 src/core/features/siteplugins/pages/course-option/course-option.module.ts rename src/core/features/siteplugins/{components => pages}/course-option/course-option.ts (51%) create mode 100644 src/core/features/siteplugins/pages/module-index/module-index.html create mode 100644 src/core/features/siteplugins/pages/module-index/module-index.module.ts create mode 100644 src/core/features/siteplugins/pages/module-index/module-index.ts create mode 100644 src/core/features/siteplugins/pages/plugin-page/plugin-page.html create mode 100644 src/core/features/siteplugins/pages/plugin-page/plugin-page.module.ts create mode 100644 src/core/features/siteplugins/pages/plugin-page/plugin-page.ts create mode 100644 src/core/features/siteplugins/siteplugins-lazy.module.ts create mode 100644 src/core/features/siteplugins/siteplugins.module.ts create mode 100644 src/core/pipes/to-locale-string.ts diff --git a/src/addons/calendar/pages/edit-event/edit-event.page.ts b/src/addons/calendar/pages/edit-event/edit-event.page.ts index 2321e24eb..d86fabc51 100644 --- a/src/addons/calendar/pages/edit-event/edit-event.page.ts +++ b/src/addons/calendar/pages/edit-event/edit-event.page.ts @@ -43,6 +43,7 @@ import { CoreFilterHelper } from '@features/filter/services/filter-helper'; import { AddonCalendarOfflineEventDBRecord } from '../../services/database/calendar-offline'; import { CoreError } from '@classes/errors/error'; import { CoreNavigator } from '@services/navigator'; +import { CanLeave } from '@guards/can-leave'; /** * Page that displays a form to create/edit an event. @@ -52,7 +53,7 @@ import { CoreNavigator } from '@services/navigator'; templateUrl: 'edit-event.html', styleUrls: ['edit-event.scss'], }) -export class AddonCalendarEditEventPage implements OnInit, OnDestroy { +export class AddonCalendarEditEventPage implements OnInit, OnDestroy, CanLeave { @ViewChild(CoreEditorRichTextEditorComponent) descriptionEditor!: CoreEditorRichTextEditorComponent; @ViewChild('editEventForm') formElement!: ElementRef; @@ -605,15 +606,17 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy { /** * Check if we can leave the page or not. * - * @return Resolved if we can leave it, rejected if not. + * @return Resolved with true if we can leave it, rejected if not. */ - async ionViewCanLeave(): Promise { + async canLeave(): Promise { if (AddonCalendarHelper.hasEventDataChanged(this.form.value, this.originalData)) { // Show confirmation if some data has been modified. await CoreDomUtils.showConfirm(Translate.instant('core.confirmcanceledit')); } CoreDomUtils.triggerFormCancelledEvent(this.formElement, this.currentSite.getId()); + + return true; } /** diff --git a/src/addons/mod/assign/components/submission/submission.ts b/src/addons/mod/assign/components/submission/submission.ts index 0e3afc1ee..bd98df951 100644 --- a/src/addons/mod/assign/components/submission/submission.ts +++ b/src/addons/mod/assign/components/submission/submission.ts @@ -56,6 +56,7 @@ import { CoreGroups } from '@services/groups'; import { CoreSync } from '@services/sync'; import { AddonModAssignSubmissionPluginComponent } from '../submission-plugin/submission-plugin'; import { AddonModAssignModuleHandlerService } from '../../services/handlers/module'; +import { CanLeave } from '@guards/can-leave'; /** * Component that displays an assignment submission. @@ -65,7 +66,7 @@ import { AddonModAssignModuleHandlerService } from '../../services/handlers/modu templateUrl: 'addon-mod-assign-submission.html', styleUrls: ['submission.scss'], }) -export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy { +export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, CanLeave { @ViewChild(CoreTabsComponent) tabs!: CoreTabsComponent; @ViewChildren(AddonModAssignSubmissionPluginComponent) submissionComponents!: @@ -252,21 +253,20 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy { /** * Check if the user can leave the view. If there are changes to be saved, it will ask for confirm. * - * @return Promise resolved if can leave the view, rejected otherwise. + * @return Promise resolved with true if can leave the view, rejected otherwise. */ - async canLeave(): Promise { + async canLeave(): Promise { // Check if there is data to save. const modified = await this.hasDataToSave(); if (modified) { // Modified, confirm user wants to go back. - try { - await CoreDomUtils.showConfirm(Translate.instant('core.confirmcanceledit')); - await this.discardDrafts(); - } catch { - // Cancelled by the user. - } + await CoreDomUtils.showConfirm(Translate.instant('core.confirmcanceledit')); + + await this.discardDrafts(); } + + return true; } /** diff --git a/src/addons/mod/assign/pages/edit/edit.ts b/src/addons/mod/assign/pages/edit/edit.ts index 6eb7739ac..31f2d9a7b 100644 --- a/src/addons/mod/assign/pages/edit/edit.ts +++ b/src/addons/mod/assign/pages/edit/edit.ts @@ -16,6 +16,7 @@ import { Component, OnInit, OnDestroy, ViewChild, ElementRef } from '@angular/co import { ActivatedRoute } from '@angular/router'; import { CoreError } from '@classes/errors/error'; import { CoreFileUploaderHelper } from '@features/fileuploader/services/fileuploader-helper'; +import { CanLeave } from '@guards/can-leave'; import { CoreNavigator } from '@services/navigator'; import { CoreSites, CoreSitesReadingStrategy } from '@services/sites'; import { CoreSync } from '@services/sync'; @@ -44,7 +45,7 @@ import { AddonModAssignSync } from '../../services/assign-sync'; selector: 'page-addon-mod-assign-edit', templateUrl: 'edit.html', }) -export class AddonModAssignEditPage implements OnInit, OnDestroy { +export class AddonModAssignEditPage implements OnInit, OnDestroy, CanLeave { @ViewChild('editSubmissionForm') formElement?: ElementRef; @@ -92,9 +93,9 @@ export class AddonModAssignEditPage implements OnInit, OnDestroy { * * @return Resolved if we can leave it, rejected if not. */ - async ionViewCanLeave(): Promise { + async canLeave(): Promise { if (this.forceLeave) { - return; + return true; } // Check if data has changed. @@ -107,6 +108,8 @@ export class AddonModAssignEditPage implements OnInit, OnDestroy { AddonModAssignHelper.clearSubmissionPluginTmpData(this.assign!, this.userSubmission, this.getInputData()); CoreDomUtils.triggerFormCancelledEvent(this.formElement, CoreSites.getCurrentSiteId()); + + return true; } /** diff --git a/src/addons/mod/assign/pages/submission-review/submission-review.ts b/src/addons/mod/assign/pages/submission-review/submission-review.ts index 8446a9c45..c1feef2cd 100644 --- a/src/addons/mod/assign/pages/submission-review/submission-review.ts +++ b/src/addons/mod/assign/pages/submission-review/submission-review.ts @@ -15,6 +15,7 @@ import { Component, OnInit, ViewChild } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { CoreCourse } from '@features/course/services/course'; +import { CanLeave } from '@guards/can-leave'; import { IonRefresher } from '@ionic/angular'; import { CoreNavigator } from '@services/navigator'; import { CoreScreen } from '@services/screen'; @@ -29,7 +30,7 @@ import { AddonModAssign, AddonModAssignAssign } from '../../services/assign'; selector: 'page-addon-mod-assign-submission-review', templateUrl: 'submission-review.html', }) -export class AddonModAssignSubmissionReviewPage implements OnInit { +export class AddonModAssignSubmissionReviewPage implements OnInit, CanLeave { @ViewChild(AddonModAssignSubmissionComponent) submissionComponent?: AddonModAssignSubmissionComponent; @@ -71,7 +72,7 @@ export class AddonModAssignSubmissionReviewPage implements OnInit { * * @return Resolved if we can leave it, rejected if not. */ - ionViewCanLeave(): boolean | Promise { + async canLeave(): Promise { if (!this.submissionComponent || this.forceLeave) { return true; } diff --git a/src/addons/mod/quiz/pages/player/player.page.ts b/src/addons/mod/quiz/pages/player/player.page.ts index fc5029f7c..1e88da13a 100644 --- a/src/addons/mod/quiz/pages/player/player.page.ts +++ b/src/addons/mod/quiz/pages/player/player.page.ts @@ -44,6 +44,7 @@ import { } from '../../services/quiz'; import { AddonModQuizAttempt, AddonModQuizHelper } from '../../services/quiz-helper'; import { AddonModQuizSync } from '../../services/quiz-sync'; +import { CanLeave } from '@guards/can-leave'; /** * Page that allows attempting a quiz. @@ -53,7 +54,7 @@ import { AddonModQuizSync } from '../../services/quiz-sync'; templateUrl: 'player.html', styleUrls: ['player.scss'], }) -export class AddonModQuizPlayerPage implements OnInit, OnDestroy { +export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave { @ViewChild(IonContent) content?: IonContent; @ViewChildren(CoreQuestionComponent) questionComponents?: QueryList; @@ -144,9 +145,9 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy { * * @return Resolved if we can leave it, rejected if not. */ - async ionViewCanLeave(): Promise { + async canLeave(): Promise { if (this.forceLeave || this.quizAborted || !this.questions.length || this.showSummary) { - return; + return true; } // Save answers. @@ -164,6 +165,8 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy { } finally { modal.dismiss(); } + + return true; } /** diff --git a/src/core/features/features.module.ts b/src/core/features/features.module.ts index 9be76ae02..653f24f03 100644 --- a/src/core/features/features.module.ts +++ b/src/core/features/features.module.ts @@ -31,6 +31,7 @@ import { CoreXAPIModule } from './xapi/xapi.module'; import { CoreViewerModule } from './viewer/viewer.module'; import { CoreSearchModule } from './search/search.module'; import { CoreCommentsModule } from './comments/comments.module'; +import { CoreSitePluginsModule } from './siteplugins/siteplugins.module'; @NgModule({ imports: [ @@ -51,6 +52,7 @@ import { CoreCommentsModule } from './comments/comments.module'; CoreH5PModule, CoreViewerModule, CoreCommentsModule, + CoreSitePluginsModule, ], }) export class CoreFeaturesModule {} diff --git a/src/core/features/siteplugins/classes/handlers/course-option-handler.ts b/src/core/features/siteplugins/classes/handlers/course-option-handler.ts index 6e4cbf464..e3960190d 100644 --- a/src/core/features/siteplugins/classes/handlers/course-option-handler.ts +++ b/src/core/features/siteplugins/classes/handlers/course-option-handler.ts @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +import { Md5 } from 'ts-md5'; + import { CoreCourseOptionsHandler, CoreCourseOptionsHandlerData, @@ -74,10 +76,8 @@ export class CoreSitePluginsCourseOptionHandler extends CoreSitePluginsBaseHandl return { title: this.title, class: this.handlerSchema.displaydata?.class, - page: '@todo CoreSitePluginsCourseOptionComponent', - pageParams: { - handlerUniqueName: this.name, - }, + page: `siteplugins/${this.name}`, + pageParams: {}, }; } @@ -85,18 +85,19 @@ export class CoreSitePluginsCourseOptionHandler extends CoreSitePluginsBaseHandl * @inheritdoc */ getMenuDisplayData(course: CoreCourseAnyCourseDataWithOptions): CoreCourseOptionsMenuHandlerData { + const args = { + courseid: course.id, + }; + const hash = Md5.hashAsciiStr(JSON.stringify(args)); + return { title: this.title, class: this.handlerSchema.displaydata?.class, icon: this.handlerSchema.displaydata?.icon || '', - page: '@todo CoreSitePluginsPluginPage', + page: `siteplugins/${this.plugin.component}/${this.handlerSchema.method}/${hash}`, pageParams: { title: this.title, - component: this.plugin.component, - method: this.handlerSchema.method, - args: { - courseid: course.id, - }, + args, initResult: this.initResult, ptrEnabled: this.handlerSchema.ptrenabled, }, diff --git a/src/core/features/siteplugins/classes/handlers/main-menu-handler.ts b/src/core/features/siteplugins/classes/handlers/main-menu-handler.ts index becc84c29..aa118ccda 100644 --- a/src/core/features/siteplugins/classes/handlers/main-menu-handler.ts +++ b/src/core/features/siteplugins/classes/handlers/main-menu-handler.ts @@ -47,11 +47,9 @@ export class CoreSitePluginsMainMenuHandler extends CoreSitePluginsBaseHandler i title: this.title, icon: this.handlerSchema.displaydata?.icon || 'fas-question', class: this.handlerSchema.displaydata?.class, - page: '@todo CoreSitePluginsPluginPage', + page: `siteplugins/${this.plugin.component}/${this.handlerSchema.method}/0`, pageParams: { title: this.title, - component: this.plugin.component, - method: this.handlerSchema.method, initResult: this.initResult, ptrEnabled: this.handlerSchema.ptrenabled, }, diff --git a/src/core/features/siteplugins/classes/handlers/message-output-handler.ts b/src/core/features/siteplugins/classes/handlers/message-output-handler.ts index 194797fab..a67af0f94 100644 --- a/src/core/features/siteplugins/classes/handlers/message-output-handler.ts +++ b/src/core/features/siteplugins/classes/handlers/message-output-handler.ts @@ -44,11 +44,9 @@ export class CoreSitePluginsMessageOutputHandler extends CoreSitePluginsBaseHand priority: this.handlerSchema.priority || 0, label: this.title, icon: this.handlerSchema.displaydata?.icon || 'fas-question', - page: '@todo CoreSitePluginsPluginPage', + page: `siteplugins/${this.plugin.component}/${this.handlerSchema.method}/0`, pageParams: { title: this.title, - component: this.plugin.component, - method: this.handlerSchema.method, initResult: this.initResult, ptrEnabled: this.handlerSchema.ptrenabled, }, diff --git a/src/core/features/siteplugins/classes/handlers/module-handler.ts b/src/core/features/siteplugins/classes/handlers/module-handler.ts index 37de850a2..11009129f 100644 --- a/src/core/features/siteplugins/classes/handlers/module-handler.ts +++ b/src/core/features/siteplugins/classes/handlers/module-handler.ts @@ -24,7 +24,7 @@ import { CoreSitePluginsCourseModuleHandlerData, CoreSitePluginsPlugin, } from '@features/siteplugins/services/siteplugins'; -import { CoreNavigationOptions } from '@services/navigator'; +import { CoreNavigationOptions, CoreNavigator } from '@services/navigator'; import { CoreLogger } from '@singletons/logger'; import { CoreSitePluginsBaseHandler } from './base-handler'; @@ -95,11 +95,13 @@ export class CoreSitePluginsModuleHandler extends CoreSitePluginsBaseHandler imp event.preventDefault(); event.stopPropagation(); - // @todo navCtrl.push('CoreSitePluginsModuleIndexPage', { - // title: module.name, - // module: module, - // courseId: courseId - // }, options); + options = options || {}; + options.params = { + title: module.name, + module, + }; + + CoreNavigator.navigateToSitePath(`siteplugins/module/${courseId}/${module.id}`, options); }; } diff --git a/src/core/features/siteplugins/classes/handlers/settings-handler.ts b/src/core/features/siteplugins/classes/handlers/settings-handler.ts index f1b9844e1..950f00039 100644 --- a/src/core/features/siteplugins/classes/handlers/settings-handler.ts +++ b/src/core/features/siteplugins/classes/handlers/settings-handler.ts @@ -49,11 +49,9 @@ export class CoreSitePluginsSettingsHandler extends CoreSitePluginsBaseHandler i title: this.title, icon: this.handlerSchema.displaydata?.icon, class: this.handlerSchema.displaydata?.class, - page: '@todo CoreSitePluginsPluginPage', + page: `siteplugins/${this.plugin.component}/${this.handlerSchema.method}/0`, params: { title: this.title, - component: this.plugin.component, - method: this.handlerSchema.method, initResult: this.initResult, ptrEnabled: this.handlerSchema.ptrenabled, }, diff --git a/src/core/features/siteplugins/classes/handlers/user-handler.ts b/src/core/features/siteplugins/classes/handlers/user-handler.ts index b19e8193f..86978a13e 100644 --- a/src/core/features/siteplugins/classes/handlers/user-handler.ts +++ b/src/core/features/siteplugins/classes/handlers/user-handler.ts @@ -20,8 +20,10 @@ import { } from '@features/siteplugins/services/siteplugins'; import { CoreUserProfile } from '@features/user/services/user'; import { CoreUserDelegateService, CoreUserProfileHandler, CoreUserProfileHandlerData } from '@features/user/services/user-delegate'; +import { CoreNavigator } from '@services/navigator'; import { CoreSites } from '@services/sites'; import { CoreUtils, PromiseDefer } from '@services/utils/utils'; +import { Md5 } from 'ts-md5'; import { CoreSitePluginsBaseHandler } from './base-handler'; /** @@ -90,17 +92,23 @@ export class CoreSitePluginsUserProfileHandler extends CoreSitePluginsBaseHandle event.preventDefault(); event.stopPropagation(); - // @todo navCtrl.push('CoreSitePluginsPluginPage', { - // title: this.title, - // component: this.plugin.component, - // method: this.handlerSchema.method, - // args: { - // courseid: courseId, - // userid: user.id - // }, - // initResult: this.initResult, - // ptrEnabled: this.handlerSchema.ptrenabled, - // }); + const args = { + courseid: courseId, + userid: user.id, + }; + const hash = Md5.hashAsciiStr(JSON.stringify(args)); + + CoreNavigator.navigateToSitePath( + `siteplugins/${this.plugin.component}/${this.handlerSchema.method}/${hash}`, + { + params: { + title: this.title, + args, + initResult: this.initResult, + ptrEnabled: this.handlerSchema.ptrenabled, + }, + }, + ); }, }; } diff --git a/src/core/features/siteplugins/components/components.module.ts b/src/core/features/siteplugins/components/components.module.ts index 1145557fb..977cceb44 100644 --- a/src/core/features/siteplugins/components/components.module.ts +++ b/src/core/features/siteplugins/components/components.module.ts @@ -18,7 +18,6 @@ import { CoreSharedModule } from '@/core/shared.module'; import { CoreCompileHtmlComponentModule } from '@features/compile/components/compile-html/compile-html.module'; import { CoreSitePluginsPluginContentComponent } from './plugin-content/plugin-content'; import { CoreSitePluginsModuleIndexComponent } from './module-index/module-index'; -import { CoreSitePluginsCourseOptionComponent } from './course-option/course-option'; import { CoreSitePluginsCourseFormatComponent } from './course-format/course-format'; import { CoreSitePluginsUserProfileFieldComponent } from './user-profile-field/user-profile-field'; import { CoreSitePluginsQuestionComponent } from './question/question'; @@ -37,7 +36,6 @@ import { CoreSitePluginsOnlyTitleBlockComponent } from './only-title-block/only- CoreSitePluginsModuleIndexComponent, CoreSitePluginsBlockComponent, CoreSitePluginsOnlyTitleBlockComponent, - CoreSitePluginsCourseOptionComponent, CoreSitePluginsCourseFormatComponent, CoreSitePluginsUserProfileFieldComponent, CoreSitePluginsQuestionComponent, @@ -57,7 +55,6 @@ import { CoreSitePluginsOnlyTitleBlockComponent } from './only-title-block/only- CoreSitePluginsModuleIndexComponent, CoreSitePluginsBlockComponent, CoreSitePluginsOnlyTitleBlockComponent, - CoreSitePluginsCourseOptionComponent, CoreSitePluginsCourseFormatComponent, CoreSitePluginsUserProfileFieldComponent, CoreSitePluginsQuestionComponent, diff --git a/src/core/features/siteplugins/components/only-title-block/only-title-block.ts b/src/core/features/siteplugins/components/only-title-block/only-title-block.ts index a492d2166..742c392d3 100644 --- a/src/core/features/siteplugins/components/only-title-block/only-title-block.ts +++ b/src/core/features/siteplugins/components/only-title-block/only-title-block.ts @@ -13,10 +13,12 @@ // limitations under the License. import { OnInit, Component } from '@angular/core'; +import { Md5 } from 'ts-md5'; import { CoreBlockBaseComponent } from '@features/block/classes/base-block-component'; import { CoreBlockDelegate } from '@features/block/services/block-delegate'; -import { CoreSitePlugins } from '@features/siteplugins/services/siteplugins'; +import { CoreSitePlugins, CoreSitePluginsUserHandlerData } from '@features/siteplugins/services/siteplugins'; +import { CoreNavigator } from '@services/navigator'; /** * Component to render blocks with only a title and link. @@ -51,18 +53,23 @@ export class CoreSitePluginsOnlyTitleBlockComponent extends CoreBlockBaseCompon return; } - // @todo - // navCtrl.push('CoreSitePluginsPluginPage', { - // title: this.title, - // component: handler.plugin.component, - // method: handler.handlerSchema.method, - // initResult: handler.initResult, - // args: { - // contextlevel: this.contextLevel, - // instanceid: this.instanceId, - // }, - // ptrEnabled: handler.handlerSchema.ptrenabled, - // }); + const args = { + contextlevel: this.contextLevel, + instanceid: this.instanceId, + }; + const hash = Md5.hashAsciiStr(JSON.stringify(args)); + + CoreNavigator.navigateToSitePath( + `siteplugins/${handler.plugin.component}/${handler.handlerSchema.method}/${hash}`, + { + params: { + title: this.title, + args, + initResult: handler.initResult, + ptrEnabled: ( handler.handlerSchema).ptrenabled, + }, + }, + ); } } diff --git a/src/core/features/siteplugins/components/plugin-content/plugin-content.ts b/src/core/features/siteplugins/components/plugin-content/plugin-content.ts index 48b4cb749..24187a1a7 100644 --- a/src/core/features/siteplugins/components/plugin-content/plugin-content.ts +++ b/src/core/features/siteplugins/components/plugin-content/plugin-content.ts @@ -13,11 +13,14 @@ // limitations under the License. import { Component, OnInit, Input, Output, EventEmitter, DoCheck, KeyValueDiffers, ViewChild, KeyValueDiffer } from '@angular/core'; +import { Subject } from 'rxjs'; +import { Md5 } from 'ts-md5'; + import { CoreSiteWSPreSets } from '@classes/site'; import { CoreCompileHtmlComponent } from '@features/compile/components/compile-html/compile-html'; import { CoreSitePlugins, CoreSitePluginsContent } from '@features/siteplugins/services/siteplugins'; +import { CoreNavigator } from '@services/navigator'; import { CoreDomUtils } from '@services/utils/dom'; -import { Subject } from 'rxjs'; /** * Component to render a site plugin content. @@ -145,17 +148,21 @@ export class CoreSitePluginsPluginContentComponent implements OnInit, DoCheck { jsData = this.data; } - // @todo - // this.navCtrl.push('CoreSitePluginsPluginPage', { - // title: title, - // component: component || this.component, - // method: method || this.method, - // args: args, - // initResult: this.initResult, - // jsData: jsData, - // preSets: preSets, - // ptrEnabled: ptrEnabled, - // }); + component = component || this.component; + method = method || this.method; + args = args || {}; + const hash = Md5.hashAsciiStr(JSON.stringify(args)); + + CoreNavigator.navigateToSitePath(`siteplugins/${component}/${method}/${hash}`, { + params: { + title, + args, + initResult: this.initResult, + jsData, + preSets, + ptrEnabled, + }, + }); } /** diff --git a/src/core/features/siteplugins/components/course-option/core-siteplugins-course-option.html b/src/core/features/siteplugins/pages/course-option/core-siteplugins-course-option.html similarity index 71% rename from src/core/features/siteplugins/components/course-option/core-siteplugins-course-option.html rename to src/core/features/siteplugins/pages/course-option/core-siteplugins-course-option.html index e63a4ec08..bb6328cbb 100644 --- a/src/core/features/siteplugins/components/course-option/core-siteplugins-course-option.html +++ b/src/core/features/siteplugins/pages/course-option/core-siteplugins-course-option.html @@ -1,5 +1,6 @@ - + ; @@ -43,6 +43,9 @@ export class CoreSitePluginsCourseOptionComponent implements OnInit { * Component being initialized. */ ngOnInit(): void { + this.courseId = CoreNavigator.getRouteNumberParam('courseId'); + this.handlerUniqueName = CoreNavigator.getRouteParam('handlerUniqueName'); + if (!this.handlerUniqueName) { return; } @@ -75,4 +78,55 @@ export class CoreSitePluginsCourseOptionComponent implements OnInit { } } + /** + * The page is about to enter and become the active page. + */ + ionViewWillEnter(): void { + this.content?.callComponentFunction('ionViewWillEnter'); + } + + /** + * The page has fully entered and is now the active page. This event will fire, whether it was the first load or a cached page. + */ + ionViewDidEnter(): void { + this.content?.callComponentFunction('ionViewDidEnter'); + } + + /** + * The page is about to leave and no longer be the active page. + */ + ionViewWillLeave(): void { + this.content?.callComponentFunction('ionViewWillLeave'); + } + + /** + * The page has finished leaving and is no longer the active page. + */ + ionViewDidLeave(): void { + this.content?.callComponentFunction('ionViewDidLeave'); + } + + /** + * The page is about to be destroyed and have its elements removed. + */ + ionViewWillUnload(): void { + this.content?.callComponentFunction('ionViewWillUnload'); + } + + /** + * Check if we can leave the page or not. + * + * @return Resolved if we can leave it, rejected if not. + */ + async canLeave(): Promise { + if (!this.content) { + return true; + } + + + const result = await this.content.callComponentFunction('canLeave'); + + return result === undefined || result === null ? true : !!result; + } + } diff --git a/src/core/features/siteplugins/pages/module-index/module-index.html b/src/core/features/siteplugins/pages/module-index/module-index.html new file mode 100644 index 000000000..c1b98c79a --- /dev/null +++ b/src/core/features/siteplugins/pages/module-index/module-index.html @@ -0,0 +1,19 @@ + + + + + + {{ title }} + + + + + + + + + + + + diff --git a/src/core/features/siteplugins/pages/module-index/module-index.module.ts b/src/core/features/siteplugins/pages/module-index/module-index.module.ts new file mode 100644 index 000000000..edf9be25a --- /dev/null +++ b/src/core/features/siteplugins/pages/module-index/module-index.module.ts @@ -0,0 +1,45 @@ +// (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 { RouterModule, Routes } from '@angular/router'; + +import { CoreSharedModule } from '@/core/shared.module'; +import { CanLeaveGuard } from '@guards/can-leave'; +import { CoreSitePluginsModuleIndexPage } from './module-index'; +import { CoreSitePluginsComponentsModule } from '../../components/components.module'; + +const routes: Routes = [ + { + path: '', + component: CoreSitePluginsModuleIndexPage, + canDeactivate: [CanLeaveGuard], + }, +]; + +/** + * Module to lazy load the page. + */ +@NgModule({ + declarations: [ + CoreSitePluginsModuleIndexPage, + ], + imports: [ + RouterModule.forChild(routes), + CoreSharedModule, + CoreSitePluginsComponentsModule, + ], + exports: [RouterModule], +}) +export class CoreSitePluginsModuleIndexPageModule {} diff --git a/src/core/features/siteplugins/pages/module-index/module-index.ts b/src/core/features/siteplugins/pages/module-index/module-index.ts new file mode 100644 index 000000000..b4daca1f6 --- /dev/null +++ b/src/core/features/siteplugins/pages/module-index/module-index.ts @@ -0,0 +1,109 @@ +// (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, ViewChild } from '@angular/core'; +import { IonRefresher } from '@ionic/angular'; + +import { CoreCourseModule } from '@features/course/services/course-helper'; +import { CanLeave } from '@guards/can-leave'; +import { CoreNavigator } from '@services/navigator'; +import { CoreSitePluginsModuleIndexComponent } from '../../components/module-index/module-index'; + +/** + * Page to render the index page of a module site plugin. + */ +@Component({ + selector: 'page-core-site-plugins-module-index', + templateUrl: 'module-index.html', +}) +export class CoreSitePluginsModuleIndexPage implements OnInit, CanLeave { + + @ViewChild(CoreSitePluginsModuleIndexComponent) content?: CoreSitePluginsModuleIndexComponent; + + title?: string; // Page title. + module?: CoreCourseModule; + courseId?: number; + + /** + * @inheritdoc + */ + ngOnInit(): void { + this.title = CoreNavigator.getRouteParam('title'); + this.module = CoreNavigator.getRouteParam('module'); + this.courseId = CoreNavigator.getRouteNumberParam('courseId'); + } + + /** + * Refresh the data. + * + * @param refresher Refresher. + */ + refreshData(refresher: IonRefresher): void { + this.content?.doRefresh().finally(() => { + refresher.complete(); + }); + } + + /** + * The page is about to enter and become the active page. + */ + ionViewWillEnter(): void { + this.content?.callComponentFunction('ionViewWillEnter'); + } + + /** + * The page has fully entered and is now the active page. This event will fire, whether it was the first load or a cached page. + */ + ionViewDidEnter(): void { + this.content?.callComponentFunction('ionViewDidEnter'); + } + + /** + * The page is about to leave and no longer be the active page. + */ + ionViewWillLeave(): void { + this.content?.callComponentFunction('ionViewWillLeave'); + } + + /** + * The page has finished leaving and is no longer the active page. + */ + ionViewDidLeave(): void { + this.content?.callComponentFunction('ionViewDidLeave'); + } + + /** + * The page is about to be destroyed and have its elements removed. + */ + ionViewWillUnload(): void { + this.content?.callComponentFunction('ionViewWillUnload'); + } + + /** + * Check if we can leave the page or not. + * + * @return Resolved if we can leave it, rejected if not. + */ + async canLeave(): Promise { + if (!this.content) { + return true; + } + + + const result = await this.content.callComponentFunction('canLeave'); + + return result === undefined || result === null ? true : !!result; + } + +} diff --git a/src/core/features/siteplugins/pages/plugin-page/plugin-page.html b/src/core/features/siteplugins/pages/plugin-page/plugin-page.html new file mode 100644 index 000000000..ea8d93c87 --- /dev/null +++ b/src/core/features/siteplugins/pages/plugin-page/plugin-page.html @@ -0,0 +1,21 @@ + + + + + + {{ title | translate }} + + + + + + + + + + + + + diff --git a/src/core/features/siteplugins/pages/plugin-page/plugin-page.module.ts b/src/core/features/siteplugins/pages/plugin-page/plugin-page.module.ts new file mode 100644 index 000000000..083771477 --- /dev/null +++ b/src/core/features/siteplugins/pages/plugin-page/plugin-page.module.ts @@ -0,0 +1,45 @@ +// (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 { RouterModule, Routes } from '@angular/router'; + +import { CoreSharedModule } from '@/core/shared.module'; +import { CanLeaveGuard } from '@guards/can-leave'; +import { CoreSitePluginsPluginPage } from './plugin-page'; +import { CoreSitePluginsComponentsModule } from '../../components/components.module'; + +const routes: Routes = [ + { + path: '', + component: CoreSitePluginsPluginPage, + canDeactivate: [CanLeaveGuard], + }, +]; + +/** + * Module to lazy load the page. + */ +@NgModule({ + declarations: [ + CoreSitePluginsPluginPage, + ], + imports: [ + RouterModule.forChild(routes), + CoreSharedModule, + CoreSitePluginsComponentsModule, + ], + exports: [RouterModule], +}) +export class CoreSitePluginsPluginPageModule {} diff --git a/src/core/features/siteplugins/pages/plugin-page/plugin-page.ts b/src/core/features/siteplugins/pages/plugin-page/plugin-page.ts new file mode 100644 index 000000000..0895d5157 --- /dev/null +++ b/src/core/features/siteplugins/pages/plugin-page/plugin-page.ts @@ -0,0 +1,120 @@ +// (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, ViewChild } from '@angular/core'; +import { IonRefresher } from '@ionic/angular'; + +import { CoreSiteWSPreSets } from '@classes/site'; +import { CoreSitePluginsContent } from '@features/siteplugins/services/siteplugins'; +import { CanLeave } from '@guards/can-leave'; +import { CoreNavigator } from '@services/navigator'; +import { CoreUtils } from '@services/utils/utils'; +import { CoreSitePluginsPluginContentComponent } from '../../components/plugin-content/plugin-content'; + +/** + * Page to render a site plugin page. + */ +@Component({ + selector: 'page-core-site-plugins-plugin', + templateUrl: 'plugin-page.html', +}) +export class CoreSitePluginsPluginPage implements OnInit, CanLeave { + + @ViewChild(CoreSitePluginsPluginContentComponent) content?: CoreSitePluginsPluginContentComponent; + + title?: string; // Page title. + component?: string; + method?: string; + args?: Record; + initResult?: CoreSitePluginsContent | null; + jsData?: Record; // JS variables to pass to the plugin so they can be used in the template or JS. + preSets?: CoreSiteWSPreSets; // The preSets for the WS call. + ptrEnabled = false; + + /** + * @inheritdoc + */ + ngOnInit(): void { + this.title = CoreNavigator.getRouteParam('title'); + this.component = CoreNavigator.getRouteParam('component'); + this.method = CoreNavigator.getRouteParam('method'); + this.args = CoreNavigator.getRouteParam('args'); + this.initResult = CoreNavigator.getRouteParam('initResult'); + this.jsData = CoreNavigator.getRouteParam('jsData'); + this.preSets = CoreNavigator.getRouteParam('preSets'); + this.ptrEnabled = !CoreUtils.isFalseOrZero(CoreNavigator.getRouteBooleanParam('ptrEnabled')); + } + + /** + * Refresh the data. + * + * @param refresher Refresher. + */ + refreshData(refresher: IonRefresher): void { + this.content?.refreshContent(false).finally(() => { + refresher.complete(); + }); + } + + /** + * The page is about to enter and become the active page. + */ + ionViewWillEnter(): void { + this.content?.callComponentFunction('ionViewWillEnter'); + } + + /** + * The page has fully entered and is now the active page. This event will fire, whether it was the first load or a cached page. + */ + ionViewDidEnter(): void { + this.content?.callComponentFunction('ionViewDidEnter'); + } + + /** + * The page is about to leave and no longer be the active page. + */ + ionViewWillLeave(): void { + this.content?.callComponentFunction('ionViewWillLeave'); + } + + /** + * The page has finished leaving and is no longer the active page. + */ + ionViewDidLeave(): void { + this.content?.callComponentFunction('ionViewDidLeave'); + } + + /** + * The page is about to be destroyed and have its elements removed. + */ + ionViewWillUnload(): void { + this.content?.callComponentFunction('ionViewWillUnload'); + } + + /** + * Check if we can leave the page or not. + * + * @return Resolved if we can leave it, rejected if not. + */ + async canLeave(): Promise { + if (!this.content) { + return true; + } + + const result = await this.content.callComponentFunction('canLeave'); + + return result === undefined || result === null ? true : !!result; + } + +} diff --git a/src/core/features/siteplugins/siteplugins-lazy.module.ts b/src/core/features/siteplugins/siteplugins-lazy.module.ts new file mode 100644 index 000000000..7b7e4a3dd --- /dev/null +++ b/src/core/features/siteplugins/siteplugins-lazy.module.ts @@ -0,0 +1,32 @@ +// (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 { RouterModule, Routes } from '@angular/router'; + +const routes: Routes = [ + { + path: 'module/:courseId/:cmId', + loadChildren: () => import('./pages/module-index/module-index.module').then( m => m.CoreSitePluginsModuleIndexPageModule), + }, + { + path: ':component/:method/:hash', + loadChildren: () => import('./pages/plugin-page/plugin-page.module').then( m => m.CoreSitePluginsPluginPageModule), + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], +}) +export class CoreSitePluginsLazyModule {} diff --git a/src/core/features/siteplugins/siteplugins.module.ts b/src/core/features/siteplugins/siteplugins.module.ts new file mode 100644 index 000000000..154e3feb0 --- /dev/null +++ b/src/core/features/siteplugins/siteplugins.module.ts @@ -0,0 +1,54 @@ +// (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 { APP_INITIALIZER, NgModule } from '@angular/core'; +import { Routes } from '@angular/router'; + +import { CoreCourseIndexRoutingModule } from '@features/course/pages/index/index-routing.module'; +import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module'; +import { CoreSitePluginsComponentsModule } from './components/components.module'; +import { CoreSitePluginsHelper } from './services/siteplugins-helper'; + +const routes: Routes = [ + { + path: 'siteplugins', + loadChildren: () => import('@features/siteplugins/siteplugins-lazy.module').then(m => m.CoreSitePluginsLazyModule), + }, +]; + +const courseIndexRoutes: Routes = [ + { + path: 'siteplugins/:handlerUniqueName', + loadChildren: () => import('@features/siteplugins/pages/course-option/course-option.module') + .then(m => m.CoreSitePluginsCourseOptionModule), + }, +]; + +@NgModule({ + imports: [ + CoreMainMenuTabRoutingModule.forChild(routes), + CoreCourseIndexRoutingModule.forChild({ children: courseIndexRoutes }), + CoreSitePluginsComponentsModule, + ], + providers: [ + { + provide: APP_INITIALIZER, + multi: true, + useValue: () => { + CoreSitePluginsHelper.initialize(); + }, + }, + ], +}) +export class CoreSitePluginsModule {} diff --git a/src/core/pipes/pipes.module.ts b/src/core/pipes/pipes.module.ts index 8e32caa7f..36e2e09c7 100644 --- a/src/core/pipes/pipes.module.ts +++ b/src/core/pipes/pipes.module.ts @@ -21,6 +21,7 @@ import { CoreTimeAgoPipe } from './time-ago'; import { CoreBytesToSizePipe } from './bytes-to-size'; import { CoreDurationPipe } from './duration'; import { CoreDateDayOrTimePipe } from './date-day-or-time'; +import { CoreToLocaleStringPipe } from './to-locale-string'; @NgModule({ declarations: [ @@ -32,6 +33,7 @@ import { CoreDateDayOrTimePipe } from './date-day-or-time'; CoreSecondsToHMSPipe, CoreDurationPipe, CoreDateDayOrTimePipe, + CoreToLocaleStringPipe, ], imports: [], exports: [ @@ -43,6 +45,7 @@ import { CoreDateDayOrTimePipe } from './date-day-or-time'; CoreSecondsToHMSPipe, CoreDurationPipe, CoreDateDayOrTimePipe, + CoreToLocaleStringPipe, ], }) export class CorePipesModule {} diff --git a/src/core/pipes/to-locale-string.ts b/src/core/pipes/to-locale-string.ts new file mode 100644 index 000000000..d64df8a6d --- /dev/null +++ b/src/core/pipes/to-locale-string.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 { Pipe, PipeTransform } from '@angular/core'; +import { CoreTimeUtils } from '@services/utils/time'; + +import { CoreLogger } from '@singletons/logger'; + +/** + * Filter to format a timestamp to a locale string. Timestamp can be in seconds or milliseconds. + * + * @deprecated since 3.6. Use coreFormatDate instead. + */ +@Pipe({ + name: 'coreToLocaleString', +}) +export class CoreToLocaleStringPipe implements PipeTransform { + + protected logger: CoreLogger; + + constructor() { + this.logger = CoreLogger.getInstance('CoreToLocaleStringPipe'); + } + + /** + * Format a timestamp to a locale string. + * + * @param timestamp The timestamp (can be in seconds or milliseconds). + * @return Formatted time. + */ + transform(timestamp: number | string): string { + if (typeof timestamp == 'string') { + // Convert the value to a number. + const numberTimestamp = parseInt(timestamp, 10); + if (isNaN(numberTimestamp)) { + this.logger.error('Invalid value received', timestamp); + + return timestamp; + } + timestamp = numberTimestamp; + } + + if (timestamp < 0) { + // Date not valid. + return ''; + } + if (timestamp < 100000000000) { + // Timestamp is in seconds, convert it to milliseconds. + timestamp = timestamp * 1000; + } + + return CoreTimeUtils.userDate(timestamp, 'core.strftimedatetimeshort'); + } + +} diff --git a/src/core/services/utils/utils.ts b/src/core/services/utils/utils.ts index b5b23f0bf..e9ac75b7d 100644 --- a/src/core/services/utils/utils.ts +++ b/src/core/services/utils/utils.ts @@ -186,15 +186,6 @@ export class CoreUtilsProvider { } } - /** - * Blocks leaving a view. - * - * @deprecated, use ionViewCanLeave instead. - */ - blockLeaveView(): void { - return; - } - /** * Check if a URL has a redirect. *