From 3a7848ec1bb802b95e22bc012e0c5dd3690aec3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Wed, 19 Jun 2024 16:53:50 +0200 Subject: [PATCH] MOBILE-3730: Load styles from site plugins --- .../classes/compile-init-component.ts | 16 +- .../classes/handlers/course-option-handler.ts | 12 +- .../classes/handlers/main-menu-handler.ts | 4 + .../handlers/message-output-handler.ts | 4 + .../classes/handlers/settings-handler.ts | 4 + .../classes/handlers/user-handler.ts | 4 + .../assign-feedback/assign-feedback.ts | 4 +- .../core-siteplugins-assign-feedback.html | 2 +- .../assign-submission/assign-submission.ts | 4 +- .../core-siteplugins-assign-submission.html | 2 +- .../siteplugins/components/block/block.ts | 11 +- .../block/core-siteplugins-block.html | 2 +- .../core-siteplugins-course-format.html | 2 +- .../components/course-format/course-format.ts | 5 +- .../core-siteplugins-module-index.html | 2 +- .../components/module-index/module-index.ts | 7 +- .../only-title-block/only-title-block.ts | 1 + .../core-siteplugins-plugin-content.html | 3 +- .../plugin-content/plugin-content.ts | 7 +- .../core-siteplugins-question-behaviour.html | 2 +- .../question-behaviour/question-behaviour.ts | 4 +- .../question/core-siteplugins-question.html | 2 +- .../components/question/question.ts | 4 +- .../core-siteplugins-quiz-access-rule.html | 2 +- .../quiz-access-rule/quiz-access-rule.ts | 4 +- .../core-siteplugins-user-profile-field.html | 2 +- .../user-profile-field/user-profile-field.ts | 4 +- ...eplugins-workshop-assessment-strategy.html | 2 +- .../workshop-assessment-strategy.ts | 4 +- .../directives/call-ws-new-content.ts | 1 + .../siteplugins/directives/new-content.ts | 1 + .../core-siteplugins-course-option.html | 5 +- .../pages/course-option/course-option.ts | 17 +- .../siteplugins/pages/plugin/plugin.html | 2 +- .../siteplugins/pages/plugin/plugin.ts | 11 +- .../siteplugins/services/siteplugins-init.ts | 170 +++++++++++------- .../siteplugins/services/siteplugins.ts | 30 ++++ 37 files changed, 245 insertions(+), 118 deletions(-) diff --git a/src/core/features/siteplugins/classes/compile-init-component.ts b/src/core/features/siteplugins/classes/compile-init-component.ts index 6fe45e7770e..614831415c2 100644 --- a/src/core/features/siteplugins/classes/compile-init-component.ts +++ b/src/core/features/siteplugins/classes/compile-init-component.ts @@ -25,6 +25,8 @@ export class CoreSitePluginsCompileInitComponent { jsData: Record = {}; // Data to pass to the component. protected handlerSchema?: CoreSitePluginsInitHandlerData; // The handler data. + stylesPath?: string; // Styles to apply to the component. + /** * Function called when the component is created. * @@ -40,18 +42,18 @@ export class CoreSitePluginsCompileInitComponent { /** * Get the handler data. * - * @param name The name of the handler. + * @param handlerName The name of the handler. */ - getHandlerData(name: string): void { + async getHandlerData(handlerName: string): Promise { // Retrieve the handler data. - const handler = CoreSitePlugins.getSitePluginHandler(name); - - this.handlerSchema = handler?.handlerSchema; + const handler = CoreSitePlugins.getSitePluginHandler(handlerName); - if (!this.handlerSchema) { + if (!handler?.handlerSchema) { return; } + this.handlerSchema = handler.handlerSchema; + // Load first template. if (this.handlerSchema.methodTemplates?.length) { this.content = this.handlerSchema.methodTemplates[0].html; @@ -70,6 +72,8 @@ export class CoreSitePluginsCompileInitComponent { if (this.handlerSchema.methodJSResult) { this.jsData.CONTENT_JS_RESULT = this.handlerSchema.methodJSResult; } + + this.stylesPath = await CoreSitePlugins.getHandlerDownloadedStyles(handlerName); } } 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 1a57f74aafa..ea34a768bfd 100644 --- a/src/core/features/siteplugins/classes/handlers/course-option-handler.ts +++ b/src/core/features/siteplugins/classes/handlers/course-option-handler.ts @@ -71,23 +71,28 @@ export class CoreSitePluginsCourseOptionHandler extends CoreSitePluginsBaseHandl /** * @inheritdoc */ - getDisplayData(): CoreCourseOptionsHandlerData { + async getDisplayData(): Promise { + const stylesPath = await this.handlerSchema.styles?.downloadedStyles; + return { title: this.title, class: this.handlerSchema.displaydata?.class, page: `siteplugins/${this.name}`, - pageParams: {}, + pageParams: { + stylesPath, + }, }; } /** * @inheritdoc */ - getMenuDisplayData(course: CoreCourseAnyCourseDataWithOptions): CoreCourseOptionsMenuHandlerData { + async getMenuDisplayData(course: CoreCourseAnyCourseDataWithOptions): Promise { const args = { courseid: course.id, }; const hash = Md5.hashAsciiStr(JSON.stringify(args)); + const stylesPath = await this.handlerSchema.styles?.downloadedStyles; return { title: this.title, @@ -99,6 +104,7 @@ export class CoreSitePluginsCourseOptionHandler extends CoreSitePluginsBaseHandl args, initResult: this.initResult, ptrEnabled: this.handlerSchema.ptrenabled, + stylesPath, }, }; } 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 1d06b9beaa8..ff87384deb9 100644 --- a/src/core/features/siteplugins/classes/handlers/main-menu-handler.ts +++ b/src/core/features/siteplugins/classes/handlers/main-menu-handler.ts @@ -14,6 +14,7 @@ import { CoreMainMenuHandler, CoreMainMenuHandlerData } from '@features/mainmenu/services/mainmenu-delegate'; import { + CoreSitePlugins, CoreSitePluginsContent, CoreSitePluginsMainMenuHandlerData, CoreSitePluginsPlugin, @@ -43,6 +44,8 @@ export class CoreSitePluginsMainMenuHandler extends CoreSitePluginsBaseHandler i * @inheritdoc */ getDisplayData(): CoreMainMenuHandlerData { + const handlerName = CoreSitePlugins.getHandlerNameFromUniqueName(this.name, this.plugin.addon); + return { title: this.title, icon: this.handlerSchema.displaydata?.icon || 'fas-question', @@ -52,6 +55,7 @@ export class CoreSitePluginsMainMenuHandler extends CoreSitePluginsBaseHandler i title: this.title, initResult: this.initResult, ptrEnabled: this.handlerSchema.ptrenabled, + handlerName, }, onlyInMore: true, }; 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 07db9433668..06ce1f49f77 100644 --- a/src/core/features/siteplugins/classes/handlers/message-output-handler.ts +++ b/src/core/features/siteplugins/classes/handlers/message-output-handler.ts @@ -14,6 +14,7 @@ import { AddonMessageOutputHandler, AddonMessageOutputHandlerData } from '@addons/messageoutput/services/messageoutput-delegate'; import { + CoreSitePlugins, CoreSitePluginsContent, CoreSitePluginsMessageOutputHandlerData, CoreSitePluginsPlugin, @@ -40,6 +41,8 @@ export class CoreSitePluginsMessageOutputHandler extends CoreSitePluginsBaseHand * @inheritdoc */ getDisplayData(): AddonMessageOutputHandlerData { + const handlerName = CoreSitePlugins.getHandlerNameFromUniqueName(this.name, this.plugin.addon); + return { priority: this.handlerSchema.priority || 0, label: this.title, @@ -49,6 +52,7 @@ export class CoreSitePluginsMessageOutputHandler extends CoreSitePluginsBaseHand title: this.title, initResult: this.initResult, ptrEnabled: this.handlerSchema.ptrenabled, + handlerName, }, }; } diff --git a/src/core/features/siteplugins/classes/handlers/settings-handler.ts b/src/core/features/siteplugins/classes/handlers/settings-handler.ts index 3328f39c2ae..1d2b733f11d 100644 --- a/src/core/features/siteplugins/classes/handlers/settings-handler.ts +++ b/src/core/features/siteplugins/classes/handlers/settings-handler.ts @@ -14,6 +14,7 @@ import { CoreSettingsHandler, CoreSettingsHandlerData } from '@features/settings/services/settings-delegate'; import { + CoreSitePlugins, CoreSitePluginsContent, CoreSitePluginsPlugin, CoreSitePluginsSettingsHandlerData, @@ -45,6 +46,8 @@ export class CoreSitePluginsSettingsHandler extends CoreSitePluginsBaseHandler i * @returns Data. */ getDisplayData(): CoreSettingsHandlerData { + const handlerName = CoreSitePlugins.getHandlerNameFromUniqueName(this.name, this.plugin.addon); + return { title: this.title, icon: this.handlerSchema.displaydata?.icon, @@ -54,6 +57,7 @@ export class CoreSitePluginsSettingsHandler extends CoreSitePluginsBaseHandler i title: this.title, initResult: this.initResult, ptrEnabled: this.handlerSchema.ptrenabled, + handlerName, }, }; } diff --git a/src/core/features/siteplugins/classes/handlers/user-handler.ts b/src/core/features/siteplugins/classes/handlers/user-handler.ts index a8a50d07a4c..04968481a11 100644 --- a/src/core/features/siteplugins/classes/handlers/user-handler.ts +++ b/src/core/features/siteplugins/classes/handlers/user-handler.ts @@ -86,6 +86,7 @@ export class CoreSitePluginsUserProfileHandler extends CoreSitePluginsBaseHandle * @inheritdoc */ getDisplayData(): CoreUserProfileHandlerData { + return { title: this.title, icon: this.handlerSchema.displaydata?.icon, @@ -94,6 +95,8 @@ export class CoreSitePluginsUserProfileHandler extends CoreSitePluginsBaseHandle event.preventDefault(); event.stopPropagation(); + const handlerName = CoreSitePlugins.getHandlerNameFromUniqueName(this.name, this.plugin.addon); + const args = { courseid: contextId, userid: user.id, @@ -108,6 +111,7 @@ export class CoreSitePluginsUserProfileHandler extends CoreSitePluginsBaseHandle args, initResult: this.initResult, ptrEnabled: this.handlerSchema.ptrenabled, + handlerName, }, }, ); diff --git a/src/core/features/siteplugins/components/assign-feedback/assign-feedback.ts b/src/core/features/siteplugins/components/assign-feedback/assign-feedback.ts index 213f4361a83..6d6ff96b6dc 100644 --- a/src/core/features/siteplugins/components/assign-feedback/assign-feedback.ts +++ b/src/core/features/siteplugins/components/assign-feedback/assign-feedback.ts @@ -39,7 +39,7 @@ export class CoreSitePluginsAssignFeedbackComponent extends CoreSitePluginsCompi /** * @inheritdoc */ - ngOnInit(): void { + async ngOnInit(): Promise { // Pass the input and output data to the component. this.jsData.assign = this.assign; this.jsData.submission = this.submission; @@ -50,7 +50,7 @@ export class CoreSitePluginsAssignFeedbackComponent extends CoreSitePluginsCompi this.jsData.canEdit = this.canEdit; if (this.plugin) { - this.getHandlerData(AddonModAssignFeedbackDelegate.getHandlerName(this.plugin.type)); + await this.getHandlerData(AddonModAssignFeedbackDelegate.getHandlerName(this.plugin.type)); } } diff --git a/src/core/features/siteplugins/components/assign-feedback/core-siteplugins-assign-feedback.html b/src/core/features/siteplugins/components/assign-feedback/core-siteplugins-assign-feedback.html index 469fc233ee0..823da036b30 100644 --- a/src/core/features/siteplugins/components/assign-feedback/core-siteplugins-assign-feedback.html +++ b/src/core/features/siteplugins/components/assign-feedback/core-siteplugins-assign-feedback.html @@ -1 +1 @@ - + diff --git a/src/core/features/siteplugins/components/assign-submission/assign-submission.ts b/src/core/features/siteplugins/components/assign-submission/assign-submission.ts index ed723258719..9c78f23762f 100644 --- a/src/core/features/siteplugins/components/assign-submission/assign-submission.ts +++ b/src/core/features/siteplugins/components/assign-submission/assign-submission.ts @@ -38,7 +38,7 @@ export class CoreSitePluginsAssignSubmissionComponent extends CoreSitePluginsCom /** * @inheritdoc */ - ngOnInit(): void { + async ngOnInit(): Promise { // Pass the input and output data to the component. this.jsData.assign = this.assign; this.jsData.submission = this.submission; @@ -48,7 +48,7 @@ export class CoreSitePluginsAssignSubmissionComponent extends CoreSitePluginsCom this.jsData.allowOffline = this.allowOffline; if (this.plugin) { - this.getHandlerData(AddonModAssignSubmissionDelegate.getHandlerName(this.plugin.type)); + await this.getHandlerData(AddonModAssignSubmissionDelegate.getHandlerName(this.plugin.type)); } } diff --git a/src/core/features/siteplugins/components/assign-submission/core-siteplugins-assign-submission.html b/src/core/features/siteplugins/components/assign-submission/core-siteplugins-assign-submission.html index 469fc233ee0..823da036b30 100644 --- a/src/core/features/siteplugins/components/assign-submission/core-siteplugins-assign-submission.html +++ b/src/core/features/siteplugins/components/assign-submission/core-siteplugins-assign-submission.html @@ -1 +1 @@ - + diff --git a/src/core/features/siteplugins/components/block/block.ts b/src/core/features/siteplugins/components/block/block.ts index 624924ec104..9a00aec5d82 100644 --- a/src/core/features/siteplugins/components/block/block.ts +++ b/src/core/features/siteplugins/components/block/block.ts @@ -36,15 +36,16 @@ export class CoreSitePluginsBlockComponent extends CoreBlockBaseComponent implem args?: Record; jsData?: Record; // Data to pass to the component. initResult?: CoreSitePluginsContent | null; + stylesPath?: string; // Styles to apply to the component. constructor() { super('CoreSitePluginsBlockComponent'); } /** - * Detect changes on input properties. + * @inheritdoc */ - ngOnChanges(): void { + async ngOnChanges(): Promise { if (this.component) { return; } @@ -67,12 +68,12 @@ export class CoreSitePluginsBlockComponent extends CoreBlockBaseComponent implem block: this.block, }; this.initResult = handler.initResult; + + this.stylesPath = await CoreSitePlugins.getHandlerDownloadedStyles(handlerName); } /** - * Invalidate block data. - * - * @returns Promise resolved when done. + * @inheritdoc */ async invalidateContent(): Promise { if (!this.component || !this.method) { diff --git a/src/core/features/siteplugins/components/block/core-siteplugins-block.html b/src/core/features/siteplugins/components/block/core-siteplugins-block.html index fb5b9bdf98c..4843e25ba7a 100644 --- a/src/core/features/siteplugins/components/block/core-siteplugins-block.html +++ b/src/core/features/siteplugins/components/block/core-siteplugins-block.html @@ -1,2 +1,2 @@ + [initResult]="initResult" [data]="jsData" [stylesPath]="stylesPath" /> diff --git a/src/core/features/siteplugins/components/course-format/core-siteplugins-course-format.html b/src/core/features/siteplugins/components/course-format/core-siteplugins-course-format.html index 8ccd604a3cd..60f291239b4 100644 --- a/src/core/features/siteplugins/components/course-format/core-siteplugins-course-format.html +++ b/src/core/features/siteplugins/components/course-format/core-siteplugins-course-format.html @@ -1,2 +1,2 @@ + [initResult]="initResult" [data]="data" [stylesPath]="stylesPath" /> diff --git a/src/core/features/siteplugins/components/course-format/course-format.ts b/src/core/features/siteplugins/components/course-format/course-format.ts index 26e2de463cb..38179e47079 100644 --- a/src/core/features/siteplugins/components/course-format/course-format.ts +++ b/src/core/features/siteplugins/components/course-format/course-format.ts @@ -49,11 +49,12 @@ export class CoreSitePluginsCourseFormatComponent implements OnChanges { args?: Record; initResult?: CoreSitePluginsContent | null; data?: Record; + stylesPath?: string; // Styles to apply to the component. /** * @inheritdoc */ - ngOnChanges(): void { + async ngOnChanges(): Promise { if (!this.course || !this.course.format) { return; } @@ -70,6 +71,8 @@ export class CoreSitePluginsCourseFormatComponent implements OnChanges { courseid: this.course.id, }; this.initResult = handler.initResult; + + this.stylesPath = await CoreSitePlugins.getHandlerDownloadedStyles(handlerName); } } diff --git a/src/core/features/siteplugins/components/module-index/core-siteplugins-module-index.html b/src/core/features/siteplugins/components/module-index/core-siteplugins-module-index.html index cee8e615ed1..2be9be11929 100644 --- a/src/core/features/siteplugins/components/module-index/core-siteplugins-module-index.html +++ b/src/core/features/siteplugins/components/module-index/core-siteplugins-module-index.html @@ -11,7 +11,7 @@ + (onLoadingContent)="contentLoading()" [stylesPath]="stylesPath" /> diff --git a/src/core/features/siteplugins/components/module-index/module-index.ts b/src/core/features/siteplugins/components/module-index/module-index.ts index f7e5a01808b..dac14566d6f 100644 --- a/src/core/features/siteplugins/components/module-index/module-index.ts +++ b/src/core/features/siteplugins/components/module-index/module-index.ts @@ -73,11 +73,12 @@ export class CoreSitePluginsModuleIndexComponent implements OnInit, OnDestroy, C isDestroyed = false; jsData?: Record; // Data to pass to the component. + stylesPath?: string; // Styles to apply to the component. /** * @inheritdoc */ - ngOnInit(): void { + async ngOnInit(): Promise { if (!this.module) { return; } @@ -110,6 +111,8 @@ export class CoreSitePluginsModuleIndexComponent implements OnInit, OnDestroy, C this.ptrEnabled = !CoreUtils.isFalseOrZero(handlerSchema.ptrenabled); this.collapsibleFooterAppearOnBottom = !CoreUtils.isFalseOrZero(handlerSchema.isresource); + + this.stylesPath = await CoreSitePlugins.getHandlerDownloadedStyles(handlerName); } // Get the data for the context menu. @@ -132,6 +135,8 @@ export class CoreSitePluginsModuleIndexComponent implements OnInit, OnDestroy, C /** * Function called when the data of the site plugin content is loaded. + * + * @param data Data received. */ contentLoaded(data: CoreSitePluginsPluginContentLoadedData): void { this.addDefaultModuleInfo = !data.content.includes(' handler.handlerSchema).ptrenabled, + handlerName, }, }, ); diff --git a/src/core/features/siteplugins/components/plugin-content/core-siteplugins-plugin-content.html b/src/core/features/siteplugins/components/plugin-content/core-siteplugins-plugin-content.html index 4ae340d7691..4e13a42e1a0 100644 --- a/src/core/features/siteplugins/components/plugin-content/core-siteplugins-plugin-content.html +++ b/src/core/features/siteplugins/components/plugin-content/core-siteplugins-plugin-content.html @@ -1,3 +1,4 @@ - + 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 49634c4a92b..01650e24201 100644 --- a/src/core/features/siteplugins/components/plugin-content/plugin-content.ts +++ b/src/core/features/siteplugins/components/plugin-content/plugin-content.ts @@ -54,6 +54,7 @@ export class CoreSitePluginsPluginContentComponent implements OnInit, DoCheck { @Input() args?: Record; @Input() initResult?: CoreSitePluginsContent | null; // Result of the init WS call of the handler. @Input() data: Record = {}; // Data to pass to the component. + @Input() stylesPath = ''; // Styles. @Input() preSets?: CoreSiteWSPreSets; // The preSets for the WS call. @Input() pageTitle?: string; // Current page title. It can be used by the "new-content" directives. @Output() onContentLoaded = new EventEmitter(); // Emits event when content is loaded. @@ -134,7 +135,8 @@ export class CoreSitePluginsPluginContentComponent implements OnInit, DoCheck { jsData?: Record | boolean, preSets?: CoreSiteWSPreSets, ptrEnabled?: boolean, - ) => this.openContent(title, args, component, method, jsData, preSets, ptrEnabled); + stylesPath?: string, + ) => this.openContent(title, args, component, method, jsData, preSets, ptrEnabled, stylesPath); this.jsData.refreshContent = (showSpinner?: boolean) => this.refreshContent(showSpinner); this.jsData.updateContent = ( args?: Record, @@ -170,6 +172,7 @@ export class CoreSitePluginsPluginContentComponent implements OnInit, DoCheck { * If true is supplied instead of an object, all initial variables from current page will be copied. * @param preSets The preSets for the WS call of the new content. * @param ptrEnabled Whether PTR should be enabled in the new page. Defaults to true. + * @param stylesPath Styles to apply to the component. */ openContent( title: string, @@ -179,6 +182,7 @@ export class CoreSitePluginsPluginContentComponent implements OnInit, DoCheck { jsData?: Record | boolean, preSets?: CoreSiteWSPreSets, ptrEnabled?: boolean, + stylesPath?: string, ): void { if (jsData === true) { jsData = this.data; @@ -196,6 +200,7 @@ export class CoreSitePluginsPluginContentComponent implements OnInit, DoCheck { jsData, preSets, ptrEnabled, + stylesPath, }, }); } diff --git a/src/core/features/siteplugins/components/question-behaviour/core-siteplugins-question-behaviour.html b/src/core/features/siteplugins/components/question-behaviour/core-siteplugins-question-behaviour.html index 469fc233ee0..823da036b30 100644 --- a/src/core/features/siteplugins/components/question-behaviour/core-siteplugins-question-behaviour.html +++ b/src/core/features/siteplugins/components/question-behaviour/core-siteplugins-question-behaviour.html @@ -1 +1 @@ - + diff --git a/src/core/features/siteplugins/components/question-behaviour/question-behaviour.ts b/src/core/features/siteplugins/components/question-behaviour/question-behaviour.ts index 8eed777762a..e57439fe64c 100644 --- a/src/core/features/siteplugins/components/question-behaviour/question-behaviour.ts +++ b/src/core/features/siteplugins/components/question-behaviour/question-behaviour.ts @@ -49,7 +49,7 @@ export class CoreSitePluginsQuestionBehaviourComponent extends CoreSitePluginsCo /** * @inheritdoc */ - ngOnInit(): void { + async ngOnInit(): Promise { // Pass the input and output data to the component. this.jsData.question = this.question; this.jsData.component = this.component; @@ -62,7 +62,7 @@ export class CoreSitePluginsQuestionBehaviourComponent extends CoreSitePluginsCo this.jsData.onAbort = this.onAbort; if (this.question) { - this.getHandlerData(CoreQuestionBehaviourDelegate.getHandlerName(this.preferredBehaviour || '')); + await this.getHandlerData(CoreQuestionBehaviourDelegate.getHandlerName(this.preferredBehaviour || '')); } } diff --git a/src/core/features/siteplugins/components/question/core-siteplugins-question.html b/src/core/features/siteplugins/components/question/core-siteplugins-question.html index 469fc233ee0..823da036b30 100644 --- a/src/core/features/siteplugins/components/question/core-siteplugins-question.html +++ b/src/core/features/siteplugins/components/question/core-siteplugins-question.html @@ -1 +1 @@ - + diff --git a/src/core/features/siteplugins/components/question/question.ts b/src/core/features/siteplugins/components/question/question.ts index 8c9bd7a39ac..09f0b2af4d3 100644 --- a/src/core/features/siteplugins/components/question/question.ts +++ b/src/core/features/siteplugins/components/question/question.ts @@ -46,7 +46,7 @@ export class CoreSitePluginsQuestionComponent extends CoreSitePluginsCompileInit /** * @inheritdoc */ - ngOnInit(): void { + async ngOnInit(): Promise { // Pass the input and output data to the component. this.jsData.question = this.question; this.jsData.component = this.component; @@ -62,7 +62,7 @@ export class CoreSitePluginsQuestionComponent extends CoreSitePluginsCompileInit this.jsData.onAbort = this.onAbort; if (this.question) { - this.getHandlerData(CoreQuestionDelegate.getHandlerName('qtype_' + this.question.type)); + await this.getHandlerData(CoreQuestionDelegate.getHandlerName('qtype_' + this.question.type)); } } diff --git a/src/core/features/siteplugins/components/quiz-access-rule/core-siteplugins-quiz-access-rule.html b/src/core/features/siteplugins/components/quiz-access-rule/core-siteplugins-quiz-access-rule.html index 469fc233ee0..823da036b30 100644 --- a/src/core/features/siteplugins/components/quiz-access-rule/core-siteplugins-quiz-access-rule.html +++ b/src/core/features/siteplugins/components/quiz-access-rule/core-siteplugins-quiz-access-rule.html @@ -1 +1 @@ - + diff --git a/src/core/features/siteplugins/components/quiz-access-rule/quiz-access-rule.ts b/src/core/features/siteplugins/components/quiz-access-rule/quiz-access-rule.ts index 7c4c2a50223..bed2e0855f5 100644 --- a/src/core/features/siteplugins/components/quiz-access-rule/quiz-access-rule.ts +++ b/src/core/features/siteplugins/components/quiz-access-rule/quiz-access-rule.ts @@ -39,7 +39,7 @@ export class CoreSitePluginsQuizAccessRuleComponent extends CoreSitePluginsCompi /** * @inheritdoc */ - ngOnInit(): void { + async ngOnInit(): Promise { // Pass the input and output data to the component. this.jsData.rule = this.rule; this.jsData.quiz = this.quiz; @@ -49,7 +49,7 @@ export class CoreSitePluginsQuizAccessRuleComponent extends CoreSitePluginsCompi this.jsData.form = this.form; if (this.rule) { - this.getHandlerData(AddonModQuizAccessRuleDelegate.getHandlerName(this.rule)); + await this.getHandlerData(AddonModQuizAccessRuleDelegate.getHandlerName(this.rule)); } } diff --git a/src/core/features/siteplugins/components/user-profile-field/core-siteplugins-user-profile-field.html b/src/core/features/siteplugins/components/user-profile-field/core-siteplugins-user-profile-field.html index 469fc233ee0..823da036b30 100644 --- a/src/core/features/siteplugins/components/user-profile-field/core-siteplugins-user-profile-field.html +++ b/src/core/features/siteplugins/components/user-profile-field/core-siteplugins-user-profile-field.html @@ -1 +1 @@ - + diff --git a/src/core/features/siteplugins/components/user-profile-field/user-profile-field.ts b/src/core/features/siteplugins/components/user-profile-field/user-profile-field.ts index f67f4802129..b34b0d1b3ff 100644 --- a/src/core/features/siteplugins/components/user-profile-field/user-profile-field.ts +++ b/src/core/features/siteplugins/components/user-profile-field/user-profile-field.ts @@ -44,7 +44,7 @@ export class CoreSitePluginsUserProfileFieldComponent extends CoreSitePluginsCom /** * @inheritdoc */ - ngOnInit(): void { + async ngOnInit(): Promise { // Pass the input data to the component. this.jsData.field = this.field; this.jsData.signup = this.signup; @@ -55,7 +55,7 @@ export class CoreSitePluginsUserProfileFieldComponent extends CoreSitePluginsCom if (this.field) { const type = 'type' in this.field ? this.field.type : this.field.datatype; - this.getHandlerData(CoreUserProfileFieldDelegate.getHandlerName(type || '')); + await this.getHandlerData(CoreUserProfileFieldDelegate.getHandlerName(type || '')); } } diff --git a/src/core/features/siteplugins/components/workshop-assessment-strategy/core-siteplugins-workshop-assessment-strategy.html b/src/core/features/siteplugins/components/workshop-assessment-strategy/core-siteplugins-workshop-assessment-strategy.html index 469fc233ee0..823da036b30 100644 --- a/src/core/features/siteplugins/components/workshop-assessment-strategy/core-siteplugins-workshop-assessment-strategy.html +++ b/src/core/features/siteplugins/components/workshop-assessment-strategy/core-siteplugins-workshop-assessment-strategy.html @@ -1 +1 @@ - + diff --git a/src/core/features/siteplugins/components/workshop-assessment-strategy/workshop-assessment-strategy.ts b/src/core/features/siteplugins/components/workshop-assessment-strategy/workshop-assessment-strategy.ts index 2030ab47427..859cd92c359 100644 --- a/src/core/features/siteplugins/components/workshop-assessment-strategy/workshop-assessment-strategy.ts +++ b/src/core/features/siteplugins/components/workshop-assessment-strategy/workshop-assessment-strategy.ts @@ -40,7 +40,7 @@ export class CoreSitePluginsWorkshopAssessmentStrategyComponent extends CoreSite /** * @inheritdoc */ - ngOnInit(): void { + async ngOnInit(): Promise { // Pass the input and output data to the component. this.jsData.workshopId = this.workshopId; this.jsData.assessment = this.assessment; @@ -49,7 +49,7 @@ export class CoreSitePluginsWorkshopAssessmentStrategyComponent extends CoreSite this.jsData.fieldErrors = this.fieldErrors; this.jsData.strategy = this.strategy; - this.getHandlerData(AddonWorkshopAssessmentStrategyDelegate.getHandlerName(this.strategy)); + await this.getHandlerData(AddonWorkshopAssessmentStrategyDelegate.getHandlerName(this.strategy)); } } diff --git a/src/core/features/siteplugins/directives/call-ws-new-content.ts b/src/core/features/siteplugins/directives/call-ws-new-content.ts index 729a8130aa6..b4ff258efb8 100644 --- a/src/core/features/siteplugins/directives/call-ws-new-content.ts +++ b/src/core/features/siteplugins/directives/call-ws-new-content.ts @@ -109,6 +109,7 @@ export class CoreSitePluginsCallWSNewContentDirective extends CoreSitePluginsCal jsData, preSets: this.newContentPreSets, ptrEnabled: this.ptrEnabled, + stylesPath: this.parentContent?.stylesPath, }, }); } diff --git a/src/core/features/siteplugins/directives/new-content.ts b/src/core/features/siteplugins/directives/new-content.ts index a22d57e2c76..e50e8083ec3 100644 --- a/src/core/features/siteplugins/directives/new-content.ts +++ b/src/core/features/siteplugins/directives/new-content.ts @@ -108,6 +108,7 @@ export class CoreSitePluginsNewContentDirective implements OnInit { jsData, preSets: this.preSets, ptrEnabled: this.ptrEnabled, + stylesPath: this.parentContent?.stylesPath, }, }); } diff --git a/src/core/features/siteplugins/pages/course-option/core-siteplugins-course-option.html b/src/core/features/siteplugins/pages/course-option/core-siteplugins-course-option.html index d34f71c555f..54bba01fa96 100644 --- a/src/core/features/siteplugins/pages/course-option/core-siteplugins-course-option.html +++ b/src/core/features/siteplugins/pages/course-option/core-siteplugins-course-option.html @@ -1,8 +1,7 @@ - + + [initResult]="initResult" [stylesPath]="stylesPath" /> diff --git a/src/core/features/siteplugins/pages/course-option/course-option.ts b/src/core/features/siteplugins/pages/course-option/course-option.ts index 890bcbe2329..de910a2dfe3 100644 --- a/src/core/features/siteplugins/pages/course-option/course-option.ts +++ b/src/core/features/siteplugins/pages/course-option/course-option.ts @@ -30,26 +30,25 @@ export class CoreSitePluginsCourseOptionPage implements OnInit { @ViewChild(CoreSitePluginsPluginContentComponent) content?: CoreSitePluginsPluginContentComponent; - courseId?: number; - handlerUniqueName?: string; component?: string; method?: string; args?: Record; initResult?: CoreSitePluginsContent | null; ptrEnabled = true; + stylesPath?: string; // Styles to apply to the component. /** * @inheritdoc */ - ngOnInit(): void { - this.courseId = CoreNavigator.getRouteNumberParam('courseId'); - this.handlerUniqueName = CoreNavigator.getRouteParam('handlerUniqueName'); + async ngOnInit(): Promise { + const courseId = CoreNavigator.getRouteNumberParam('courseId'); + const handlerName = CoreNavigator.getRouteParam('handlerUniqueName'); - if (!this.handlerUniqueName) { + if (!handlerName) { return; } - const handler = CoreSitePlugins.getSitePluginHandler(this.handlerUniqueName); + const handler = CoreSitePlugins.getSitePluginHandler(handlerName); if (!handler) { return; } @@ -57,11 +56,13 @@ export class CoreSitePluginsCourseOptionPage implements OnInit { this.component = handler.plugin.component; this.method = handler.handlerSchema.method; this.args = { - courseid: this.courseId, + courseid: courseId, }; this.initResult = handler.initResult; this.ptrEnabled = !('ptrenabled' in handler.handlerSchema) || !CoreUtils.isFalseOrZero(handler.handlerSchema.ptrenabled); + + this.stylesPath = await CoreSitePlugins.getHandlerDownloadedStyles(handlerName); } /** diff --git a/src/core/features/siteplugins/pages/plugin/plugin.html b/src/core/features/siteplugins/pages/plugin/plugin.html index ee8ef621c14..356240c058d 100644 --- a/src/core/features/siteplugins/pages/plugin/plugin.html +++ b/src/core/features/siteplugins/pages/plugin/plugin.html @@ -15,5 +15,5 @@

{{ title | translate }}

+ [data]="jsData" [pageTitle]="title" [stylesPath]="stylesPath" /> diff --git a/src/core/features/siteplugins/pages/plugin/plugin.ts b/src/core/features/siteplugins/pages/plugin/plugin.ts index 4571faab02f..13ab6180360 100644 --- a/src/core/features/siteplugins/pages/plugin/plugin.ts +++ b/src/core/features/siteplugins/pages/plugin/plugin.ts @@ -15,7 +15,7 @@ import { Component, OnInit, ViewChild } from '@angular/core'; import { CoreSiteWSPreSets } from '@classes/sites/authenticated-site'; -import { CoreSitePluginsContent } from '@features/siteplugins/services/siteplugins'; +import { CoreSitePlugins, CoreSitePluginsContent } from '@features/siteplugins/services/siteplugins'; import { CanLeave } from '@guards/can-leave'; import { CoreNavigator } from '@services/navigator'; import { CoreUtils } from '@services/utils/utils'; @@ -40,11 +40,12 @@ export class CoreSitePluginsPluginPage implements OnInit, CanLeave { 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; + stylesPath?: string; // Styles to apply to the component. /** * @inheritdoc */ - ngOnInit(): void { + async ngOnInit(): Promise { this.title = CoreNavigator.getRouteParam('title'); this.component = CoreNavigator.getRouteParam('component'); this.method = CoreNavigator.getRouteParam('method'); @@ -53,6 +54,12 @@ export class CoreSitePluginsPluginPage implements OnInit, CanLeave { this.jsData = CoreNavigator.getRouteParam('jsData'); this.preSets = CoreNavigator.getRouteParam('preSets'); this.ptrEnabled = !CoreUtils.isFalseOrZero(CoreNavigator.getRouteBooleanParam('ptrEnabled')); + + this.stylesPath = CoreNavigator.getRouteParam('stylesPath'); + if (!this.stylesPath) { + const handlerName = CoreNavigator.getRouteParam('handlerName'); + this.stylesPath = await CoreSitePlugins.getHandlerDownloadedStyles(handlerName); + } } /** diff --git a/src/core/features/siteplugins/services/siteplugins-init.ts b/src/core/features/siteplugins/services/siteplugins-init.ts index fc48549199d..c62506516ce 100644 --- a/src/core/features/siteplugins/services/siteplugins-init.ts +++ b/src/core/features/siteplugins/services/siteplugins-init.ts @@ -89,6 +89,7 @@ import { CorePath } from '@singletons/path'; import { CoreEnrolAction, CoreEnrolDelegate } from '@features/enrol/services/enrol-delegate'; import { CoreSitePluginsEnrolHandler } from '../classes/handlers/enrol-handler'; import { CORE_SITE_PLUGINS_COMPONENT } from '../constants'; +import { CorePromisedValue } from '@classes/promised-value'; /** * Helper service to provide functionalities regarding site plugins. It basically has the features to load and register site @@ -158,68 +159,105 @@ export class CoreSitePluginsInitService { * @param handlerName Name of the handler in the plugin. * @param handlerSchema Data about the handler. * @param siteId Site ID. If not provided, current site. - * @returns Promise resolved with the CSS code. */ protected async downloadStyles( plugin: CoreSitePluginsPlugin, handlerName: string, handlerSchema: CoreSitePluginsHandlerData, - siteId?: string, - ): Promise { - const site = await CoreSites.getSite(siteId); + siteId: string, + ): Promise { + try { + if (handlerSchema.styles?.downloadedStyles) { + return; + } - // Make sure it's an absolute URL. Do not use toAbsoluteURL because it can change the behaviour and break plugin styles. - let url = handlerSchema.styles?.url; - if (url && !CoreUrlUtils.isAbsoluteURL(url)) { - url = CorePath.concatenatePaths(site.getURL(), url); - } + const uniqueName = CoreSitePlugins.getHandlerUniqueName(plugin, handlerName); + const componentId = uniqueName + '#main'; - if (url && handlerSchema.styles?.version) { - // Add the version to the URL to prevent getting a cached file. - url += (url.indexOf('?') != -1 ? '&' : '?') + 'version=' + handlerSchema.styles.version; - } + if (!handlerSchema.styles?.url) { + // No styles. Clear previous styles if any. + await this.clearPreviousStyles(componentId, siteId); - const uniqueName = CoreSitePlugins.getHandlerUniqueName(plugin, handlerName); - const componentId = uniqueName + '#main'; + if (handlerSchema.styles) { + handlerSchema.styles = undefined; + } + + return; + } + + const site = await CoreSites.getSite(siteId); + siteId = site.getId(); + + // Make sure it's an absolute URL. Do not use toAbsoluteURL because it can change the behaviour and break plugin styles. + let fileUrl = handlerSchema.styles.url; + const version = handlerSchema.styles.version; + + if (!CoreUrlUtils.isAbsoluteURL(fileUrl)) { + fileUrl = CorePath.concatenatePaths(site.getURL(), fileUrl); + } + + if (version) { + // Add the version to the URL to prevent getting a cached file. + fileUrl = CoreUrlUtils.addParamsToUrl(fileUrl, { version }); + } + + await this.clearPreviousStyles(componentId, siteId, fileUrl); + + handlerSchema.styles.downloadedStyles = new CorePromisedValue(); + // Download the file if not downloaded or the version changed. + const path = await CoreFilepool.downloadUrl( + siteId, + fileUrl, + false, + CORE_SITE_PLUGINS_COMPONENT, + componentId, + 0, + undefined, + undefined, + undefined, + handlerSchema.styles.version, + ); + + const cssCode = await CoreWS.getText(path); + + await CoreUtils.ignoreErrors( + CoreFilepool.treatCSSCode(siteId, fileUrl, cssCode, CORE_SITE_PLUGINS_COMPONENT, uniqueName, version), + ); + + handlerSchema.styles.downloadedStyles.resolve(path); + } catch (error) { + this.logger.error('Error getting styles for plugin', handlerName, handlerSchema, error); + if (handlerSchema.styles?.downloadedStyles) { + handlerSchema.styles.downloadedStyles.reject(error); + } + } + } + + /** + * Clear previous styles for a handler. + * This function will remove all the CSS files for the handler that aren't used anymore. + * + * @param componentId Component ID. + * @param siteId Site ID. + * @param url URL of the current CSS file. If not provided, all the CSS files for the handler will be removed. + */ + protected async clearPreviousStyles( + componentId: string, + siteId: string, + url?: string, + ): Promise { // Remove the CSS files for this handler that aren't used anymore. Don't block the call for this. const files = await CoreUtils.ignoreErrors( - CoreFilepool.getFilesByComponent(site.getId(), CORE_SITE_PLUGINS_COMPONENT, componentId), + CoreFilepool.getFilesByComponent(siteId, CORE_SITE_PLUGINS_COMPONENT, componentId), ); files?.forEach((file) => { if (file.url !== url) { // It's not the current file, delete it. - CoreUtils.ignoreErrors(CoreFilepool.removeFileByUrl(site.getId(), file.url)); + CoreUtils.ignoreErrors(CoreFilepool.removeFileByUrl(siteId, file.url)); } }); - - if (!url) { - // No styles. - return ''; - } - - // Update the schema with the final CSS URL. - if (handlerSchema.styles) { - handlerSchema.styles.url = url; - } - - // Download the file if not downloaded or the version changed. - const path = await CoreFilepool.downloadUrl( - site.getId(), - url, - false, - CORE_SITE_PLUGINS_COMPONENT, - componentId, - 0, - undefined, - undefined, - undefined, - handlerSchema.styles?.version, - ); - - // File is downloaded, get the contents. - return CoreWS.getText(path); } /** @@ -394,21 +432,28 @@ export class CoreSitePluginsInitService { * * @param plugin Data of the plugin. * @param handlerName Name of the handler in the plugin. - * @param fileUrl CSS file URL. - * @param cssCode CSS code. - * @param version Styles version. + * @param handlerSchema Data about the handler. * @param siteId Site ID. If not provided, current site. */ - protected loadStyles( + protected async loadStyles( plugin: CoreSitePluginsPlugin, handlerName: string, - fileUrl: string, - cssCode: string, - version?: number, + handlerSchema: CoreSitePluginsHandlerData, siteId?: string, - ): void { + ): Promise { siteId = siteId || CoreSites.getCurrentSiteId(); + this.downloadStyles(plugin, handlerName, handlerSchema, siteId); + + const path = await CoreSitePlugins.getHandlerDownloadedStyles(handlerName); + if (!path || !handlerSchema.styles?.url) { + return; + } + const fileUrl = handlerSchema.styles.url; + const version = handlerSchema.styles.version; + + const cssCode = await CoreWS.getText(path); + // Create the style and add it to the header. const styleEl = document.createElement('style'); const uniqueName = CoreSitePlugins.getHandlerUniqueName(plugin, handlerName); @@ -454,12 +499,7 @@ export class CoreSitePluginsInitService { const siteId = CoreSites.getCurrentSiteId(); try { - const [initResult, cssCode] = await Promise.all([ - this.executeHandlerInit(plugin, handlerSchema), - this.downloadStyles(plugin, handlerName, handlerSchema, siteId).catch((error) => { - this.logger.error('Error getting styles for plugin', handlerName, handlerSchema, error); - }), - ]); + const initResult = await this.executeHandlerInit(plugin, handlerSchema); if (initResult?.disabled) { // This handler is disabled for the current user, stop. @@ -468,10 +508,7 @@ export class CoreSitePluginsInitService { return; } - if (cssCode && handlerSchema.styles?.url) { - // Load the styles. - this.loadStyles(plugin, handlerName, handlerSchema.styles.url, cssCode, handlerSchema.styles.version, siteId); - } + let loadStyles = true; let uniqueName: string | undefined; @@ -545,7 +582,16 @@ export class CoreSitePluginsInitService { break; default: + loadStyles = false; // Nothing to do. + this.loadStyles(plugin, handlerName, handlerSchema, siteId); + } + + if (loadStyles) { + // Load the styles without waiting for them to be downloaded. + this.downloadStyles(plugin, handlerName, handlerSchema, siteId); + } else { + handlerSchema.styles = undefined; } if (uniqueName) { diff --git a/src/core/features/siteplugins/services/siteplugins.ts b/src/core/features/siteplugins/services/siteplugins.ts index 30deff990c6..3137db77352 100644 --- a/src/core/features/siteplugins/services/siteplugins.ts +++ b/src/core/features/siteplugins/services/siteplugins.ts @@ -700,6 +700,35 @@ export class CoreSitePluginsProvider { this.moduleHandlerInstances[modName] = handler; } + /** + * Get the path to the downloaded styles of a handler. + * + * @param handlerName Handler's name. + * @returns Path to the downloaded styles. + */ + async getHandlerDownloadedStyles(handlerName?: string): Promise { + if (!handlerName) { + return ''; + } + const handler = this.getSitePluginHandler(handlerName); + if (!handler?.handlerSchema.styles?.downloadedStyles) { + return ''; + } + + return await handler.handlerSchema.styles.downloadedStyles; + } + + /** + * Get the name of the handler from a unique name. + * + * @param uniqueName Unique name of the handler. + * @param addon Addon name. + * @returns Handler name. + */ + getHandlerNameFromUniqueName(uniqueName: string, addon: string): string { + return uniqueName.replace(addon + '_', ''); + } + } export const CoreSitePlugins = makeSingleton(CoreSitePluginsProvider); @@ -839,6 +868,7 @@ export type CoreSitePluginsHandlerCommonData = { styles?: { url?: string; version?: number; + downloadedStyles?: CorePromisedValue; // Added in the app. Path resolved when styles are downloaded. }; moodlecomponent?: string; };