From d6007c2aaee1b5ce27ce1cef88295c5cdf538aea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Mon, 29 Jul 2024 11:46:23 +0200 Subject: [PATCH 1/8] MOBILE-4616 chore: Create CoreToastsService to add toasts functions --- src/addons/calendar/pages/event/event.ts | 15 ++- src/addons/mod/assign/pages/edit/edit.ts | 5 +- src/addons/mod/data/services/data-helper.ts | 19 ++-- .../discussion-options-menu.ts | 16 +++- .../mod/forum/components/index/index.ts | 6 +- src/addons/mod/forum/components/post/post.ts | 6 +- .../mod/forum/pages/discussion/discussion.ts | 16 +++- src/addons/mod/glossary/pages/entry/entry.ts | 9 +- src/addons/notes/components/add/add-modal.ts | 9 +- src/addons/notes/pages/list/list.ts | 9 +- .../services/privatefiles-helper.ts | 7 +- .../insights/services/handlers/action-link.ts | 14 ++- src/core/classes/sites/authenticated-site.ts | 8 +- src/core/core.module.ts | 2 + .../features/comments/pages/viewer/viewer.ts | 19 ++-- .../components/contactdpo/contactdpo.ts | 9 +- .../components/newrequest/newrequest.ts | 9 +- .../services/fileuploader-helper.ts | 7 +- .../features/rating/components/rate/rate.ts | 9 +- .../components/set-button/set-button.ts | 10 +- src/core/features/settings/pages/dev/dev.ts | 12 ++- .../settings/pages/deviceinfo/deviceinfo.ts | 7 +- src/core/features/settings/pages/site/site.ts | 6 +- .../pages/synchronization/synchronization.ts | 6 +- .../siteplugins/directives/call-ws.ts | 4 +- src/core/services/toasts.ts | 75 +++++++++++++++ src/core/services/utils/dom.ts | 95 ++++--------------- src/core/singletons/text.ts | 7 +- src/core/utils/fix-aria-hidden.ts | 50 ++++++++++ src/types/config.d.ts | 2 +- 30 files changed, 330 insertions(+), 138 deletions(-) create mode 100644 src/core/services/toasts.ts create mode 100644 src/core/utils/fix-aria-hidden.ts diff --git a/src/addons/calendar/pages/event/event.ts b/src/addons/calendar/pages/event/event.ts index 490aec001..39924a448 100644 --- a/src/addons/calendar/pages/event/event.ts +++ b/src/addons/calendar/pages/event/event.ts @@ -24,7 +24,7 @@ import { AddonCalendarOffline } from '../../services/calendar-offline'; import { AddonCalendarSync, AddonCalendarSyncEvents, AddonCalendarSyncProvider } from '../../services/calendar-sync'; import { CoreNetwork } from '@services/network'; import { CoreEventObserver, CoreEvents } from '@singletons/events'; -import { CoreDomUtils, ToastDuration } from '@services/utils/dom'; +import { CoreDomUtils } from '@services/utils/dom'; import { CoreTextUtils } from '@services/utils/text'; import { CoreSites } from '@services/sites'; import { CoreCourse } from '@features/course/services/course'; @@ -43,6 +43,7 @@ import { CoreRemindersSetReminderMenuComponent } from '@features/reminders/compo import { CoreLocalNotifications } from '@services/local-notifications'; import { CorePlatform } from '@services/platform'; import { CoreConfig } from '@services/config'; +import { CoreToasts, ToastDuration } from '@services/toasts'; /** * Page that displays a single calendar event. @@ -556,7 +557,11 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy { } if (onlineEventDeleted || this.event.id < 0) { - CoreDomUtils.showToast('addon.calendar.eventcalendareventdeleted', true, ToastDuration.LONG); + CoreToasts.show({ + message: 'addon.calendar.eventcalendareventdeleted', + translateMessage: true, + duration: ToastDuration.LONG, + }); // Event deleted, close the view. CoreNavigator.back(); @@ -611,7 +616,11 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy { } if (data.deleted && data.deleted.indexOf(this.eventId) != -1) { - CoreDomUtils.showToast('addon.calendar.eventcalendareventdeleted', true, ToastDuration.LONG); + CoreToasts.show({ + message: 'addon.calendar.eventcalendareventdeleted', + translateMessage: true, + duration: ToastDuration.LONG, + }); // Event was deleted, close the view. CoreNavigator.back(); diff --git a/src/addons/mod/assign/pages/edit/edit.ts b/src/addons/mod/assign/pages/edit/edit.ts index e421deaf0..836927079 100644 --- a/src/addons/mod/assign/pages/edit/edit.ts +++ b/src/addons/mod/assign/pages/edit/edit.ts @@ -20,7 +20,7 @@ import { CanLeave } from '@guards/can-leave'; import { CoreNavigator } from '@services/navigator'; import { CoreSites, CoreSitesReadingStrategy } from '@services/sites'; import { CoreSync } from '@services/sync'; -import { CoreDomUtils, ToastDuration } from '@services/utils/dom'; +import { CoreDomUtils } from '@services/utils/dom'; import { CoreFormFields, CoreForms } from '@singletons/form'; import { Translate } from '@singletons'; import { CoreEvents } from '@singletons/events'; @@ -45,6 +45,7 @@ import { ADDON_MOD_ASSIGN_SUBMISSION_SAVED_EVENT, ADDON_MOD_ASSIGN_SUBMITTED_FOR_GRADING_EVENT, } from '../../constants'; +import { CoreToasts, ToastDuration } from '@services/toasts'; /** * Page that allows adding or editing an assigment submission. @@ -488,7 +489,7 @@ export class AddonModAssignEditPage implements OnInit, OnDestroy, CanLeave { * Function called when the time is up. */ async timeUp(): Promise { - this.timeUpToast = await CoreDomUtils.showToastWithOptions({ + this.timeUpToast = await CoreToasts.show({ message: Translate.instant('addon.mod_assign.caneditsubmission'), duration: ToastDuration.STICKY, buttons: [Translate.instant('core.dismiss')], diff --git a/src/addons/mod/data/services/data-helper.ts b/src/addons/mod/data/services/data-helper.ts index ee443d507..faaeeb5f9 100644 --- a/src/addons/mod/data/services/data-helper.ts +++ b/src/addons/mod/data/services/data-helper.ts @@ -19,7 +19,7 @@ import { CoreFileUploader, CoreFileUploaderStoreFilesResult } from '@features/fi import { CoreRatingOffline } from '@features/rating/services/rating-offline'; import { FileEntry } from '@awesome-cordova-plugins/file/ngx'; import { CoreSites, CoreSitesReadingStrategy } from '@services/sites'; -import { CoreDomUtils, ToastDuration } from '@services/utils/dom'; +import { CoreDomUtils } from '@services/utils/dom'; import { CoreFormFields } from '@singletons/form'; import { CoreTextUtils } from '@services/utils/text'; import { CoreUtils } from '@services/utils/utils'; @@ -47,6 +47,7 @@ import { AddonModDataTemplateType, AddonModDataTemplateMode, } from '../constants'; +import { CoreToasts, ToastDuration } from '@services/toasts'; /** * Service that provides helper functions for datas. @@ -176,11 +177,11 @@ export class AddonModDataHelperProvider { CoreEvents.trigger(ADDON_MOD_DATA_ENTRY_CHANGED, { dataId: dataId, entryId: entryId }, siteId); - CoreDomUtils.showToast( - approve ? 'addon.mod_data.recordapproved' : 'addon.mod_data.recorddisapproved', - true, - ToastDuration.LONG, - ); + CoreToasts.show({ + message: approve ? 'addon.mod_data.recordapproved' : 'addon.mod_data.recorddisapproved', + translateMessage: true, + duration: ToastDuration.LONG, + }); } catch { // Ignore error, it was already displayed. } finally { @@ -883,7 +884,11 @@ export class AddonModDataHelperProvider { CoreEvents.trigger(ADDON_MOD_DATA_ENTRY_CHANGED, { dataId, entryId, deleted: true }, siteId); - CoreDomUtils.showToast('addon.mod_data.recorddeleted', true, ToastDuration.LONG); + CoreToasts.show({ + message: 'addon.mod_data.recorddeleted', + translateMessage: true, + duration: ToastDuration.LONG, + }); modal.dismiss(); } catch { diff --git a/src/addons/mod/forum/components/discussion-options-menu/discussion-options-menu.ts b/src/addons/mod/forum/components/discussion-options-menu/discussion-options-menu.ts index 77dfa2971..0eb646d2d 100644 --- a/src/addons/mod/forum/components/discussion-options-menu/discussion-options-menu.ts +++ b/src/addons/mod/forum/components/discussion-options-menu/discussion-options-menu.ts @@ -19,6 +19,7 @@ import { PopoverController } from '@singletons'; import { CoreEvents } from '@singletons/events'; import { AddonModForum, AddonModForumDiscussion } from '../../services/forum'; import { ADDON_MOD_FORUM_CHANGE_DISCUSSION_EVENT } from '../../constants'; +import { CoreToasts } from '@services/toasts'; /** * This component is meant to display a popover with the discussion options. @@ -74,7 +75,10 @@ export class AddonModForumDiscussionOptionsMenuComponent implements OnInit { CoreEvents.trigger(ADDON_MOD_FORUM_CHANGE_DISCUSSION_EVENT, data, CoreSites.getCurrentSiteId()); PopoverController.dismiss({ action: 'lock', value: locked }); - CoreDomUtils.showToast('addon.mod_forum.lockupdated', true); + CoreToasts.show({ + message: 'addon.mod_forum.lockupdated', + translateMessage: true, + }); } catch (error) { CoreDomUtils.showErrorModal(error); PopoverController.dismiss(); @@ -103,7 +107,10 @@ export class AddonModForumDiscussionOptionsMenuComponent implements OnInit { CoreEvents.trigger(ADDON_MOD_FORUM_CHANGE_DISCUSSION_EVENT, data, CoreSites.getCurrentSiteId()); PopoverController.dismiss({ action: 'pin', value: pinned }); - CoreDomUtils.showToast('addon.mod_forum.pinupdated', true); + CoreToasts.show({ + message: 'addon.mod_forum.pinupdated', + translateMessage: true, + }); } catch (error) { CoreDomUtils.showErrorModal(error); PopoverController.dismiss(); @@ -132,7 +139,10 @@ export class AddonModForumDiscussionOptionsMenuComponent implements OnInit { CoreEvents.trigger(ADDON_MOD_FORUM_CHANGE_DISCUSSION_EVENT, data, CoreSites.getCurrentSiteId()); PopoverController.dismiss({ action: 'star', value: starred }); - CoreDomUtils.showToast('addon.mod_forum.favouriteupdated', true); + CoreToasts.show({ + message: 'addon.mod_forum.favouriteupdated', + translateMessage: true, + }); } catch (error) { CoreDomUtils.showErrorModal(error); PopoverController.dismiss(); diff --git a/src/addons/mod/forum/components/index/index.ts b/src/addons/mod/forum/components/index/index.ts index 7746222cd..c7b7f4035 100644 --- a/src/addons/mod/forum/components/index/index.ts +++ b/src/addons/mod/forum/components/index/index.ts @@ -65,6 +65,7 @@ import { ADDON_MOD_FORUM_SEARCH_PAGE_NAME, } from '@addons/mod/forum/constants'; import { CoreSearchGlobalSearch } from '@features/search/services/global-search'; +import { CoreToasts } from '@services/toasts'; /** * Component that displays a forum entry page. */ @@ -554,7 +555,10 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom try { if (isNewDiscussion) { - CoreDomUtils.showToast('addon.mod_forum.postaddedsuccess', true); + CoreToasts.show({ + message: 'addon.mod_forum.postaddedsuccess', + translateMessage: true, + }); const newDiscGroupId = (data as AddonModForumNewDiscussionData).groupId; diff --git a/src/addons/mod/forum/components/post/post.ts b/src/addons/mod/forum/components/post/post.ts index 49f7b0db0..759782675 100644 --- a/src/addons/mod/forum/components/post/post.ts +++ b/src/addons/mod/forum/components/post/post.ts @@ -53,6 +53,7 @@ import { AddonModForumSharedPostFormData } from '../../pages/discussion/discussi import { CoreDom } from '@singletons/dom'; import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; import { ADDON_MOD_FORUM_CHANGE_DISCUSSION_EVENT, ADDON_MOD_FORUM_COMPONENT } from '../../constants'; +import { CoreToasts } from '@services/toasts'; /** * Components that shows a discussion post, its attachments and the action buttons allowed (reply, etc.). @@ -167,7 +168,10 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges CoreSites.getCurrentSiteId(), ); - CoreDomUtils.showToast('addon.mod_forum.deletedpost', true); + CoreToasts.show({ + message: 'addon.mod_forum.deletedpost', + translateMessage: true, + }); } catch (error) { CoreDomUtils.showErrorModal(error); } finally { diff --git a/src/addons/mod/forum/pages/discussion/discussion.ts b/src/addons/mod/forum/pages/discussion/discussion.ts index 522ec6a07..ea1d5b137 100644 --- a/src/addons/mod/forum/pages/discussion/discussion.ts +++ b/src/addons/mod/forum/pages/discussion/discussion.ts @@ -59,6 +59,7 @@ import { ADDON_MOD_FORUM_REPLY_DISCUSSION_EVENT, } from '../../constants'; import { CoreCourseContentsPage } from '@features/course/pages/contents/contents'; +import { CoreToasts } from '@services/toasts'; type SortType = 'flat-newest' | 'flat-oldest' | 'nested'; @@ -730,7 +731,10 @@ export class AddonModForumDiscussionPage implements OnInit, AfterViewInit, OnDes }; CoreEvents.trigger(ADDON_MOD_FORUM_CHANGE_DISCUSSION_EVENT, data, CoreSites.getCurrentSiteId()); - CoreDomUtils.showToast('addon.mod_forum.lockupdated', true); + CoreToasts.show({ + message: 'addon.mod_forum.lockupdated', + translateMessage: true, + }); } catch (error) { CoreDomUtils.showErrorModal(error); } finally { @@ -763,7 +767,10 @@ export class AddonModForumDiscussionPage implements OnInit, AfterViewInit, OnDes }; CoreEvents.trigger(ADDON_MOD_FORUM_CHANGE_DISCUSSION_EVENT, data, CoreSites.getCurrentSiteId()); - CoreDomUtils.showToast('addon.mod_forum.pinupdated', true); + CoreToasts.show({ + message: 'addon.mod_forum.pinupdated', + translateMessage: true, + }); } catch (error) { CoreDomUtils.showErrorModal(error); } finally { @@ -796,7 +803,10 @@ export class AddonModForumDiscussionPage implements OnInit, AfterViewInit, OnDes }; CoreEvents.trigger(ADDON_MOD_FORUM_CHANGE_DISCUSSION_EVENT, data, CoreSites.getCurrentSiteId()); - CoreDomUtils.showToast('addon.mod_forum.favouriteupdated', true); + CoreToasts.show({ + message: 'addon.mod_forum.favouriteupdated', + translateMessage: true, + }); } catch (error) { CoreDomUtils.showErrorModal(error); } finally { diff --git a/src/addons/mod/glossary/pages/entry/entry.ts b/src/addons/mod/glossary/pages/entry/entry.ts index 7b55531ff..19e5637ce 100644 --- a/src/addons/mod/glossary/pages/entry/entry.ts +++ b/src/addons/mod/glossary/pages/entry/entry.ts @@ -26,7 +26,7 @@ import { CoreTag } from '@features/tag/services/tag'; import { FileEntry } from '@awesome-cordova-plugins/file/ngx'; import { CoreNavigator } from '@services/navigator'; import { CoreNetwork } from '@services/network'; -import { CoreDomUtils, ToastDuration } from '@services/utils/dom'; +import { CoreDomUtils } from '@services/utils/dom'; import { CoreUtils } from '@services/utils/utils'; import { Translate } from '@singletons'; import { CoreEventObserver, CoreEvents } from '@singletons/events'; @@ -40,6 +40,7 @@ import { CoreTime } from '@singletons/time'; import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; import { ADDON_MOD_GLOSSARY_COMPONENT, ADDON_MOD_GLOSSARY_ENTRY_UPDATED, ADDON_MOD_GLOSSARY_PAGE_NAME } from '../../constants'; import { CoreCourseContentsPage } from '@features/course/pages/contents/contents'; +import { CoreToasts, ToastDuration } from '@services/toasts'; /** * Page that displays a glossary entry. @@ -214,7 +215,11 @@ export class AddonModGlossaryEntryPage implements OnInit, OnDestroy { await AddonModGlossaryHelper.deleteStoredFiles(glossaryId, concept, timecreated); } - CoreDomUtils.showToast('addon.mod_glossary.entrydeleted', true, ToastDuration.LONG); + CoreToasts.show({ + message: 'addon.mod_glossary.entrydeleted', + translateMessage: true, + duration: ToastDuration.LONG, + }); await this.goBack(); } catch (error) { diff --git a/src/addons/notes/components/add/add-modal.ts b/src/addons/notes/components/add/add-modal.ts index 1fdf03ea8..337b97dc6 100644 --- a/src/addons/notes/components/add/add-modal.ts +++ b/src/addons/notes/components/add/add-modal.ts @@ -15,11 +15,12 @@ import { AddonNotes, AddonNotesPublishState } from '@addons/notes/services/notes'; import { Component, ViewChild, ElementRef, Input } from '@angular/core'; import { CoreSites } from '@services/sites'; -import { CoreDomUtils, ToastDuration } from '@services/utils/dom'; +import { CoreDomUtils } from '@services/utils/dom'; import { CoreForms } from '@singletons/form'; import { ModalController } from '@singletons'; import { CoreKeyboard } from '@singletons/keyboard'; import { CoreSharedModule } from '@/core/shared.module'; +import { CoreToasts, ToastDuration } from '@services/toasts'; /** * Component that displays a text area for composing a note. @@ -62,7 +63,11 @@ export class AddonNotesAddComponent { CoreForms.triggerFormSubmittedEvent(this.formElement, sent, CoreSites.getCurrentSiteId()); ModalController.dismiss({ type: this.type, sent: true }).finally(() => { - CoreDomUtils.showToast(sent ? 'addon.notes.eventnotecreated' : 'core.datastoredoffline', true, ToastDuration.LONG); + CoreToasts.show({ + message: sent ? 'addon.notes.eventnotecreated' : 'core.datastoredoffline', + translateMessage: true, + duration: ToastDuration.LONG, + }); }); } catch (error){ CoreDomUtils.showErrorModal(error); diff --git a/src/addons/notes/pages/list/list.ts b/src/addons/notes/pages/list/list.ts index b31a6ac17..840ef91dd 100644 --- a/src/addons/notes/pages/list/list.ts +++ b/src/addons/notes/pages/list/list.ts @@ -24,13 +24,14 @@ import { IonContent } from '@ionic/angular'; import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; import { CoreNavigator } from '@services/navigator'; import { CoreSites } from '@services/sites'; -import { CoreDomUtils, ToastDuration } from '@services/utils/dom'; +import { CoreDomUtils } from '@services/utils/dom'; import { CoreTextUtils } from '@services/utils/text'; import { CoreUrl } from '@singletons/url'; import { CoreUtils } from '@services/utils/utils'; import { Translate } from '@singletons'; import { CoreEventObserver, CoreEvents } from '@singletons/events'; import { CoreTime } from '@singletons/time'; +import { CoreToasts, ToastDuration } from '@services/toasts'; /** * Page that displays a list of notes. @@ -240,7 +241,11 @@ export class AddonNotesListPage implements OnInit, OnDestroy { this.refreshNotes(false); - CoreDomUtils.showToast('addon.notes.eventnotedeleted', true, ToastDuration.LONG); + CoreToasts.show({ + message: 'addon.notes.eventnotedeleted', + translateMessage: true, + duration: ToastDuration.LONG, + }); } catch (error) { CoreDomUtils.showErrorModalDefault(error, 'Delete note failed.'); diff --git a/src/addons/privatefiles/services/privatefiles-helper.ts b/src/addons/privatefiles/services/privatefiles-helper.ts index a00f2bd94..b12cd780a 100644 --- a/src/addons/privatefiles/services/privatefiles-helper.ts +++ b/src/addons/privatefiles/services/privatefiles-helper.ts @@ -20,6 +20,7 @@ import { CoreFileUploaderHelper } from '@features/fileuploader/services/fileuplo import { AddonPrivateFiles, AddonPrivateFilesGetUserInfoWSResult } from './privatefiles'; import { CoreError } from '@classes/errors/error'; import { makeSingleton, Translate } from '@singletons'; +import { CoreToasts } from '@services/toasts'; /** * Service that provides some helper functions regarding private and site files. @@ -64,7 +65,11 @@ export class AddonPrivateFilesHelperProvider { try { await AddonPrivateFiles.moveFromDraftToPrivate(result.itemid); - CoreDomUtils.showToast('core.fileuploader.fileuploaded', true, undefined, 'core-toast-success'); + CoreToasts.show({ + message: 'core.fileuploader.fileuploaded', + translateMessage: true, + cssClass: 'core-toast-success', + }); } finally { modal.dismiss(); } diff --git a/src/addons/report/insights/services/handlers/action-link.ts b/src/addons/report/insights/services/handlers/action-link.ts index e91ce2386..679ae22d6 100644 --- a/src/addons/report/insights/services/handlers/action-link.ts +++ b/src/addons/report/insights/services/handlers/action-link.ts @@ -20,6 +20,7 @@ import { CoreSites } from '@services/sites'; import { CoreDomUtils } from '@services/utils/dom'; import { makeSingleton, Translate } from '@singletons'; import { AddonReportInsights } from '../insights'; +import { CoreToasts } from '@services/toasts'; // Bulk actions supported, along with the related lang string. const BULK_ACTIONS = { @@ -64,12 +65,17 @@ export class AddonReportInsightsActionLinkHandlerService extends CoreContentLink if (BULK_ACTIONS[params.action]) { // Done, display a toast. - CoreDomUtils.showToast(Translate.instant('addon.report_insights.actionsaved', { - $a: Translate.instant(BULK_ACTIONS[params.action]), - })); + CoreToasts.show({ + message: Translate.instant('addon.report_insights.actionsaved', { + $a: Translate.instant(BULK_ACTIONS[params.action]), + }), + }); } else if (!params.forwardurl) { // Forward URL not defined, display a toast. - CoreDomUtils.showToast('core.success', true); + CoreToasts.show({ + message: 'core.success', + translateMessage: true, + }); } else { // Try to open the link in the app. const forwardUrl = decodeURIComponent(params.forwardurl); diff --git a/src/core/classes/sites/authenticated-site.ts b/src/core/classes/sites/authenticated-site.ts index 20be3c9fe..a40896b9e 100644 --- a/src/core/classes/sites/authenticated-site.ts +++ b/src/core/classes/sites/authenticated-site.ts @@ -21,7 +21,7 @@ import { CoreWSPreSetsSplitRequest, CoreWSTypeExpected, } from '@services/ws'; -import { CoreDomUtils, ToastDuration } from '@services/utils/dom'; +import { CoreToasts, ToastDuration } from '@services/toasts'; import { CoreTextUtils } from '@services/utils/text'; import { CoreUtils } from '@services/utils/utils'; import { CoreConstants } from '@/core/constants'; @@ -420,7 +420,11 @@ export class CoreAuthenticatedSite extends CoreUnauthenticatedSite { if (wsPreSets.cleanUnicode && CoreTextUtils.hasUnicodeData(data)) { // Data will be cleaned, notify the user. - CoreDomUtils.showToast('core.unicodenotsupported', true, ToastDuration.LONG); + CoreToasts.show({ + message: 'core.unicodenotsupported', + translateMessage: true, + duration: ToastDuration.LONG, + }); } else { // No need to clean data in this call. wsPreSets.cleanUnicode = false; diff --git a/src/core/core.module.ts b/src/core/core.module.ts index 5a33b79e3..d94191d1f 100644 --- a/src/core/core.module.ts +++ b/src/core/core.module.ts @@ -34,6 +34,7 @@ export async function getCoreServices(): Promise[]> { const { CoreCustomURLSchemesProvider } = await import('@services/urlschemes'); const { CoreDbProvider } = await import('@services/db'); const { CoreDomUtilsProvider } = await import('@services/utils/dom'); + const { CoreToastsService } = await import('@services/toasts'); const { CoreFileHelperProvider } = await import('@services/file-helper'); const { CoreFilepoolProvider } = await import('@services/filepool'); const { CoreFileProvider } = await import('@services/file'); @@ -82,6 +83,7 @@ export async function getCoreServices(): Promise[]> { CoreSyncProvider, CoreTextUtilsProvider, CoreTimeUtilsProvider, + CoreToastsService, CoreUpdateManagerProvider, CoreUrlUtilsProvider, CoreUtilsProvider, diff --git a/src/core/features/comments/pages/viewer/viewer.ts b/src/core/features/comments/pages/viewer/viewer.ts index e1a077947..a403084b6 100644 --- a/src/core/features/comments/pages/viewer/viewer.ts +++ b/src/core/features/comments/pages/viewer/viewer.ts @@ -31,7 +31,7 @@ import { ContextLevel, CoreConstants } from '@/core/constants'; import { CoreNavigator } from '@services/navigator'; import { NgZone, Translate } from '@singletons'; import { CoreUtils } from '@services/utils/utils'; -import { CoreDomUtils, ToastDuration } from '@services/utils/dom'; +import { CoreDomUtils } from '@services/utils/dom'; import { CoreUser } from '@features/user/services/user'; import { CoreTextUtils } from '@services/utils/text'; import { CoreError } from '@classes/errors/error'; @@ -43,6 +43,7 @@ import moment from 'moment-timezone'; import { Subscription } from 'rxjs'; import { CoreAnimations } from '@components/animations'; import { CoreKeyboard } from '@singletons/keyboard'; +import { CoreToasts, ToastDuration } from '@services/toasts'; /** * Page that displays comments. @@ -323,11 +324,11 @@ export class CoreCommentsViewerPage implements OnInit, OnDestroy { this.area, ); - CoreDomUtils.showToast( - commentsResponse ? 'core.comments.eventcommentcreated' : 'core.datastoredoffline', - true, - ToastDuration.LONG, - ); + CoreToasts.show({ + message: commentsResponse ? 'core.comments.eventcommentcreated' : 'core.datastoredoffline', + translateMessage: true, + duration: ToastDuration.LONG, + }); if (commentsResponse) { this.invalidateComments(); @@ -426,7 +427,11 @@ export class CoreCommentsViewerPage implements OnInit, OnDestroy { this.invalidateComments(); - CoreDomUtils.showToast('core.comments.eventcommentdeleted', true, ToastDuration.LONG); + CoreToasts.show({ + message: 'core.comments.eventcommentdeleted', + translateMessage: true, + duration: ToastDuration.LONG, + }); } catch (error) { CoreDomUtils.showErrorModalDefault(error, 'Delete comment failed.'); } diff --git a/src/core/features/dataprivacy/components/contactdpo/contactdpo.ts b/src/core/features/dataprivacy/components/contactdpo/contactdpo.ts index 6bcd74ba6..dc210b959 100644 --- a/src/core/features/dataprivacy/components/contactdpo/contactdpo.ts +++ b/src/core/features/dataprivacy/components/contactdpo/contactdpo.ts @@ -18,7 +18,8 @@ import { FormGroup, FormBuilder, Validators } from '@angular/forms'; import { CoreDataPrivacy } from '@features/dataprivacy/services/dataprivacy'; import { CoreUser } from '@features/user/services/user'; import { CoreSites } from '@services/sites'; -import { CoreDomUtils, ToastDuration } from '@services/utils/dom'; +import { CoreDomUtils } from '@services/utils/dom'; +import { CoreToasts, ToastDuration } from '@services/toasts'; import { CoreUtils } from '@services/utils/utils'; import { ModalController } from '@singletons'; @@ -80,7 +81,11 @@ export class CoreDataPrivacyContactDPOComponent implements OnInit { // Send the message. const succeed = await CoreDataPrivacy.contactDPO(this.message); if (succeed) { - CoreDomUtils.showToast('core.dataprivacy.requestsubmitted', true, ToastDuration.LONG); + CoreToasts.show({ + message: 'core.dataprivacy.requestsubmitted', + translateMessage: true, + duration: ToastDuration.LONG, + }); ModalController.dismiss(true); } } catch (error) { diff --git a/src/core/features/dataprivacy/components/newrequest/newrequest.ts b/src/core/features/dataprivacy/components/newrequest/newrequest.ts index 7939da1fd..53fde8914 100644 --- a/src/core/features/dataprivacy/components/newrequest/newrequest.ts +++ b/src/core/features/dataprivacy/components/newrequest/newrequest.ts @@ -20,7 +20,8 @@ import { CoreDataPrivacyDataRequestType, CoreDataPrivacyGetAccessInformationWSResponse, } from '@features/dataprivacy/services/dataprivacy'; -import { CoreDomUtils, ToastDuration } from '@services/utils/dom'; +import { CoreDomUtils } from '@services/utils/dom'; +import { CoreToasts, ToastDuration } from '@services/toasts'; import { ModalController } from '@singletons'; @@ -105,7 +106,11 @@ export class CoreDataPrivacyNewRequestComponent implements OnInit { // Send the message. const requestId = await CoreDataPrivacy.createDataRequest(this.typeControl.value, this.message); if (requestId) { - CoreDomUtils.showToast('core.dataprivacy.requestsubmitted', true, ToastDuration.LONG); + CoreToasts.show({ + message: 'core.dataprivacy.requestsubmitted', + translateMessage: true, + duration: ToastDuration.LONG, + }); ModalController.dismiss(true); } } catch (error) { diff --git a/src/core/features/fileuploader/services/fileuploader-helper.ts b/src/core/features/fileuploader/services/fileuploader-helper.ts index 1d6a8cf76..cbbad35a1 100644 --- a/src/core/features/fileuploader/services/fileuploader-helper.ts +++ b/src/core/features/fileuploader/services/fileuploader-helper.ts @@ -44,6 +44,7 @@ import { CorePath } from '@singletons/path'; import { CorePromisedValue } from '@classes/promised-value'; import { CorePlatform } from '@services/platform'; import { Chooser } from '@features/native/plugins'; +import { CoreToasts } from '@services/toasts'; /** * Helper service to upload files. @@ -457,7 +458,11 @@ export class CoreFileUploaderHelperProvider { await this.uploadGenericFile(CoreFile.getFileEntryURL(fileEntry), file.name, file.type, deleteAfterUpload, siteId); - CoreDomUtils.showToast('core.fileuploader.fileuploaded', true, undefined, 'core-toast-success'); + CoreToasts.show({ + message: 'core.fileuploader.fileuploaded', + translateMessage: true, + cssClass: 'core-toast-success', + }); } catch (error) { CoreDomUtils.showErrorModalDefault(error, 'core.fileuploader.errorreadingfile', true); diff --git a/src/core/features/rating/components/rate/rate.ts b/src/core/features/rating/components/rate/rate.ts index fdd30b0e5..a7b9c562e 100644 --- a/src/core/features/rating/components/rate/rate.ts +++ b/src/core/features/rating/components/rate/rate.ts @@ -23,7 +23,8 @@ import { } from '@features/rating/services/rating'; import { CoreRatingOffline } from '@features/rating/services/rating-offline'; import { CoreSites } from '@services/sites'; -import { CoreDomUtils, ToastDuration } from '@services/utils/dom'; +import { CoreDomUtils } from '@services/utils/dom'; +import { CoreToasts, ToastDuration } from '@services/toasts'; import { Translate } from '@singletons'; import { CoreEventObserver, CoreEvents } from '@singletons/events'; @@ -147,7 +148,11 @@ export class CoreRatingRateComponent implements OnChanges, OnDestroy { ); if (response === undefined) { - CoreDomUtils.showToast('core.datastoredoffline', true, ToastDuration.LONG); + CoreToasts.show({ + message: 'core.datastoredoffline', + translateMessage: true, + duration: ToastDuration.LONG, + }); } else { this.onUpdate.emit(); } diff --git a/src/core/features/reminders/components/set-button/set-button.ts b/src/core/features/reminders/components/set-button/set-button.ts index a775ad6a8..b63761b87 100644 --- a/src/core/features/reminders/components/set-button/set-button.ts +++ b/src/core/features/reminders/components/set-button/set-button.ts @@ -18,6 +18,7 @@ import { CoreDomUtils } from '@services/utils/dom'; import { CoreRemindersSetReminderMenuComponent } from '../set-reminder-menu/set-reminder-menu'; import { Translate } from '@singletons'; import { CoreTimeUtils } from '@services/utils/time'; +import { CoreToasts } from '@services/toasts'; /** * Component that displays a button to set a reminder. @@ -127,7 +128,10 @@ export class CoreRemindersSetButtonComponent implements OnInit { if (timebefore === undefined || timebefore === CoreRemindersService.DISABLED) { this.setTimebefore(undefined); - CoreDomUtils.showToast('core.reminders.reminderunset', true); + CoreToasts.show({ + message: 'core.reminders.reminderunset', + translateMessage: true, + }); return; } @@ -148,8 +152,8 @@ export class CoreRemindersSetButtonComponent implements OnInit { await CoreReminders.addReminder(reminder); const time = this.time - timebefore; - const text = Translate.instant('core.reminders.reminderset', { $a: CoreTimeUtils.userDate(time * 1000) }); - CoreDomUtils.showToast(text); + const message = Translate.instant('core.reminders.reminderset', { $a: CoreTimeUtils.userDate(time * 1000) }); + CoreToasts.show({ message }); } } diff --git a/src/core/features/settings/pages/dev/dev.ts b/src/core/features/settings/pages/dev/dev.ts index ce9bac44b..8b2888399 100644 --- a/src/core/features/settings/pages/dev/dev.ts +++ b/src/core/features/settings/pages/dev/dev.ts @@ -24,7 +24,8 @@ import { CoreFile } from '@services/file'; import { CoreNavigator } from '@services/navigator'; import { CorePlatform } from '@services/platform'; import { CoreSites } from '@services/sites'; -import { CoreDomUtils, ToastDuration } from '@services/utils/dom'; +import { CoreDomUtils } from '@services/utils/dom'; +import { CoreToasts, ToastDuration } from '@services/toasts'; import { CoreText } from '@singletons/text'; /** @@ -177,7 +178,7 @@ export class CoreSettingsDevPage implements OnInit { await CoreConfig.delete(ONBOARDING_DONE); await CoreConfig.delete(FAQ_QRCODE_INFO_DONE); - CoreDomUtils.showToast('User tours have been reseted'); + CoreToasts.show({ message: 'User tours have been reseted' }); } /** @@ -194,7 +195,10 @@ export class CoreSettingsDevPage implements OnInit { return; } - await CoreDomUtils.showToast('Caches invalidated', true, ToastDuration.LONG); + await CoreToasts.show({ + message: 'Caches invalidated', + duration: ToastDuration.LONG, + }); } /** @@ -205,7 +209,7 @@ export class CoreSettingsDevPage implements OnInit { await CoreFile.clearDeletedSitesFolder(sites); await CoreFile.clearTmpFolder(); - CoreDomUtils.showToast('File storage cleared'); + CoreToasts.show({ message: 'File storage cleared' }); } async setEnabledStagingSites(enabled: boolean): Promise { diff --git a/src/core/features/settings/pages/deviceinfo/deviceinfo.ts b/src/core/features/settings/pages/deviceinfo/deviceinfo.ts index 815976b56..38b5a45ea 100644 --- a/src/core/features/settings/pages/deviceinfo/deviceinfo.ts +++ b/src/core/features/settings/pages/deviceinfo/deviceinfo.ts @@ -23,7 +23,7 @@ import { CoreUtils } from '@services/utils/utils'; import { Subscription } from 'rxjs'; import { CorePushNotifications } from '@features/pushnotifications/services/pushnotifications'; import { CoreConfig } from '@services/config'; -import { CoreDomUtils } from '@services/utils/dom'; +import { CoreToasts } from '@services/toasts'; import { CoreNavigator } from '@services/navigator'; import { CorePlatform } from '@services/platform'; import { CoreNetwork } from '@services/network'; @@ -265,7 +265,10 @@ export class CoreSettingsDeviceInfoPage implements OnDestroy { this.showDevOptions = true; await CoreConfig.set('showDevOptions', 1); - CoreDomUtils.showToast('core.settings.youradev', true); + CoreToasts.show({ + message: 'core.settings.youradev', + translateMessage: true, + }); } else { this.showDevOptions = false; await CoreConfig.delete('showDevOptions'); diff --git a/src/core/features/settings/pages/site/site.ts b/src/core/features/settings/pages/site/site.ts index 32c0afa5f..a3575362c 100644 --- a/src/core/features/settings/pages/site/site.ts +++ b/src/core/features/settings/pages/site/site.ts @@ -29,6 +29,7 @@ import { NgZone } from '@singletons'; import { CoreConstants } from '@/core/constants'; import { CoreConfig } from '@services/config'; import { CoreSettingsHandlersSource } from '@features/settings/classes/settings-handlers-source'; +import { CoreToasts } from '@services/toasts'; /** * Page that displays the list of site settings pages. @@ -117,7 +118,10 @@ export class CoreSitePreferencesPage implements AfterViewInit, OnDestroy { // Using syncOnlyOnWifi false to force manual sync. await CoreSettingsHelper.synchronizeSite(false, this.siteId); - CoreDomUtils.showToast('core.settings.sitesynccompleted', true); + CoreToasts.show({ + message: 'core.settings.sitesynccompleted', + translateMessage: true, + }); } catch (error) { if (this.isDestroyed) { return; diff --git a/src/core/features/settings/pages/synchronization/synchronization.ts b/src/core/features/settings/pages/synchronization/synchronization.ts index 2bf02cdbf..2b778e6d3 100644 --- a/src/core/features/settings/pages/synchronization/synchronization.ts +++ b/src/core/features/settings/pages/synchronization/synchronization.ts @@ -25,6 +25,7 @@ import { CoreAccountsList, CoreLoginHelper } from '@features/login/services/logi import { CoreNetwork } from '@services/network'; import { Subscription } from 'rxjs'; import { CoreNavigator } from '@services/navigator'; +import { CoreToasts } from '@services/toasts'; /** * Page that displays the synchronization settings. @@ -132,7 +133,10 @@ export class CoreSettingsSynchronizationPage implements OnInit, OnDestroy { try { await CoreSettingsHelper.synchronizeSite(false, siteId); - CoreDomUtils.showToast('core.settings.sitesynccompleted', true); + CoreToasts.show({ + message: 'core.settings.sitesynccompleted', + translateMessage: true, + }); } catch (error) { if (this.isDestroyed) { return; diff --git a/src/core/features/siteplugins/directives/call-ws.ts b/src/core/features/siteplugins/directives/call-ws.ts index 6c232973e..fc5bc0d3a 100644 --- a/src/core/features/siteplugins/directives/call-ws.ts +++ b/src/core/features/siteplugins/directives/call-ws.ts @@ -15,7 +15,7 @@ import { Directive, Input, ElementRef, Optional } from '@angular/core'; import { Translate } from '@singletons'; -import { CoreDomUtils } from '@services/utils/dom'; +import { CoreToasts } from '@services/toasts'; import { CoreUtils } from '@services/utils/utils'; import { CoreNavigator } from '@services/navigator'; import { CoreSitePluginsCallWSOnClickBaseDirective } from '../classes/call-ws-click-directive'; @@ -74,7 +74,7 @@ export class CoreSitePluginsCallWSDirective extends CoreSitePluginsCallWSOnClick if (this.successMessage !== undefined) { // Display the success message. - CoreDomUtils.showToast(this.successMessage || Translate.instant('core.success')); + CoreToasts.show({ message: this.successMessage || Translate.instant('core.success') }); } } diff --git a/src/core/services/toasts.ts b/src/core/services/toasts.ts new file mode 100644 index 000000000..683f9496d --- /dev/null +++ b/src/core/services/toasts.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 { CoreConstants } from '@/core/constants'; +import { Injectable } from '@angular/core'; +import { ToastOptions } from '@ionic/angular'; +import { Translate, ToastController, makeSingleton } from '@singletons'; +import { fixOverlayAriaHidden } from '@/core/utils/fix-aria-hidden'; + +/** + * Handles application toasts. + */ +@Injectable({ providedIn: 'root' }) +export class CoreToastsService { + + /** + * Displays an autodimissable toast modal window. + * + * @param options Options. + * @returns Promise resolved with Toast instance. + */ + async show(options: ShowToastOptions): Promise { + if (options.translateMessage && typeof options.message === 'string') { + options.message = Translate.instant(options.message); + } + + options.duration = options.duration ?? ToastDuration.SHORT; + + // Convert some values and set default values. + const toastOptions: ToastOptions = { + ...options, + duration: CoreConstants.CONFIG.toastDurations[options.duration] ?? options.duration ?? 2000, + position: options.position ?? 'bottom', + }; + + const loader = await ToastController.create(toastOptions); + + await loader.present(); + + fixOverlayAriaHidden(loader); + + return loader; + } + +} + +export const CoreToasts = makeSingleton(CoreToastsService); + +/** + * Toast duration. + */ +export enum ToastDuration { + LONG = 'long', + SHORT = 'short', + STICKY = 'sticky', +} + +/** + * Options for showToastWithOptions. + */ +export type ShowToastOptions = Omit & { + duration?: ToastDuration | number; + translateMessage?: boolean; +}; diff --git a/src/core/services/utils/dom.ts b/src/core/services/utils/dom.ts index c00e2f88d..5090f66c8 100644 --- a/src/core/services/utils/dom.ts +++ b/src/core/services/utils/dom.ts @@ -14,7 +14,7 @@ import { Injectable, SimpleChange, KeyValueChanges } from '@angular/core'; import { IonContent } from '@ionic/angular'; -import { ModalOptions, PopoverOptions, AlertOptions, AlertButton, TextFieldTypes, ToastOptions } from '@ionic/core'; +import { ModalOptions, PopoverOptions, AlertOptions, AlertButton, TextFieldTypes } from '@ionic/core'; import { Md5 } from 'ts-md5'; import { CoreConfig } from '@services/config'; @@ -32,12 +32,9 @@ import { makeSingleton, Translate, AlertController, - ToastController, PopoverController, ModalController, Router, - ActionSheetController, - LoadingController, } from '@singletons'; import { CoreLogger } from '@singletons/logger'; import { CoreFileSizeSum } from '@services/plugin-file-delegate'; @@ -60,6 +57,8 @@ import { CoreWSError } from '@classes/errors/wserror'; import { CoreErrorLogs } from '@singletons/error-logs'; import { CoreKeyboard } from '@singletons/keyboard'; import { CoreWait } from '@singletons/wait'; +import { CoreToasts, ToastDuration, ShowToastOptions } from '../toasts'; +import { fixOverlayAriaHidden } from '@/core/utils/fix-aria-hidden'; /* * "Utils" service with helper functions for UI, DOM elements and HTML code. @@ -874,7 +873,7 @@ export class CoreDomUtilsProvider { alertMessageEl && this.treatAnchors(alertMessageEl); } - this.fixAriaHidden(alert); + fixOverlayAriaHidden(alert); return; }); @@ -1375,27 +1374,26 @@ export class CoreDomUtilsProvider { /** * Displays an autodimissable toast modal window. * - * @param text The text of the toast. - * @param needsTranslate Whether the 'text' needs to be translated. + * @param message The text of the toast. + * @param translateMessage Whether the 'text' needs to be translated. * @param duration Duration in ms of the dimissable toast. * @param cssClass Class to add to the toast. * @returns Toast instance. + * + * @deprecated since 4.5. Use CoreToasts.show instead. */ async showToast( - text: string, - needsTranslate?: boolean, + message: string, + translateMessage?: boolean, duration: ToastDuration | number = ToastDuration.SHORT, cssClass: string = '', ): Promise { - if (needsTranslate) { - text = Translate.instant(text); - } - - return this.showToastWithOptions({ - message: text, - duration: duration, + return CoreToasts.show({ + message, + translateMessage, + duration, + cssClass, position: 'bottom', - cssClass: cssClass, }); } @@ -1404,22 +1402,11 @@ export class CoreDomUtilsProvider { * * @param options Options. * @returns Promise resolved with Toast instance. + * + * @deprecated since 4.5. Use CoreToasts.show instead. */ async showToastWithOptions(options: ShowToastOptions): Promise { - // Convert some values and set default values. - const toastOptions: ToastOptions = { - ...options, - duration: CoreConstants.CONFIG.toastDurations[options.duration] ?? options.duration ?? 2000, - position: options.position ?? 'bottom', - }; - - const loader = await ToastController.create(toastOptions); - - await loader.present(); - - this.fixAriaHidden(loader); - - return loader; + return CoreToasts.show(options); } /** @@ -1515,7 +1502,7 @@ export class CoreDomUtilsProvider { } if (!alreadyDisplayed) { - this.fixAriaHidden(modal); + fixOverlayAriaHidden(modal); } const result = await resultPromise; @@ -1528,32 +1515,6 @@ export class CoreDomUtilsProvider { } } - /** - * Temporary fix to remove aria-hidden from ion-router-outlet if needed. It can be removed once the Ionic bug is fixed. - * https://github.com/ionic-team/ionic-framework/issues/29396 - * - * @param overlay Overlay dismissed. - */ - protected async fixAriaHidden( - overlay: HTMLIonModalElement | HTMLIonPopoverElement | HTMLIonAlertElement | HTMLIonToastElement, - ): Promise { - - await overlay.onDidDismiss(); - - const overlays = await Promise.all([ - ModalController.getTop(), - PopoverController.getTop(), - ActionSheetController.getTop(), - AlertController.getTop(), - LoadingController.getTop(), - ToastController.getTop(), - ]); - - if (!overlays.find(overlay => overlay !== undefined)) { - document.querySelector('ion-router-outlet')?.removeAttribute('aria-hidden'); - } - } - /** * Opens a side Modal. * @@ -1603,7 +1564,7 @@ export class CoreDomUtilsProvider { await popover.present(); - this.fixAriaHidden(popover); + fixOverlayAriaHidden(popover); return popover; } @@ -1841,19 +1802,3 @@ export enum VerticalPoint { MID = 'mid', BOTTOM = 'bottom', } - -/** - * Toast duration. - */ -export enum ToastDuration { - LONG = 'long', - SHORT = 'short', - STICKY = 'sticky', -} - -/** - * Options for showToastWithOptions. - */ -export type ShowToastOptions = Omit & { - duration: ToastDuration | number; -}; diff --git a/src/core/singletons/text.ts b/src/core/singletons/text.ts index 100c9be74..19b15a8b8 100644 --- a/src/core/singletons/text.ts +++ b/src/core/singletons/text.ts @@ -13,7 +13,7 @@ // limitations under the License. import { Clipboard } from '@singletons'; -import { CoreDomUtils } from '@services/utils/dom'; +import { CoreToasts } from '@services/toasts'; /** * Singleton with helper functions for text manipulation. @@ -89,7 +89,10 @@ export class CoreText { } // Show toast using ionicLoading. - CoreDomUtils.showToast('core.copiedtoclipboard', true); + CoreToasts.show({ + message: 'core.copiedtoclipboard', + translateMessage: true, + }); } } diff --git a/src/core/utils/fix-aria-hidden.ts b/src/core/utils/fix-aria-hidden.ts new file mode 100644 index 000000000..68ef2f85b --- /dev/null +++ b/src/core/utils/fix-aria-hidden.ts @@ -0,0 +1,50 @@ +// (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 { + ActionSheetController, + AlertController, + LoadingController, + ModalController, + PopoverController, + ToastController, +} from '@singletons'; + +/** + * Temporary fix to remove aria-hidden from ion-router-outlet if needed. It can be removed once the Ionic bug is fixed. + * https://github.com/ionic-team/ionic-framework/issues/29396 + * + * !!! Only for internal use, this function will be removed without deprecation !!! + * + * @param overlay Overlay dismissed. + */ +export async function fixOverlayAriaHidden( + overlay: HTMLIonModalElement | HTMLIonPopoverElement | HTMLIonAlertElement | HTMLIonToastElement, +): Promise { + + await overlay.onDidDismiss(); + + const overlays = await Promise.all([ + ModalController.getTop(), + PopoverController.getTop(), + ActionSheetController.getTop(), + AlertController.getTop(), + LoadingController.getTop(), + ToastController.getTop(), + ]); + + if (!overlays.find(overlay => overlay !== undefined)) { + document.querySelector('ion-router-outlet')?.removeAttribute('aria-hidden'); + } +} diff --git a/src/types/config.d.ts b/src/types/config.d.ts index cda618cf6..e3de69599 100644 --- a/src/types/config.d.ts +++ b/src/types/config.d.ts @@ -18,7 +18,7 @@ import { CoreLoginSiteInfo, CoreSitesDemoSiteData } from '@services/sites'; import { OpenFileAction } from '@services/utils/utils'; import { CoreLoginSiteFinderSettings, CoreLoginSiteSelectorListMethod } from '@features/login/services/login-helper'; import { CoreDatabaseConfiguration } from '@classes/database/database-table'; -import { ToastDuration } from '@services/utils/dom'; +import { ToastDuration } from '@services/toasts'; /* eslint-disable @typescript-eslint/naming-convention */ From 6303769f0cd3791b29cd64c4066fe2a7ba9c719c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Tue, 16 Jul 2024 15:09:24 +0200 Subject: [PATCH 2/8] MOBILE-4616 chore: Move ucFirst to CoreText --- src/addons/qtype/ddmarker/classes/ddmarker.ts | 4 ++-- .../features/course/classes/main-resource-component.ts | 3 ++- .../course/components/module-summary/module-summary.ts | 4 ++-- src/core/services/utils/mimetype.ts | 6 +++--- src/core/services/utils/text.ts | 4 +++- src/core/singletons/text.ts | 10 ++++++++++ 6 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/addons/qtype/ddmarker/classes/ddmarker.ts b/src/addons/qtype/ddmarker/classes/ddmarker.ts index 98d937316..49fddf82d 100644 --- a/src/addons/qtype/ddmarker/classes/ddmarker.ts +++ b/src/addons/qtype/ddmarker/classes/ddmarker.ts @@ -13,7 +13,7 @@ // limitations under the License. import { CoreDomUtils } from '@services/utils/dom'; -import { CoreTextUtils } from '@services/utils/text'; +import { CoreText } from '@singletons/text'; import { CoreCoordinates, CoreDom } from '@singletons/dom'; import { CoreEventObserver } from '@singletons/events'; import { CoreLogger } from '@singletons/logger'; @@ -267,7 +267,7 @@ export class AddonQtypeDdMarkerQuestion { } // Check that a function to draw this shape exists. - const drawFunc = 'drawShape' + CoreTextUtils.ucFirst(shape); + const drawFunc = 'drawShape' + CoreText.capitalize(shape); if (!(this[drawFunc] instanceof Function)) { return; } diff --git a/src/core/features/course/classes/main-resource-component.ts b/src/core/features/course/classes/main-resource-component.ts index 24de44d23..d5c4f064c 100644 --- a/src/core/features/course/classes/main-resource-component.ts +++ b/src/core/features/course/classes/main-resource-component.ts @@ -33,6 +33,7 @@ import { CoreCourseModulePrefetchDelegate } from '../services/module-prefetch-de import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; import { CoreUrl } from '@singletons/url'; import { CoreTime } from '@singletons/time'; +import { CoreText } from '@singletons/text'; /** * Result of a resource download. @@ -232,7 +233,7 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, const lastDownloaded = await CoreCourseHelper.getModulePackageLastDownloaded(this.module, this.component); - this.downloadTimeReadable = CoreTextUtils.ucFirst(lastDownloaded.downloadTimeReadable); + this.downloadTimeReadable = CoreText.capitalize(lastDownloaded.downloadTimeReadable); } /** diff --git a/src/core/features/course/components/module-summary/module-summary.ts b/src/core/features/course/components/module-summary/module-summary.ts index 5b87b840c..edaae3b37 100644 --- a/src/core/features/course/components/module-summary/module-summary.ts +++ b/src/core/features/course/components/module-summary/module-summary.ts @@ -28,7 +28,7 @@ import { CoreFilepool } from '@services/filepool'; import { CoreNavigator } from '@services/navigator'; import { CoreSites } from '@services/sites'; import { CoreDomUtils } from '@services/utils/dom'; -import { CoreTextUtils } from '@services/utils/text'; +import { CoreText } from '@singletons/text'; import { CoreUtils } from '@services/utils/utils'; import { ModalController, NgZone } from '@singletons'; import { CoreEventObserver, CoreEvents } from '@singletons/events'; @@ -222,7 +222,7 @@ export class CoreCourseModuleSummaryComponent implements OnInit, OnDestroy { if (this.canPrefetch) { if (moduleInfo.downloadTime && moduleInfo.downloadTime > 0) { - this.downloadTimeReadable = CoreTextUtils.ucFirst(moduleInfo.downloadTimeReadable); + this.downloadTimeReadable = CoreText.capitalize(moduleInfo.downloadTimeReadable); } this.prefetchLoading = moduleInfo.status === DownloadStatus.DOWNLOADING; this.prefetchDisabled = moduleInfo.status === DownloadStatus.DOWNLOADED; diff --git a/src/core/services/utils/mimetype.ts b/src/core/services/utils/mimetype.ts index 5bbf67724..3125c6bf9 100644 --- a/src/core/services/utils/mimetype.ts +++ b/src/core/services/utils/mimetype.ts @@ -16,7 +16,7 @@ import { Injectable } from '@angular/core'; import { FileEntry } from '@awesome-cordova-plugins/file/ngx'; import { CoreFile } from '@services/file'; -import { CoreTextUtils } from '@services/utils/text'; +import { CoreText } from '@singletons/text'; import { makeSingleton, Translate } from '@singletons'; import { CoreLogger } from '@singletons/logger'; import { CoreWSFile } from '@services/ws'; @@ -466,7 +466,7 @@ export class CoreMimetypeUtilsProvider { const value = attr[key]; translateParams[key] = value; translateParams[key.toUpperCase()] = value.toUpperCase(); - translateParams[CoreTextUtils.ucFirst(key)] = CoreTextUtils.ucFirst(value); + translateParams[CoreText.capitalize(key)] = CoreText.capitalize(value); } // MIME types may include + symbol but this is not permitted in string ids. @@ -486,7 +486,7 @@ export class CoreMimetypeUtilsProvider { } if (capitalise) { - result = CoreTextUtils.ucFirst(result); + result = CoreText.capitalize(result); } return result; diff --git a/src/core/services/utils/text.ts b/src/core/services/utils/text.ts index a40bcdbd9..2bfb864ed 100644 --- a/src/core/services/utils/text.ts +++ b/src/core/services/utils/text.ts @@ -28,6 +28,7 @@ import { CorePath } from '@singletons/path'; import { CorePlatform } from '@services/platform'; import { ContextLevel } from '@/core/constants'; import { CoreDom } from '@singletons/dom'; +import { CoreText } from '@singletons/text'; /** * Different type of errors the app can treat. @@ -1007,9 +1008,10 @@ export class CoreTextUtilsProvider { * * @param text Text to treat. * @returns Treated text. + * @deprecated since 4.5. Use CoreText.capitalize instead. */ ucFirst(text: string): string { - return text.charAt(0).toUpperCase() + text.slice(1); + return CoreText.capitalize(text); } /** diff --git a/src/core/singletons/text.ts b/src/core/singletons/text.ts index 19b15a8b8..0a60c5a48 100644 --- a/src/core/singletons/text.ts +++ b/src/core/singletons/text.ts @@ -95,4 +95,14 @@ export class CoreText { }); } + /** + * Make a string's first character uppercase. + * + * @param text Text to treat. + * @returns Treated text. + */ + static capitalize(text: string): string { + return text.charAt(0).toUpperCase() + text.slice(1); + } + } From ab7b69c26233955d6a7c2bbb08f71c4411d8e765 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Tue, 16 Jul 2024 15:13:10 +0200 Subject: [PATCH 3/8] MOBILE-4616 chore: Move waitForResizeDone to CoreWait --- .../rich-text-editor/rich-text-editor.ts | 2 +- src/core/services/utils/dom.ts | 19 ++---------- src/core/singletons/dom.ts | 4 +-- src/core/singletons/wait.ts | 29 +++++++++++++++++++ 4 files changed, 35 insertions(+), 19 deletions(-) diff --git a/src/core/features/editor/components/rich-text-editor/rich-text-editor.ts b/src/core/features/editor/components/rich-text-editor/rich-text-editor.ts index c611b2e4d..9a27b95d1 100644 --- a/src/core/features/editor/components/rich-text-editor/rich-text-editor.ts +++ b/src/core/features/editor/components/rich-text-editor/rich-text-editor.ts @@ -1045,7 +1045,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterViewInit, * Window resized. */ protected async windowResized(): Promise { - await CoreDomUtils.waitForResizeDone(); + await CoreWait.waitForResizeDone(); this.isPhone = CoreScreen.isMobile; this.maximizeEditorSize(); diff --git a/src/core/services/utils/dom.ts b/src/core/services/utils/dom.ts index 5090f66c8..68ff56d09 100644 --- a/src/core/services/utils/dom.ts +++ b/src/core/services/utils/dom.ts @@ -1715,24 +1715,11 @@ export class CoreDomUtilsProvider { * @param windowHeight Initial window height. * @param retries Number of retries done. * @returns Promise resolved when done. + * + * @deprecated since 4.5. Use CoreWait.waitForResizeDone instead. */ async waitForResizeDone(windowWidth?: number, windowHeight?: number, retries = 0): Promise { - if (!CorePlatform.isIOS()) { - return; // Only wait in iOS. - } - - windowWidth = windowWidth || window.innerWidth; - windowHeight = windowHeight || window.innerHeight; - - if (windowWidth != window.innerWidth || windowHeight != window.innerHeight || retries >= 10) { - // Window size changed or max number of retries reached, stop. - return; - } - - // Wait a bit and try again. - await CoreWait.wait(50); - - return this.waitForResizeDone(windowWidth, windowHeight, retries+1); + return CoreWait.waitForResizeDone(windowWidth, windowHeight, retries); } /** diff --git a/src/core/singletons/dom.ts b/src/core/singletons/dom.ts index 07c0c425e..8f962cc87 100644 --- a/src/core/singletons/dom.ts +++ b/src/core/singletons/dom.ts @@ -13,10 +13,10 @@ // limitations under the License. import { CoreCancellablePromise } from '@classes/cancellable-promise'; -import { CoreDomUtils } from '@services/utils/dom'; import { CoreUtils } from '@services/utils/utils'; import { CoreEventObserver } from '@singletons/events'; import { CorePlatform } from '@services/platform'; +import { CoreWait } from './wait'; /** * Singleton with helper functions for dom. @@ -214,7 +214,7 @@ export class CoreDom { */ static onWindowResize(resizeFunction: (ev?: Event) => void, debounceDelay = 20): CoreEventObserver { const resizeListener = CoreUtils.debounce(async (ev?: Event) => { - await CoreDomUtils.waitForResizeDone(); + await CoreWait.waitForResizeDone(); resizeFunction(ev); }, debounceDelay); diff --git a/src/core/singletons/wait.ts b/src/core/singletons/wait.ts index d2a633464..72e6c6ea8 100644 --- a/src/core/singletons/wait.ts +++ b/src/core/singletons/wait.ts @@ -13,6 +13,7 @@ // limitations under the License. import { CoreCancellablePromise } from '@classes/cancellable-promise'; +import { CorePlatform } from '@services/platform'; /** * Singleton with helper functions to wait. @@ -83,6 +84,34 @@ export class CoreWait { ); } + /** + * In iOS the resize event is triggered before the window size changes. Wait for the size to change. + * Use of this function is discouraged. Please use CoreDom.onWindowResize to check window resize event. + * + * @param windowWidth Initial window width. + * @param windowHeight Initial window height. + * @param retries Number of retries done. + * @returns Promise resolved when done. + */ + static async waitForResizeDone(windowWidth?: number, windowHeight?: number, retries = 0): Promise { + if (!CorePlatform.isIOS()) { + return; // Only wait in iOS. + } + + windowWidth = windowWidth || window.innerWidth; + windowHeight = windowHeight || window.innerHeight; + + if (windowWidth != window.innerWidth || windowHeight != window.innerHeight || retries >= 10) { + // Window size changed or max number of retries reached, stop. + return; + } + + // Wait a bit and try again. + await CoreWait.wait(50); + + return CoreWait.waitForResizeDone(windowWidth, windowHeight, retries+1); + } + } /** From 3f9ea653f96bab11815f6ea3311ee69cf589ef45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Tue, 16 Jul 2024 15:24:04 +0200 Subject: [PATCH 4/8] MOBILE-4616 chore: Move modals functions to CoreModalsService --- src/addons/calendar/pages/day/day.ts | 3 +- src/addons/calendar/pages/index/index.ts | 3 +- .../enrol/guest/services/enrol-handler.ts | 3 +- .../enrol/self/services/enrol-handler.ts | 3 +- .../messages/pages/discussion/discussion.ts | 3 +- .../classes/base-feedback-plugin-component.ts | 4 +- .../mod/book/pages/contents/contents.ts | 3 +- src/addons/mod/chat/pages/chat/chat.ts | 3 +- src/addons/mod/data/components/index/index.ts | 3 +- src/addons/mod/imscp/pages/view/view.ts | 3 +- src/addons/mod/lesson/pages/player/player.ts | 3 +- .../mod/lesson/services/handlers/prefetch.ts | 4 +- src/addons/mod/quiz/pages/player/player.ts | 3 +- src/addons/mod/quiz/pages/review/review.ts | 3 +- .../mod/quiz/services/handlers/prefetch.ts | 1 - src/addons/mod/quiz/services/quiz-helper.ts | 3 +- src/addons/mod/scorm/pages/player/player.ts | 3 +- src/addons/mod/wiki/components/index/index.ts | 3 +- .../mod/workshop/components/index/index.ts | 4 +- src/addons/notes/pages/list/list.ts | 3 +- src/core/classes/page-loads-manager.ts | 4 +- src/core/components/combobox/combobox.ts | 4 +- .../side-blocks-button/side-blocks-button.ts | 4 +- .../services/contentlinks-helper.ts | 3 +- .../course/classes/main-resource-component.ts | 3 +- .../components/course-format/course-format.ts | 5 +- .../pages/module-preview/module-preview.ts | 3 +- .../features/course/services/course-helper.ts | 3 +- .../features/dataprivacy/pages/main/main.ts | 5 +- src/core/features/login/pages/site/site.ts | 5 +- .../user-menu-button/user-menu-button.ts | 4 +- .../components/user-menu/user-menu.ts | 3 +- .../policy/pages/acceptances/acceptances.ts | 3 +- .../policy/pages/site-policy/site-policy.ts | 3 +- .../rating/components/aggregate/aggregate.ts | 4 +- .../reportbuilder/pages/report/report.ts | 4 +- .../pages/global-search/global-search.ts | 3 +- .../services/sharedfiles-helper.ts | 3 +- .../features/sitehome/pages/index/index.ts | 22 +-- .../components/module-index/module-index.ts | 4 +- src/core/services/modals.ts | 127 +++++++++++++++++- src/core/services/utils/dom.ts | 104 ++------------ src/core/services/utils/text.ts | 4 +- src/core/services/utils/utils.ts | 4 +- 44 files changed, 232 insertions(+), 160 deletions(-) diff --git a/src/addons/calendar/pages/day/day.ts b/src/addons/calendar/pages/day/day.ts index 1edf50164..88183fd6c 100644 --- a/src/addons/calendar/pages/day/day.ts +++ b/src/addons/calendar/pages/day/day.ts @@ -49,6 +49,7 @@ import { AddonCalendarEventsSource } from '@addons/calendar/classes/events-sourc import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; import { CoreUrl } from '@singletons/url'; import { CoreTime } from '@singletons/time'; +import { CoreModals } from '@services/modals'; /** * Page that displays the calendar events for a certain day. @@ -375,7 +376,7 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { * Show the filter menu. */ async openFilter(): Promise { - await CoreDomUtils.openSideModal({ + await CoreModals.openSideModal({ component: AddonCalendarFilterComponent, componentProps: { courses: this.manager?.getSource().courses, diff --git a/src/addons/calendar/pages/index/index.ts b/src/addons/calendar/pages/index/index.ts index 0943286ae..7201116a0 100644 --- a/src/addons/calendar/pages/index/index.ts +++ b/src/addons/calendar/pages/index/index.ts @@ -32,6 +32,7 @@ import { AddonCalendarFilterComponent } from '../../components/filter/filter'; import { CoreNavigator } from '@services/navigator'; import { CoreConstants } from '@/core/constants'; import { CoreMainMenuDeepLinkManager } from '@features/mainmenu/classes/deep-link-manager'; +import { CoreModals } from '@services/modals'; /** * Page that displays the calendar events. @@ -330,7 +331,7 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy { * Show the filter menu. */ async openFilter(): Promise { - await CoreDomUtils.openSideModal({ + await CoreModals.openSideModal({ component: AddonCalendarFilterComponent, componentProps: { courses: this.courses, diff --git a/src/addons/enrol/guest/services/enrol-handler.ts b/src/addons/enrol/guest/services/enrol-handler.ts index 00c2a1313..cb5795dfb 100644 --- a/src/addons/enrol/guest/services/enrol-handler.ts +++ b/src/addons/enrol/guest/services/enrol-handler.ts @@ -25,6 +25,7 @@ import { CorePasswordModalResponse } from '@components/password-modal/password-m import { CoreDomUtils } from '@services/utils/dom'; import { CoreWSError } from '@classes/errors/wserror'; import { CoreEnrol, CoreEnrolEnrolmentMethod } from '@features/enrol/services/enrol'; +import { CoreModals } from '@services/modals'; /** * Enrol handler. @@ -118,7 +119,7 @@ export class AddonEnrolGuestHandlerService implements CoreEnrolGuestHandler { }; try { - const response = await CoreDomUtils.promptPassword({ + const response = await CoreModals.promptPassword({ title: method.name, validator: validatePassword, }); diff --git a/src/addons/enrol/self/services/enrol-handler.ts b/src/addons/enrol/self/services/enrol-handler.ts index 5cc0589f0..c2838f53d 100644 --- a/src/addons/enrol/self/services/enrol-handler.ts +++ b/src/addons/enrol/self/services/enrol-handler.ts @@ -20,6 +20,7 @@ import { CorePasswordModalResponse } from '@components/password-modal/password-m import { CoreCoursesProvider } from '@features/courses/services/courses'; import { CoreDomUtils } from '@services/utils/dom'; import { CoreEnrol, CoreEnrolEnrolmentMethod } from '@features/enrol/services/enrol'; +import { CoreModals } from '@services/modals'; /** * Enrol handler. @@ -145,7 +146,7 @@ export class AddonEnrolSelfHandlerService implements CoreEnrolSelfHandler { if (!response.validated) { try { - const response = await CoreDomUtils.promptPassword({ + const response = await CoreModals.promptPassword({ validator: validatePassword, title: method.name, placeholder: 'addon.enrol_self.password', diff --git a/src/addons/messages/pages/discussion/discussion.ts b/src/addons/messages/pages/discussion/discussion.ts index 78df7f2e3..19b55f398 100644 --- a/src/addons/messages/pages/discussion/discussion.ts +++ b/src/addons/messages/pages/discussion/discussion.ts @@ -47,6 +47,7 @@ import { CoreDom } from '@singletons/dom'; import { CoreKeyboard } from '@singletons/keyboard'; import { CoreText } from '@singletons/text'; import { CoreWait } from '@singletons/wait'; +import { CoreModals } from '@services/modals'; /** * Page that displays a message discussion page. @@ -1250,7 +1251,7 @@ export class AddonMessagesDiscussionPage implements OnInit, OnDestroy, AfterView await import('@addons/messages/components/conversation-info/conversation-info.module'); // Display the group information. - const userId = await CoreDomUtils.openSideModal({ + const userId = await CoreModals.openSideModal({ component: AddonMessagesConversationInfoComponent, componentProps: { conversationId: this.conversationId, diff --git a/src/addons/mod/assign/classes/base-feedback-plugin-component.ts b/src/addons/mod/assign/classes/base-feedback-plugin-component.ts index 8af588332..31713b758 100644 --- a/src/addons/mod/assign/classes/base-feedback-plugin-component.ts +++ b/src/addons/mod/assign/classes/base-feedback-plugin-component.ts @@ -15,7 +15,7 @@ import { Component, Input } from '@angular/core'; import { CoreCanceledError } from '@classes/errors/cancelederror'; import { CoreError } from '@classes/errors/error'; -import { CoreDomUtils } from '@services/utils/dom'; +import { CoreModals } from '@services/modals'; import { AddonModAssignFeedbackCommentsTextData } from '../feedback/comments/services/handler'; import { AddonModAssignAssign, AddonModAssignPlugin, AddonModAssignSubmission } from '../services/assign'; @@ -49,7 +49,7 @@ export class AddonModAssignFeedbackPluginBaseComponent implements IAddonModAssig await import('@addons/mod/assign/components/edit-feedback-modal/edit-feedback-modal'); // Create the navigation modal. - const modalData = await CoreDomUtils.openModal({ + const modalData = await CoreModals.openModal({ component: AddonModAssignEditFeedbackModalComponent, componentProps: { assign: this.assign, diff --git a/src/addons/mod/book/pages/contents/contents.ts b/src/addons/mod/book/pages/contents/contents.ts index 77b1f47ac..6376ddb63 100644 --- a/src/addons/mod/book/pages/contents/contents.ts +++ b/src/addons/mod/book/pages/contents/contents.ts @@ -40,6 +40,7 @@ import { import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; import { CoreUrl } from '@singletons/url'; import { ADDON_MOD_BOOK_COMPONENT, AddonModBookNavStyle } from '../../constants'; +import { CoreModals } from '@services/modals'; /** * Page that displays a book contents. @@ -250,7 +251,7 @@ export class AddonModBookContentsPage implements OnInit, OnDestroy { // Create the toc modal. const visibleChapter = this.manager?.getSelectedItem(); - const modalData = await CoreDomUtils.openSideModal({ + const modalData = await CoreModals.openSideModal({ component: AddonModBookTocComponent, componentProps: { moduleId: this.cmId, diff --git a/src/addons/mod/chat/pages/chat/chat.ts b/src/addons/mod/chat/pages/chat/chat.ts index f1d770852..aa8de8521 100644 --- a/src/addons/mod/chat/pages/chat/chat.ts +++ b/src/addons/mod/chat/pages/chat/chat.ts @@ -31,6 +31,7 @@ import { CoreTime } from '@singletons/time'; import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; import { CoreKeyboard } from '@singletons/keyboard'; import { CoreWait } from '@singletons/wait'; +import { CoreModals } from '@services/modals'; /** * Page that displays a chat session. @@ -187,7 +188,7 @@ export class AddonModChatChatPage implements OnInit, OnDestroy, CanLeave { */ async showChatUsers(): Promise { // Create the toc modal. - const modalData = await CoreDomUtils.openSideModal({ + const modalData = await CoreModals.openSideModal({ component: AddonModChatUsersModalComponent, componentProps: { sessionId: this.sessionId, diff --git a/src/addons/mod/data/components/index/index.ts b/src/addons/mod/data/components/index/index.ts index c8c029718..94264157d 100644 --- a/src/addons/mod/data/components/index/index.ts +++ b/src/addons/mod/data/components/index/index.ts @@ -51,6 +51,7 @@ import { AddonModDataTemplateType, AddonModDataTemplateMode, } from '../../constants'; +import { CoreModals } from '@services/modals'; const contentToken = ''; @@ -401,7 +402,7 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp async showSearch(): Promise { const { AddonModDataSearchModalComponent } = await import('@addons/mod/data/components/search-modal/search-modal'); - const modalData = await CoreDomUtils.openModal({ + const modalData = await CoreModals.openModal({ component: AddonModDataSearchModalComponent, componentProps: { search: this.search, diff --git a/src/addons/mod/imscp/pages/view/view.ts b/src/addons/mod/imscp/pages/view/view.ts index 7a96a3324..60e1cf34c 100644 --- a/src/addons/mod/imscp/pages/view/view.ts +++ b/src/addons/mod/imscp/pages/view/view.ts @@ -28,6 +28,7 @@ import { CoreUtils } from '@services/utils/utils'; import { Translate } from '@singletons'; import { AddonModImscpTocComponent } from '../../components/toc/toc'; import { AddonModImscp, AddonModImscpImscp, AddonModImscpTocItem } from '../../services/imscp'; +import { CoreModals } from '@services/modals'; /** * Page that displays a IMSCP content. @@ -273,7 +274,7 @@ export class AddonModImscpViewPage implements OnInit { */ async showToc(): Promise { // Create the toc modal. - const itemHref = await CoreDomUtils.openSideModal({ + const itemHref = await CoreModals.openSideModal({ component: AddonModImscpTocComponent, componentProps: { items: this.items, diff --git a/src/addons/mod/lesson/pages/player/player.ts b/src/addons/mod/lesson/pages/player/player.ts index 6c8228e11..85203bfba 100644 --- a/src/addons/mod/lesson/pages/player/player.ts +++ b/src/addons/mod/lesson/pages/player/player.ts @@ -55,6 +55,7 @@ import { AddonModLessonSync } from '../../services/lesson-sync'; import { CoreFormFields, CoreForms } from '@singletons/form'; import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; import { ADDON_MOD_LESSON_COMPONENT, AddonModLessonJumpTo } from '../../constants'; +import { CoreModals } from '@services/modals'; /** * Page that allows attempting and reviewing a lesson. @@ -829,7 +830,7 @@ export class AddonModLessonPlayerPage implements OnInit, OnDestroy, CanLeave { async showMenu(): Promise { this.menuShown = true; - await CoreDomUtils.openSideModal({ + await CoreModals.openSideModal({ component: AddonModLessonMenuModalPage, componentProps: { pageInstance: this, diff --git a/src/addons/mod/lesson/services/handlers/prefetch.ts b/src/addons/mod/lesson/services/handlers/prefetch.ts index 852a07d58..6df58a118 100644 --- a/src/addons/mod/lesson/services/handlers/prefetch.ts +++ b/src/addons/mod/lesson/services/handlers/prefetch.ts @@ -21,7 +21,7 @@ import { CoreFilepool } from '@services/filepool'; import { CoreGroups } from '@services/groups'; import { CoreFileSizeSum, CorePluginFileDelegate } from '@services/plugin-file-delegate'; import { CoreSites, CoreSitesReadingStrategy } from '@services/sites'; -import { CoreDomUtils } from '@services/utils/dom'; +import { CoreModals } from '@services/modals'; import { CoreUtils } from '@services/utils/utils'; import { CoreWSFile } from '@services/ws'; import { makeSingleton, Translate } from '@singletons'; @@ -136,7 +136,7 @@ export class AddonModLessonPrefetchHandlerService extends CoreCourseActivityPref } // Create and show the modal. - const response = await CoreDomUtils.promptPassword({ + const response = await CoreModals.promptPassword({ title: 'addon.mod_lesson.enterpassword', placeholder: 'core.login.password', submit: 'addon.mod_lesson.continue', diff --git a/src/addons/mod/quiz/pages/player/player.ts b/src/addons/mod/quiz/pages/player/player.ts index c67450887..7a1b56f99 100644 --- a/src/addons/mod/quiz/pages/player/player.ts +++ b/src/addons/mod/quiz/pages/player/player.ts @@ -55,6 +55,7 @@ import { CoreWSError } from '@classes/errors/wserror'; import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; import { ADDON_MOD_QUIZ_ATTEMPT_FINISHED_EVENT, AddonModQuizAttemptStates, ADDON_MOD_QUIZ_COMPONENT } from '../../constants'; import { CoreWait } from '@singletons/wait'; +import { CoreModals } from '@services/modals'; /** * Page that allows attempting a quiz. @@ -728,7 +729,7 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave { } // Create the navigation modal. - const modalData = await CoreDomUtils.openSideModal({ + const modalData = await CoreModals.openSideModal({ component: AddonModQuizNavigationModalComponent, componentProps: { navigation: this.navigation, diff --git a/src/addons/mod/quiz/pages/review/review.ts b/src/addons/mod/quiz/pages/review/review.ts index 524ebed9e..13c95f478 100644 --- a/src/addons/mod/quiz/pages/review/review.ts +++ b/src/addons/mod/quiz/pages/review/review.ts @@ -36,6 +36,7 @@ import { import { AddonModQuizHelper } from '../../services/quiz-helper'; import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; import { ADDON_MOD_QUIZ_COMPONENT } from '../../constants'; +import { CoreModals } from '@services/modals'; /** * Page that allows reviewing a quiz attempt. @@ -266,7 +267,7 @@ export class AddonModQuizReviewPage implements OnInit { async openNavigation(): Promise { // Create the navigation modal. - const modalData = await CoreDomUtils.openSideModal({ + const modalData = await CoreModals.openSideModal({ component: AddonModQuizNavigationModalComponent, componentProps: { navigation: this.navigation, diff --git a/src/addons/mod/quiz/services/handlers/prefetch.ts b/src/addons/mod/quiz/services/handlers/prefetch.ts index b6c17276b..9f30d9406 100644 --- a/src/addons/mod/quiz/services/handlers/prefetch.ts +++ b/src/addons/mod/quiz/services/handlers/prefetch.ts @@ -474,7 +474,6 @@ export class AddonModQuizPrefetchHandlerService extends CoreCourseActivityPrefet * @param accessInfo Quiz access info. * @param attempt Attempt. * @param modOptions Other options. - * @param siteId Site ID. * @returns Promise resolved when done. */ protected async prefetchAttemptReview( diff --git a/src/addons/mod/quiz/services/quiz-helper.ts b/src/addons/mod/quiz/services/quiz-helper.ts index 2bc6d85ea..24f2e6337 100644 --- a/src/addons/mod/quiz/services/quiz-helper.ts +++ b/src/addons/mod/quiz/services/quiz-helper.ts @@ -40,6 +40,7 @@ import { import { QuestionDisplayOptionsMarks } from '@features/question/constants'; import { CoreGroups } from '@services/groups'; import { CoreTimeUtils } from '@services/utils/time'; +import { CoreModals } from '@services/modals'; /** * Helper service that provides some features for quiz. @@ -272,7 +273,7 @@ export class AddonModQuizHelperProvider { await import('@addons/mod/quiz/components/preflight-modal/preflight-modal'); // Create and show the modal. - const modalData = await CoreDomUtils.openModal>({ + const modalData = await CoreModals.openModal>({ component: AddonModQuizPreflightModalComponent, componentProps: { title: options.title, diff --git a/src/addons/mod/scorm/pages/player/player.ts b/src/addons/mod/scorm/pages/player/player.ts index c9d4f6aa5..f93df0c48 100644 --- a/src/addons/mod/scorm/pages/player/player.ts +++ b/src/addons/mod/scorm/pages/player/player.ts @@ -44,6 +44,7 @@ import { ADDON_MOD_SCORM_UPDATE_TOC_EVENT, } from '../../constants'; import { CoreWait } from '@singletons/wait'; +import { CoreModals } from '@services/modals'; /** * Page that allows playing a SCORM. @@ -513,7 +514,7 @@ export class AddonModScormPlayerPage implements OnInit, OnDestroy { * Show the TOC. */ async openToc(): Promise { - const modalData = await CoreDomUtils.openSideModal({ + const modalData = await CoreModals.openSideModal({ component: AddonModScormTocComponent, componentProps: { toc: this.toc, diff --git a/src/addons/mod/wiki/components/index/index.ts b/src/addons/mod/wiki/components/index/index.ts index c9940f548..590245828 100644 --- a/src/addons/mod/wiki/components/index/index.ts +++ b/src/addons/mod/wiki/components/index/index.ts @@ -58,6 +58,7 @@ import { ADDON_MOD_WIKI_PAGE_CREATED_EVENT, ADDON_MOD_WIKI_PAGE_NAME, } from '../../constants'; +import { CoreModals } from '@services/modals'; /** * Component that displays a wiki entry page. @@ -665,7 +666,7 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp async openMap(): Promise { // Create the toc modal. const { AddonModWikiMapModalComponent } = await import('../map/map'); - const modalData = await CoreDomUtils.openSideModal({ + const modalData = await CoreModals.openSideModal({ component: AddonModWikiMapModalComponent, componentProps: { pages: this.subwikiPages, diff --git a/src/addons/mod/workshop/components/index/index.ts b/src/addons/mod/workshop/components/index/index.ts index 628d70853..ee4337778 100644 --- a/src/addons/mod/workshop/components/index/index.ts +++ b/src/addons/mod/workshop/components/index/index.ts @@ -21,7 +21,7 @@ import { IonContent } from '@ionic/angular'; import { CoreGroupInfo, CoreGroups } from '@services/groups'; import { CoreNavigator } from '@services/navigator'; import { CorePlatform } from '@services/platform'; -import { CoreDomUtils } from '@services/utils/dom'; +import { CoreModals } from '@services/modals'; import { CoreUtils } from '@services/utils/utils'; import { CoreEventObserver, CoreEvents } from '@singletons/events'; import { Subscription } from 'rxjs'; @@ -400,7 +400,7 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity const { AddonModWorkshopPhaseInfoModalComponent } = await import('@addons/mod/workshop/components/phase-modal/phase-modal'); - const modalData = await CoreDomUtils.openModal({ + const modalData = await CoreModals.openModal({ component: AddonModWorkshopPhaseInfoModalComponent, componentProps: { phases: CoreUtils.objectToArray(this.phases), diff --git a/src/addons/notes/pages/list/list.ts b/src/addons/notes/pages/list/list.ts index 840ef91dd..d4cc56a65 100644 --- a/src/addons/notes/pages/list/list.ts +++ b/src/addons/notes/pages/list/list.ts @@ -32,6 +32,7 @@ import { Translate } from '@singletons'; import { CoreEventObserver, CoreEvents } from '@singletons/events'; import { CoreTime } from '@singletons/time'; import { CoreToasts, ToastDuration } from '@services/toasts'; +import { CoreModals } from '@services/modals'; /** * Page that displays a list of notes. @@ -197,7 +198,7 @@ export class AddonNotesListPage implements OnInit, OnDestroy { const { AddonNotesAddComponent } = await import('@addons/notes/components/add/add-modal'); - const modalData = await CoreDomUtils.openModal({ + const modalData = await CoreModals.openModal({ component: AddonNotesAddComponent, componentProps: { userId: this.userId, diff --git a/src/core/classes/page-loads-manager.ts b/src/core/classes/page-loads-manager.ts index a67816df0..174f6a66d 100644 --- a/src/core/classes/page-loads-manager.ts +++ b/src/core/classes/page-loads-manager.ts @@ -13,7 +13,7 @@ // limitations under the License. import { CoreNavigator } from '@services/navigator'; -import { CoreDomUtils } from '@services/utils/dom'; +import { CoreModals } from '@services/modals'; import { Subject } from 'rxjs'; import { AsyncDirective } from './async-directive'; import { PageLoadWatcher } from './page-load-watcher'; @@ -131,7 +131,7 @@ export class PageLoadsManager { const { CoreRefreshButtonModalComponent } = await import('@components/refresh-button-modal/refresh-button-modal'); - await CoreDomUtils.openModal({ + await CoreModals.openModal({ component: CoreRefreshButtonModalComponent, cssClass: 'core-modal-no-background core-modal-fullscreen', closeOnNavigate: true, diff --git a/src/core/components/combobox/combobox.ts b/src/core/components/combobox/combobox.ts index ddf7e75e8..e85060584 100644 --- a/src/core/components/combobox/combobox.ts +++ b/src/core/components/combobox/combobox.ts @@ -15,7 +15,7 @@ import { Component, EventEmitter, Input, Output } from '@angular/core'; import { Translate } from '@singletons'; import { ModalOptions } from '@ionic/core'; -import { CoreDomUtils } from '@services/utils/dom'; +import { CoreModals } from '@services/modals'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; /** @@ -123,7 +123,7 @@ export class CoreComboboxComponent implements ControlValueAccessor { this.modalOptions.id = this.listboxId; } - const data = await CoreDomUtils.openModal(this.modalOptions); + const data = await CoreModals.openModal(this.modalOptions); this.expanded = false; if (data) { diff --git a/src/core/features/block/components/side-blocks-button/side-blocks-button.ts b/src/core/features/block/components/side-blocks-button/side-blocks-button.ts index 69ae1e0ac..77ca2aacb 100644 --- a/src/core/features/block/components/side-blocks-button/side-blocks-button.ts +++ b/src/core/features/block/components/side-blocks-button/side-blocks-button.ts @@ -16,7 +16,7 @@ import { Component, ElementRef, Input, OnDestroy, OnInit } from '@angular/core'; import { CoreCancellablePromise } from '@classes/cancellable-promise'; import { CoreUserTourDirectiveOptions } from '@directives/user-tour'; import { CoreUserToursAlignment, CoreUserToursSide } from '@features/usertours/services/user-tours'; -import { CoreDomUtils } from '@services/utils/dom'; +import { CoreModals } from '@services/modals'; import { CoreDom } from '@singletons/dom'; import { CoreBlockSideBlocksTourComponent } from '../side-blocks-tour/side-blocks-tour'; import { CoreBlockSideBlocksComponent } from '../side-blocks/side-blocks'; @@ -68,7 +68,7 @@ export class CoreBlockSideBlocksButtonComponent implements OnInit, OnDestroy { * Open side blocks. */ openBlocks(): void { - CoreDomUtils.openSideModal({ + CoreModals.openSideModal({ component: CoreBlockSideBlocksComponent, componentProps: { contextLevel: this.contextLevel, diff --git a/src/core/features/contentlinks/services/contentlinks-helper.ts b/src/core/features/contentlinks/services/contentlinks-helper.ts index b7ba93788..4f63b6770 100644 --- a/src/core/features/contentlinks/services/contentlinks-helper.ts +++ b/src/core/features/contentlinks/services/contentlinks-helper.ts @@ -20,6 +20,7 @@ import { CoreSite } from '@classes/sites/site'; import { makeSingleton, Translate } from '@singletons'; import { CoreNavigator } from '@services/navigator'; import { CoreCustomURLSchemes } from '@services/urlschemes'; +import { CoreModals } from '@services/modals'; /** * Service that provides some features regarding content links. @@ -98,7 +99,7 @@ export class CoreContentLinksHelperProvider { const { CoreContentLinksChooseSiteModalComponent } = await import('@features/contentlinks/components/choose-site-modal/choose-site-modal'); - await CoreDomUtils.openModal({ + await CoreModals.openModal({ component: CoreContentLinksChooseSiteModalComponent, componentProps: { url: url, diff --git a/src/core/features/course/classes/main-resource-component.ts b/src/core/features/course/classes/main-resource-component.ts index d5c4f064c..a2e8c5d95 100644 --- a/src/core/features/course/classes/main-resource-component.ts +++ b/src/core/features/course/classes/main-resource-component.ts @@ -34,6 +34,7 @@ import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; import { CoreUrl } from '@singletons/url'; import { CoreTime } from '@singletons/time'; import { CoreText } from '@singletons/text'; +import { CoreModals } from '@services/modals'; /** * Result of a resource download. @@ -424,7 +425,7 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, return; } - const data = await CoreDomUtils.openSideModal({ + const data = await CoreModals.openSideModal({ component: CoreCourseModuleSummaryComponent, componentProps: { moduleId: this.module.id, diff --git a/src/core/features/course/components/course-format/course-format.ts b/src/core/features/course/components/course-format/course-format.ts index 63ddb340f..e836d75d7 100644 --- a/src/core/features/course/components/course-format/course-format.ts +++ b/src/core/features/course/components/course-format/course-format.ts @@ -53,6 +53,7 @@ import { CoreUserTourDirectiveOptions } from '@directives/user-tour'; import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; import { CoreBlockSideBlocksComponent } from '@features/block/components/side-blocks/side-blocks'; import { ContextLevel } from '@/core/constants'; +import { CoreModals } from '@services/modals'; /** * Component to display course contents using a certain format. If the format isn't found, use default one. @@ -316,7 +317,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { this.sectionChanged(section); } } else if (this.initialBlockInstanceId && this.displayBlocks && this.hasBlocks) { - CoreDomUtils.openSideModal({ + CoreModals.openSideModal({ component: CoreBlockSideBlocksComponent, componentProps: { contextLevel: ContextLevel.COURSE, @@ -431,7 +432,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { const { CoreCourseCourseIndexComponent } = await import('@features/course/components/course-index/course-index'); - const data = await CoreDomUtils.openModal({ + const data = await CoreModals.openModal({ component: CoreCourseCourseIndexComponent, initialBreakpoint: 1, breakpoints: [0, 1], diff --git a/src/core/features/course/pages/module-preview/module-preview.ts b/src/core/features/course/pages/module-preview/module-preview.ts index bbaf90c66..86219e53e 100644 --- a/src/core/features/course/pages/module-preview/module-preview.ts +++ b/src/core/features/course/pages/module-preview/module-preview.ts @@ -20,6 +20,7 @@ import { import { CoreCourse } from '@features/course/services/course'; import { CoreCourseHelper, CoreCourseModuleData, CoreCourseSection } from '@features/course/services/course-helper'; import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate'; +import { CoreModals } from '@services/modals'; import { CoreNavigator } from '@services/navigator'; import { CoreSites } from '@services/sites'; import { CoreDomUtils } from '@services/utils/dom'; @@ -106,7 +107,7 @@ export class CoreCourseModulePreviewPage implements OnInit { return; } - const data = await CoreDomUtils.openSideModal({ + const data = await CoreModals.openSideModal({ component: CoreCourseModuleSummaryComponent, componentProps: { moduleId: this.module.id, diff --git a/src/core/features/course/services/course-helper.ts b/src/core/features/course/services/course-helper.ts index b3e31546f..f0671b3f4 100644 --- a/src/core/features/course/services/course-helper.ts +++ b/src/core/features/course/services/course-helper.ts @@ -75,6 +75,7 @@ import { CoreLocalNotifications } from '@services/local-notifications'; import { CoreEnrol } from '@features/enrol/services/enrol'; import { CoreEnrolAction, CoreEnrolDelegate } from '@features/enrol/services/enrol-delegate'; import { LazyRoutesModule } from '@/app/app-routing.module'; +import { CoreModals } from '@services/modals'; /** * Prefetch info of a module. @@ -2009,7 +2010,7 @@ export class CoreCourseHelperProvider { * @param course Course selected */ openCourseSummary(course: CoreCourseWithImageAndColor & CoreCourseAnyCourseData): void { - CoreDomUtils.openSideModal({ + CoreModals.openSideModal({ component: CoreCourseSummaryPage, componentProps: { courseId: course.id, diff --git a/src/core/features/dataprivacy/pages/main/main.ts b/src/core/features/dataprivacy/pages/main/main.ts index 6ac26a9cc..36fd84bd3 100644 --- a/src/core/features/dataprivacy/pages/main/main.ts +++ b/src/core/features/dataprivacy/pages/main/main.ts @@ -19,6 +19,7 @@ import { CoreDataPrivacyGetAccessInformationWSResponse, CoreDataPrivacyRequest, } from '@features/dataprivacy/services/dataprivacy'; +import { CoreModals } from '@services/modals'; import { CoreNavigator } from '@services/navigator'; import { CoreScreen } from '@services/screen'; import { CoreDomUtils } from '@services/utils/dom'; @@ -117,7 +118,7 @@ export class CoreDataPrivacyMainPage implements OnInit { await import('@features/dataprivacy/components/contactdpo/contactdpo'); // Create and show the modal. - const succeed = await CoreDomUtils.openModal({ + const succeed = await CoreModals.openModal({ component: CoreDataPrivacyContactDPOComponent, }); @@ -139,7 +140,7 @@ export class CoreDataPrivacyMainPage implements OnInit { await import('@features/dataprivacy/components/newrequest/newrequest'); // Create and show the modal. - const succeed = await CoreDomUtils.openModal({ + const succeed = await CoreModals.openModal({ component: CoreDataPrivacyNewRequestComponent, componentProps: { accessInfo: this.accessInfo, diff --git a/src/core/features/login/pages/site/site.ts b/src/core/features/login/pages/site/site.ts index b127025c0..49202fd68 100644 --- a/src/core/features/login/pages/site/site.ts +++ b/src/core/features/login/pages/site/site.ts @@ -46,6 +46,7 @@ import { CoreSitesFactory } from '@services/sites-factory'; import { ONBOARDING_DONE } from '@features/login/constants'; import { CoreUnauthenticatedSite } from '@classes/sites/unauthenticated-site'; import { CoreKeyboard } from '@singletons/keyboard'; +import { CoreModals } from '@services/modals'; /** * Site (url) chooser when adding a new site. @@ -260,7 +261,7 @@ export class CoreLoginSitePage implements OnInit { const { CoreLoginSiteHelpComponent } = await import('@features/login/components/site-help/site-help'); - await CoreDomUtils.openModal({ + await CoreModals.openModal({ component: CoreLoginSiteHelpComponent, cssClass: 'core-modal-fullscreen', }); @@ -273,7 +274,7 @@ export class CoreLoginSitePage implements OnInit { const { CoreLoginSiteOnboardingComponent } = await import('@features/login/components/site-onboarding/site-onboarding'); - await CoreDomUtils.openModal({ + await CoreModals.openModal({ component: CoreLoginSiteOnboardingComponent, cssClass: 'core-modal-fullscreen', }); diff --git a/src/core/features/mainmenu/components/user-menu-button/user-menu-button.ts b/src/core/features/mainmenu/components/user-menu-button/user-menu-button.ts index e747efdeb..b520be854 100644 --- a/src/core/features/mainmenu/components/user-menu-button/user-menu-button.ts +++ b/src/core/features/mainmenu/components/user-menu-button/user-menu-button.ts @@ -19,7 +19,7 @@ import { CoreUserToursAlignment, CoreUserToursSide } from '@features/usertours/s import { IonRouterOutlet } from '@ionic/angular'; import { CoreScreen } from '@services/screen'; import { CoreSites } from '@services/sites'; -import { CoreDomUtils } from '@services/utils/dom'; +import { CoreModals } from '@services/modals'; import { CoreMainMenuUserMenuTourComponent } from '../user-menu-tour/user-menu-tour'; import { CoreMainMenuUserMenuComponent } from '../user-menu/user-menu'; import { CoreMainMenuPage } from '@features/mainmenu/pages/menu/menu'; @@ -66,7 +66,7 @@ export class CoreMainMenuUserButtonComponent implements OnInit { event.preventDefault(); event.stopPropagation(); - CoreDomUtils.openSideModal({ + CoreModals.openSideModal({ component: CoreMainMenuUserMenuComponent, }); } diff --git a/src/core/features/mainmenu/components/user-menu/user-menu.ts b/src/core/features/mainmenu/components/user-menu/user-menu.ts index af928f3c7..1387bea70 100644 --- a/src/core/features/mainmenu/components/user-menu/user-menu.ts +++ b/src/core/features/mainmenu/components/user-menu/user-menu.ts @@ -28,6 +28,7 @@ import { CoreUserProfileHandlerType, CoreUserDelegateContext, } from '@features/user/services/user-delegate'; +import { CoreModals } from '@services/modals'; import { CoreNavigator } from '@services/navigator'; import { CoreSites } from '@services/sites'; import { CoreDomUtils } from '@services/utils/dom'; @@ -248,7 +249,7 @@ export class CoreMainMenuUserMenuComponent implements OnInit, OnDestroy { event.preventDefault(); event.stopPropagation(); - const closeAll = await CoreDomUtils.openSideModal({ + const closeAll = await CoreModals.openSideModal({ component: CoreLoginSitesModalComponent, cssClass: 'core-modal-lateral core-modal-lateral-sm', }); diff --git a/src/core/features/policy/pages/acceptances/acceptances.ts b/src/core/features/policy/pages/acceptances/acceptances.ts index 3344c6957..9f4513565 100644 --- a/src/core/features/policy/pages/acceptances/acceptances.ts +++ b/src/core/features/policy/pages/acceptances/acceptances.ts @@ -26,6 +26,7 @@ import { Subscription } from 'rxjs'; import { CORE_DATAPRIVACY_FEATURE_NAME, CORE_DATAPRIVACY_PAGE_NAME } from '@features/dataprivacy/constants'; import { CoreNavigator } from '@services/navigator'; import { CoreDataPrivacy } from '@features/dataprivacy/services/dataprivacy'; +import { CoreModals } from '@services/modals'; /** * Page to view user acceptances. @@ -194,7 +195,7 @@ export class CorePolicyAcceptancesPage implements OnInit, OnDestroy { const { CorePolicyViewPolicyModalComponent } = await import('@features/policy/components/policy-modal/policy-modal'); - CoreDomUtils.openModal({ + CoreModals.openModal({ component: CorePolicyViewPolicyModalComponent, componentProps: { policy }, }); diff --git a/src/core/features/policy/pages/site-policy/site-policy.ts b/src/core/features/policy/pages/site-policy/site-policy.ts index 46e65efb1..e59851459 100644 --- a/src/core/features/policy/pages/site-policy/site-policy.ts +++ b/src/core/features/policy/pages/site-policy/site-policy.ts @@ -31,6 +31,7 @@ import { CoreScreen } from '@services/screen'; import { Subscription } from 'rxjs'; import { CoreDom } from '@singletons/dom'; import { CoreWait } from '@singletons/wait'; +import { CoreModals } from '@services/modals'; /** * Page to accept a site policy. @@ -468,7 +469,7 @@ export class CorePolicySitePolicyPage implements OnInit, OnDestroy { const { CorePolicyViewPolicyModalComponent } = await import('@features/policy/components/policy-modal/policy-modal'); - CoreDomUtils.openModal({ + CoreModals.openModal({ component: CorePolicyViewPolicyModalComponent, componentProps: { policy }, }); diff --git a/src/core/features/rating/components/aggregate/aggregate.ts b/src/core/features/rating/components/aggregate/aggregate.ts index 8f81aac08..7837e6343 100644 --- a/src/core/features/rating/components/aggregate/aggregate.ts +++ b/src/core/features/rating/components/aggregate/aggregate.ts @@ -20,8 +20,8 @@ import { CoreRatingInfoItem, CoreRatingProvider, } from '@features/rating/services/rating'; +import { CoreModals } from '@services/modals'; import { CoreSites } from '@services/sites'; -import { CoreDomUtils } from '@services/utils/dom'; import { CoreEventObserver, CoreEvents } from '@singletons/events'; /** @@ -120,7 +120,7 @@ export class CoreRatingAggregateComponent implements OnChanges, OnDestroy { const { CoreRatingRatingsComponent } = await import('@features/rating/components/ratings/ratings'); - await CoreDomUtils.openModal({ + await CoreModals.openModal({ component: CoreRatingRatingsComponent, componentProps: { contextLevel: this.contextLevel, diff --git a/src/core/features/reportbuilder/pages/report/report.ts b/src/core/features/reportbuilder/pages/report/report.ts index 958509675..c44252df5 100644 --- a/src/core/features/reportbuilder/pages/report/report.ts +++ b/src/core/features/reportbuilder/pages/report/report.ts @@ -15,8 +15,8 @@ import { Component, OnInit } from '@angular/core'; import { CoreReportBuilderReportSummaryComponent } from '@features/reportbuilder/components/report-summary/report-summary'; import { CoreReportBuilderReportDetail } from '@features/reportbuilder/services/reportbuilder'; +import { CoreModals } from '@services/modals'; import { CoreNavigator } from '@services/navigator'; -import { CoreDomUtils } from '@services/utils/dom'; @Component({ selector: 'core-report-builder-report', @@ -43,7 +43,7 @@ export class CoreReportBuilderReportPage implements OnInit { } openInfo(): void { - CoreDomUtils.openSideModal({ + CoreModals.openSideModal({ component: CoreReportBuilderReportSummaryComponent, componentProps: { reportDetail: this.reportDetail }, }); diff --git a/src/core/features/search/pages/global-search/global-search.ts b/src/core/features/search/pages/global-search/global-search.ts index c67d2abfc..059899d8c 100644 --- a/src/core/features/search/pages/global-search/global-search.ts +++ b/src/core/features/search/pages/global-search/global-search.ts @@ -29,6 +29,7 @@ import { } from '@features/search/services/global-search'; import { CoreNavigator } from '@services/navigator'; import { CoreSearchBoxComponent } from '@features/search/components/search-box/search-box'; +import { CoreModals } from '@services/modals'; @Component({ selector: 'page-core-search-global-search', @@ -139,7 +140,7 @@ export class CoreSearchGlobalSearchPage implements OnInit, OnDestroy, AfterViewI const { CoreSearchGlobalSearchFiltersComponent } = await import('@features/search/components/global-search-filters/global-search-filters.module'); - await CoreDomUtils.openSideModal({ + await CoreModals.openSideModal({ component: CoreSearchGlobalSearchFiltersComponent, componentProps: { hideCourses: !!this.courseId, diff --git a/src/core/features/sharedfiles/services/sharedfiles-helper.ts b/src/core/features/sharedfiles/services/sharedfiles-helper.ts index ba178ba95..0c8613bcb 100644 --- a/src/core/features/sharedfiles/services/sharedfiles-helper.ts +++ b/src/core/features/sharedfiles/services/sharedfiles-helper.ts @@ -30,6 +30,7 @@ import { SHAREDFILES_PAGE_NAME } from '../constants'; import { CoreSharedFilesChooseSitePage } from '../pages/choose-site/choose-site'; import { CoreError } from '@classes/errors/error'; import { CorePlatform } from '@services/platform'; +import { CoreModals } from '@services/modals'; /** * Helper service to share files with the app. @@ -152,7 +153,7 @@ export class CoreSharedFilesHelperProvider { const { CoreSharedFilesListModalComponent } = await import('@features/sharedfiles/components/list-modal/list-modal'); - const file = await CoreDomUtils.openModal({ + const file = await CoreModals.openModal({ component: CoreSharedFilesListModalComponent, cssClass: 'core-modal-fullscreen', componentProps: { mimetypes, pick: true }, diff --git a/src/core/features/sitehome/pages/index/index.ts b/src/core/features/sitehome/pages/index/index.ts index 508e2fcc1..30ff59412 100644 --- a/src/core/features/sitehome/pages/index/index.ts +++ b/src/core/features/sitehome/pages/index/index.ts @@ -32,6 +32,7 @@ import { CoreTime } from '@singletons/time'; import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; import { CoreBlockSideBlocksComponent } from '@features/block/components/side-blocks/side-blocks'; import { ContextLevel } from '@/core/constants'; +import { CoreModals } from '@services/modals'; /** * Page that displays site home index. @@ -229,17 +230,18 @@ export class CoreSiteHomeIndexPage implements OnInit, OnDestroy { */ private openFocusedInstance() { const blockInstanceId = CoreNavigator.getRouteNumberParam('blockInstanceId'); - - if (blockInstanceId) { - CoreDomUtils.openSideModal({ - component: CoreBlockSideBlocksComponent, - componentProps: { - contextLevel: ContextLevel.COURSE, - instanceId: this.siteHomeId, - initialBlockInstanceId: blockInstanceId, - }, - }); + if (!blockInstanceId) { + return; } + + CoreModals.openSideModal({ + component: CoreBlockSideBlocksComponent, + componentProps: { + contextLevel: ContextLevel.COURSE, + instanceId: this.siteHomeId, + initialBlockInstanceId: blockInstanceId, + }, + }); } } 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 f7e5a0180..970092bb7 100644 --- a/src/core/features/siteplugins/components/module-index/module-index.ts +++ b/src/core/features/siteplugins/components/module-index/module-index.ts @@ -30,7 +30,7 @@ import { CoreSitePluginsContent, CoreSitePluginsCourseModuleHandlerData, } from '@features/siteplugins/services/siteplugins'; -import { CoreDomUtils } from '@services/utils/dom'; +import { CoreModals } from '@services/modals'; import { CoreUtils } from '@services/utils/utils'; import { CoreSitePluginsPluginContentComponent, CoreSitePluginsPluginContentLoadedData } from '../plugin-content/plugin-content'; @@ -157,7 +157,7 @@ export class CoreSitePluginsModuleIndexComponent implements OnInit, OnDestroy, C return; } - const data = await CoreDomUtils.openSideModal({ + const data = await CoreModals.openSideModal({ component: CoreCourseModuleSummaryComponent, componentProps: { moduleId: this.module.id, diff --git a/src/core/services/modals.ts b/src/core/services/modals.ts index 78a1f2bf6..13df097d7 100644 --- a/src/core/services/modals.ts +++ b/src/core/services/modals.ts @@ -14,10 +14,19 @@ import { Constructor } from '@/core/utils/types'; import { Injectable } from '@angular/core'; +import { NavigationStart } from '@angular/router'; import { CoreModalComponent } from '@classes/modal-component'; +import { CoreModalLateralTransitionEnter, CoreModalLateralTransitionLeave } from '@classes/modal-lateral-transition'; import { CoreSheetModalComponent } from '@components/sheet-modal/sheet-modal'; -import { AngularFrameworkDelegate, makeSingleton } from '@singletons'; +import { AngularFrameworkDelegate, makeSingleton, ModalController, Router } from '@singletons'; import { CoreDirectivesRegistry } from '@singletons/directives-registry'; +import { Subscription, filter } from 'rxjs'; +import { Md5 } from 'ts-md5'; +import { fixOverlayAriaHidden } from '../utils/fix-aria-hidden'; +import { ModalOptions } from '@ionic/angular'; +import { CoreCanceledError } from '@classes/errors/cancelederror'; +import { CoreWSError } from '@classes/errors/wserror'; +import { CorePasswordModalResponse, CorePasswordModalParams } from '@components/password-modal/password-modal'; /** * Handles application modals. @@ -25,6 +34,8 @@ import { CoreDirectivesRegistry } from '@singletons/directives-registry'; @Injectable({ providedIn: 'root' }) export class CoreModalsService { + protected displayedModals: Record = {}; // To prevent duplicated modals. + /** * Get index of the overlay on top of the stack. * @@ -84,6 +95,118 @@ export class CoreModalsService { return modal.result; } -} + /** + * Opens a Modal. + * + * @param options Modal Options. + * @returns The modal data when the modal closes. + */ + async openModal( + options: OpenModalOptions, + ): Promise { + const { waitForDismissCompleted, closeOnNavigate, ...modalOptions } = options; + const listenCloseEvents = closeOnNavigate ?? true; // Default to true. + // TODO: Improve this if we need two modals with same component open at the same time. + const modalId = Md5.hashAsciiStr(options.component?.toString() || ''); + const alreadyDisplayed = !!this.displayedModals[modalId]; + + const modal = alreadyDisplayed + ? this.displayedModals[modalId] + : await ModalController.create(modalOptions); + + let navSubscription: Subscription | undefined; + + // Get the promise before presenting to get result if modal is suddenly hidden. + const resultPromise = waitForDismissCompleted ? modal.onDidDismiss() : modal.onWillDismiss(); + + if (!this.displayedModals[modalId]) { + // Store the modal and remove it when dismissed. + this.displayedModals[modalId] = modal; + + if (listenCloseEvents) { + // Listen navigation events to close modals. + navSubscription = Router.events + .pipe(filter(event => event instanceof NavigationStart)) + .subscribe(async () => { + modal.dismiss(); + }); + } + + await modal.present(); + } + + if (!alreadyDisplayed) { + fixOverlayAriaHidden(modal); + } + + const result = await resultPromise; + + navSubscription?.unsubscribe(); + delete this.displayedModals[modalId]; + + if (result?.data) { + return result?.data; + } + } + + /** + * Opens a side Modal. + * + * @param options Modal Options. + * @returns The modal data when the modal closes. + */ + async openSideModal( + options: OpenModalOptions, + ): Promise { + + options = Object.assign({ + cssClass: 'core-modal-lateral', + showBackdrop: true, + backdropDismiss: true, + enterAnimation: CoreModalLateralTransitionEnter, + leaveAnimation: CoreModalLateralTransitionLeave, + }, options); + + return this.openModal(options); + } + + /** + * Prompts password to the user and returns the entered text. + * + * @param passwordParams Params to show the modal. + * @returns Entered password, error and validation. + */ + async promptPassword(passwordParams?: CorePasswordModalParams): Promise { + const { CorePasswordModalComponent } = + await import('@/core/components/password-modal/password-modal.module'); + + const modalData = await CoreModals.openModal( + { + cssClass: 'core-password-modal', + showBackdrop: true, + backdropDismiss: true, + component: CorePasswordModalComponent, + componentProps: passwordParams, + }, + ); + + if (modalData === undefined) { + throw new CoreCanceledError(); + } else if (modalData instanceof CoreWSError) { + throw modalData; + } + + return modalData; + } + +} export const CoreModals = makeSingleton(CoreModalsService); + +/** + * Options for the openModal function. + */ +export type OpenModalOptions = ModalOptions & { + waitForDismissCompleted?: boolean; + closeOnNavigate?: boolean; // Default true. +}; diff --git a/src/core/services/utils/dom.ts b/src/core/services/utils/dom.ts index 68ff56d09..e5503e6ab 100644 --- a/src/core/services/utils/dom.ts +++ b/src/core/services/utils/dom.ts @@ -14,7 +14,7 @@ import { Injectable, SimpleChange, KeyValueChanges } from '@angular/core'; import { IonContent } from '@ionic/angular'; -import { ModalOptions, PopoverOptions, AlertOptions, AlertButton, TextFieldTypes } from '@ionic/core'; +import { PopoverOptions, AlertOptions, AlertButton, TextFieldTypes } from '@ionic/core'; import { Md5 } from 'ts-md5'; import { CoreConfig } from '@services/config'; @@ -33,18 +33,12 @@ import { Translate, AlertController, PopoverController, - ModalController, - Router, } from '@singletons'; import { CoreLogger } from '@singletons/logger'; import { CoreFileSizeSum } from '@services/plugin-file-delegate'; import { CoreNetworkError } from '@classes/errors/network-error'; import { CoreBSTooltipComponent } from '@components/bs-tooltip/bs-tooltip'; -import { CoreModalLateralTransitionEnter, CoreModalLateralTransitionLeave } from '@classes/modal-lateral-transition'; import { CoreSites } from '@services/sites'; -import { NavigationStart } from '@angular/router'; -import { filter } from 'rxjs/operators'; -import { Subscription } from 'rxjs'; import { CoreNetwork } from '@services/network'; import { CoreSiteError } from '@classes/errors/siteerror'; import { CoreUserSupport } from '@features/user/services/support'; @@ -53,12 +47,12 @@ import { CorePlatform } from '@services/platform'; import { CoreCancellablePromise } from '@classes/cancellable-promise'; import { CoreLang } from '@services/lang'; import { CorePasswordModalParams, CorePasswordModalResponse } from '@components/password-modal/password-modal'; -import { CoreWSError } from '@classes/errors/wserror'; import { CoreErrorLogs } from '@singletons/error-logs'; import { CoreKeyboard } from '@singletons/keyboard'; import { CoreWait } from '@singletons/wait'; import { CoreToasts, ToastDuration, ShowToastOptions } from '../toasts'; import { fixOverlayAriaHidden } from '@/core/utils/fix-aria-hidden'; +import { CoreModals, OpenModalOptions } from '@services/modals'; /* * "Utils" service with helper functions for UI, DOM elements and HTML code. @@ -77,7 +71,6 @@ export class CoreDomUtilsProvider { protected matchesFunctionName?: string; // Name of the "matches" function to use when simulating a closest call. protected debugDisplay = false; // Whether to display debug messages. Store it in a variable to make it synchronous. protected displayedAlerts: Record = {}; // To prevent duplicated alerts. - protected displayedModals: Record = {}; // To prevent duplicated modals. protected activeLoadingModals: CoreIonLoadingElement[] = []; protected logger: CoreLogger; @@ -1465,54 +1458,13 @@ export class CoreDomUtilsProvider { * * @param options Modal Options. * @returns The modal data when the modal closes. + * + * @deprecated since 4.5. Use CoreModals.openModal instead. */ async openModal( options: OpenModalOptions, ): Promise { - const { waitForDismissCompleted, closeOnNavigate, ...modalOptions } = options; - const listenCloseEvents = closeOnNavigate ?? true; // Default to true. - - // TODO: Improve this if we need two modals with same component open at the same time. - const modalId = Md5.hashAsciiStr(options.component?.toString() || ''); - const alreadyDisplayed = !!this.displayedModals[modalId]; - - const modal = alreadyDisplayed - ? this.displayedModals[modalId] - : await ModalController.create(modalOptions); - - let navSubscription: Subscription | undefined; - - // Get the promise before presenting to get result if modal is suddenly hidden. - const resultPromise = waitForDismissCompleted ? modal.onDidDismiss() : modal.onWillDismiss(); - - if (!this.displayedModals[modalId]) { - // Store the modal and remove it when dismissed. - this.displayedModals[modalId] = modal; - - if (listenCloseEvents) { - // Listen navigation events to close modals. - navSubscription = Router.events - .pipe(filter(event => event instanceof NavigationStart)) - .subscribe(async () => { - modal.dismiss(); - }); - } - - await modal.present(); - } - - if (!alreadyDisplayed) { - fixOverlayAriaHidden(modal); - } - - const result = await resultPromise; - - navSubscription?.unsubscribe(); - delete this.displayedModals[modalId]; - - if (result?.data) { - return result?.data; - } + return CoreModals.openModal(options); } /** @@ -1520,20 +1472,13 @@ export class CoreDomUtilsProvider { * * @param options Modal Options. * @returns The modal data when the modal closes. + * + * @deprecated since 4.5. Use CoreModals.openSideModal instead. */ async openSideModal( options: OpenModalOptions, ): Promise { - - options = Object.assign({ - cssClass: 'core-modal-lateral', - showBackdrop: true, - backdropDismiss: true, - enterAnimation: CoreModalLateralTransitionEnter, - leaveAnimation: CoreModalLateralTransitionLeave, - }, options); - - return this.openModal(options); + return CoreModals.openSideModal(options); } /** @@ -1574,28 +1519,11 @@ export class CoreDomUtilsProvider { * * @param passwordParams Params to show the modal. * @returns Entered password, error and validation. + * + * @deprecated since 4.5. Use CoreModals.promptPassword instead. */ async promptPassword(passwordParams?: CorePasswordModalParams): Promise { - const { CorePasswordModalComponent } = - await import('@/core/components/password-modal/password-modal.module'); - - const modalData = await CoreDomUtils.openModal( - { - cssClass: 'core-password-modal', - showBackdrop: true, - backdropDismiss: true, - component: CorePasswordModalComponent, - componentProps: passwordParams, - }, - ); - - if (modalData === undefined) { - throw new CoreCanceledError(); - } else if (modalData instanceof CoreWSError) { - throw modalData; - } - - return modalData; + return CoreModals.promptPassword(passwordParams); } /** @@ -1617,7 +1545,7 @@ export class CoreDomUtilsProvider { } const { CoreViewerImageComponent } = await import('@features/viewer/components/image/image'); - await CoreDomUtils.openModal({ + await CoreModals.openModal({ component: CoreViewerImageComponent, componentProps: { title, @@ -1765,14 +1693,6 @@ export type OpenPopoverOptions = Omit & { waitForDismissCompleted?: boolean; }; -/** - * Options for the openModal function. - */ -export type OpenModalOptions = ModalOptions & { - waitForDismissCompleted?: boolean; - closeOnNavigate?: boolean; // Default true. -}; - /** * Buttons for prompt alert. */ diff --git a/src/core/services/utils/text.ts b/src/core/services/utils/text.ts index 2bfb864ed..5215f87d0 100644 --- a/src/core/services/utils/text.ts +++ b/src/core/services/utils/text.ts @@ -21,7 +21,7 @@ import { DomSanitizer, makeSingleton, Translate } from '@singletons'; import { CoreWSFile } from '@services/ws'; import { Locutus } from '@singletons/locutus'; import { CoreFileHelper } from '@services/file-helper'; -import { CoreDomUtils } from './dom'; +import { CoreModals } from '@services/modals'; import { CoreUrl } from '@singletons/url'; import { AlertButton } from '@ionic/angular'; import { CorePath } from '@singletons/path'; @@ -1050,7 +1050,7 @@ export class CoreTextUtilsProvider { ...options, }; - await CoreDomUtils.openModal(modalOptions); + await CoreModals.openModal(modalOptions); } } diff --git a/src/core/services/utils/utils.ts b/src/core/services/utils/utils.ts index cdced27db..0c1724eaf 100644 --- a/src/core/services/utils/utils.ts +++ b/src/core/services/utils/utils.ts @@ -20,7 +20,7 @@ import { CoreEvents } from '@singletons/events'; import { CoreFile } from '@services/file'; import { CoreLang } from '@services/lang'; import { CoreWS } from '@services/ws'; -import { CoreDomUtils } from '@services/utils/dom'; +import { CoreModals } from '@services/modals'; import { CoreMimetypeUtils } from '@services/utils/mimetype'; import { CoreTextUtils } from '@services/utils/text'; import { makeSingleton, InAppBrowser, FileOpener, WebIntent, Translate, NgZone } from '@singletons'; @@ -1657,7 +1657,7 @@ export class CoreUtilsProvider { async scanQR(title?: string): Promise { const { CoreViewerQRScannerComponent } = await import('@features/viewer/components/qr-scanner/qr-scanner'); - return CoreDomUtils.openModal({ + return CoreModals.openModal({ component: CoreViewerQRScannerComponent, cssClass: 'core-modal-fullscreen', componentProps: { From 48259062fd49f30e3f6588fced055a83ee11d41b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Tue, 16 Jul 2024 16:12:21 +0200 Subject: [PATCH 5/8] MOBILE-4616 chore: Import side modals during runtime --- .../calendar/components/components.module.ts | 3 -- .../calendar/components/filter/filter.ts | 5 ++++ src/addons/calendar/pages/day/day.ts | 3 +- src/addons/calendar/pages/index/index.ts | 3 +- .../components/components.module.ts | 2 -- .../mod/book/components/components.module.ts | 3 -- src/addons/mod/book/components/toc/toc.ts | 7 ++++- .../mod/book/pages/contents/contents.ts | 3 +- .../mod/chat/components/components.module.ts | 3 -- .../components/users-modal/users-modal.ts | 6 +++- src/addons/mod/chat/pages/chat/chat.ts | 4 ++- .../choice/components/components.module.ts | 2 -- .../feedback/components/components.module.ts | 2 -- .../glossary/components/components.module.ts | 2 -- .../components/components.module.ts | 2 -- .../mod/imscp/components/components.module.ts | 3 -- src/addons/mod/imscp/components/toc/toc.ts | 5 ++++ src/addons/mod/imscp/pages/view/view.ts | 3 +- .../lesson/components/components.module.ts | 5 ---- .../components/menu-modal/menu-modal.ts | 5 ++++ src/addons/mod/lesson/pages/player/player.ts | 3 +- .../mod/quiz/components/components.module.ts | 3 -- .../navigation-modal/navigation-modal.ts | 5 ++++ src/addons/mod/quiz/pages/player/player.ts | 3 +- src/addons/mod/quiz/pages/review/review.ts | 3 +- .../resource/components/components.module.ts | 2 -- .../mod/scorm/components/components.module.ts | 5 ---- src/addons/mod/scorm/components/toc/toc.ts | 5 ++++ src/addons/mod/scorm/pages/player/player.ts | 3 +- .../mod/wiki/components/components.module.ts | 6 ---- src/addons/mod/wiki/components/index/index.ts | 4 ++- src/addons/mod/wiki/components/map/map.ts | 5 ++++ .../subwiki-picker/subwiki-picker.ts | 5 ++++ .../block/components/components.module.ts | 3 -- .../side-blocks-button/side-blocks-button.ts | 5 ++-- .../components/side-blocks/side-blocks.ts | 9 +++++- .../course/classes/main-resource-component.ts | 4 ++- .../course/components/components.module.ts | 3 -- .../components/course-format/course-format.ts | 3 +- .../module-summary/module-summary.ts | 7 ++++- .../pages/module-preview/module-preview.ts | 7 ++--- .../features/course/services/course-helper.ts | 6 ++-- .../editor/components/components.module.ts | 2 -- .../h5p/components/components.module.ts | 2 -- .../login/components/components.module.ts | 3 -- .../components/sites-modal/sites-modal.ts | 5 ++++ .../mainmenu/components/components.module.ts | 3 -- .../user-menu-button/user-menu-button.ts | 5 ++-- .../components/user-menu/user-menu.ts | 10 +++++-- .../components/components.module.ts | 3 -- .../report-summary/report-summary.ts | 13 +++++++- .../reportbuilder/pages/report/report.ts | 10 +++++-- .../global-search-filters.component.ts | 5 ++++ .../global-search-filters.module.ts | 30 ------------------- .../pages/global-search/global-search.ts | 2 +- .../features/sitehome/pages/index/index.ts | 5 ++-- .../components/module-index/module-index.ts | 7 ++--- 57 files changed, 145 insertions(+), 130 deletions(-) delete mode 100644 src/core/features/search/components/global-search-filters/global-search-filters.module.ts diff --git a/src/addons/calendar/components/components.module.ts b/src/addons/calendar/components/components.module.ts index fffd48aa8..aa476bee7 100644 --- a/src/addons/calendar/components/components.module.ts +++ b/src/addons/calendar/components/components.module.ts @@ -18,13 +18,11 @@ import { CoreSharedModule } from '@/core/shared.module'; import { AddonCalendarCalendarComponent } from './calendar/calendar'; import { AddonCalendarUpcomingEventsComponent } from './upcoming-events/upcoming-events'; -import { AddonCalendarFilterComponent } from './filter/filter'; @NgModule({ declarations: [ AddonCalendarCalendarComponent, AddonCalendarUpcomingEventsComponent, - AddonCalendarFilterComponent, ], imports: [ CoreSharedModule, @@ -32,7 +30,6 @@ import { AddonCalendarFilterComponent } from './filter/filter'; exports: [ AddonCalendarCalendarComponent, AddonCalendarUpcomingEventsComponent, - AddonCalendarFilterComponent, ], }) export class AddonCalendarComponentsModule {} diff --git a/src/addons/calendar/components/filter/filter.ts b/src/addons/calendar/components/filter/filter.ts index 8c1330e68..94db1a1ca 100644 --- a/src/addons/calendar/components/filter/filter.ts +++ b/src/addons/calendar/components/filter/filter.ts @@ -20,6 +20,7 @@ import { CoreEvents } from '@singletons/events'; import { AddonCalendarEventType, AddonCalendarProvider } from '../../services/calendar'; import { AddonCalendarFilter, AddonCalendarEventIcons } from '../../services/calendar-helper'; import { ALL_COURSES_ID } from '@features/courses/services/courses-helper'; +import { CoreSharedModule } from '@/core/shared.module'; /** * Component to display the events filter that includes events types and a list of courses. @@ -28,6 +29,10 @@ import { ALL_COURSES_ID } from '@features/courses/services/courses-helper'; selector: 'addon-calendar-filter', templateUrl: 'filter.html', styleUrls: ['../../calendar-common.scss', 'filter.scss'], + standalone: true, + imports: [ + CoreSharedModule, + ], }) export class AddonCalendarFilterComponent implements OnInit { diff --git a/src/addons/calendar/pages/day/day.ts b/src/addons/calendar/pages/day/day.ts index 88183fd6c..16faee918 100644 --- a/src/addons/calendar/pages/day/day.ts +++ b/src/addons/calendar/pages/day/day.ts @@ -30,7 +30,6 @@ import { AddonCalendarFilter, AddonCalendarHelper } from '../../services/calenda import { AddonCalendarSync, AddonCalendarSyncProvider } from '../../services/calendar-sync'; import { CoreCategoryData, CoreCourses, CoreEnrolledCourseData } from '@features/courses/services/courses'; import { CoreCoursesHelper } from '@features/courses/services/courses-helper'; -import { AddonCalendarFilterComponent } from '../../components/filter/filter'; import moment from 'moment-timezone'; import { NgZone, Translate } from '@singletons'; import { CoreNavigator } from '@services/navigator'; @@ -376,6 +375,8 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { * Show the filter menu. */ async openFilter(): Promise { + const { AddonCalendarFilterComponent } = await import('../../components/filter/filter'); + await CoreModals.openSideModal({ component: AddonCalendarFilterComponent, componentProps: { diff --git a/src/addons/calendar/pages/index/index.ts b/src/addons/calendar/pages/index/index.ts index 7201116a0..7ee837bf6 100644 --- a/src/addons/calendar/pages/index/index.ts +++ b/src/addons/calendar/pages/index/index.ts @@ -28,7 +28,6 @@ import { CoreEnrolledCourseData } from '@features/courses/services/courses'; import { ActivatedRoute, Params } from '@angular/router'; import { AddonCalendarCalendarComponent } from '../../components/calendar/calendar'; import { AddonCalendarUpcomingEventsComponent } from '../../components/upcoming-events/upcoming-events'; -import { AddonCalendarFilterComponent } from '../../components/filter/filter'; import { CoreNavigator } from '@services/navigator'; import { CoreConstants } from '@/core/constants'; import { CoreMainMenuDeepLinkManager } from '@features/mainmenu/classes/deep-link-manager'; @@ -331,6 +330,8 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy { * Show the filter menu. */ async openFilter(): Promise { + const { AddonCalendarFilterComponent } = await import('../../components/filter/filter'); + await CoreModals.openSideModal({ component: AddonCalendarFilterComponent, componentProps: { diff --git a/src/addons/mod/bigbluebuttonbn/components/components.module.ts b/src/addons/mod/bigbluebuttonbn/components/components.module.ts index d786b230c..db3d5cf55 100644 --- a/src/addons/mod/bigbluebuttonbn/components/components.module.ts +++ b/src/addons/mod/bigbluebuttonbn/components/components.module.ts @@ -25,8 +25,6 @@ import { CoreCourseComponentsModule } from '@features/course/components/componen CoreSharedModule, CoreCourseComponentsModule, ], - providers: [ - ], exports: [ AddonModBBBIndexComponent, ], diff --git a/src/addons/mod/book/components/components.module.ts b/src/addons/mod/book/components/components.module.ts index d31d09782..c3f80c2b4 100644 --- a/src/addons/mod/book/components/components.module.ts +++ b/src/addons/mod/book/components/components.module.ts @@ -18,12 +18,10 @@ import { CoreSharedModule } from '@/core/shared.module'; import { CoreCourseComponentsModule } from '@features/course/components/components.module'; import { AddonModBookIndexComponent } from './index/index'; -import { AddonModBookTocComponent } from './toc/toc'; @NgModule({ declarations: [ AddonModBookIndexComponent, - AddonModBookTocComponent, ], imports: [ CoreSharedModule, @@ -31,7 +29,6 @@ import { AddonModBookTocComponent } from './toc/toc'; ], exports: [ AddonModBookIndexComponent, - AddonModBookTocComponent, ], }) export class AddonModBookComponentsModule {} diff --git a/src/addons/mod/book/components/toc/toc.ts b/src/addons/mod/book/components/toc/toc.ts index f59ccdf2f..8aa4cf2ad 100644 --- a/src/addons/mod/book/components/toc/toc.ts +++ b/src/addons/mod/book/components/toc/toc.ts @@ -16,6 +16,7 @@ import { Component, Input, OnInit } from '@angular/core'; import { ModalController } from '@singletons'; import { AddonModBookTocChapter, AddonModBookBookWSData } from '../../services/book'; import { AddonModBookNumbering } from '../../constants'; +import { CoreSharedModule } from '@/core/shared.module'; /** * Modal to display the TOC of a book. @@ -23,7 +24,11 @@ import { AddonModBookNumbering } from '../../constants'; @Component({ selector: 'addon-mod-book-toc', templateUrl: 'toc.html', - styleUrls: ['toc.scss'], + styleUrl: 'toc.scss', + standalone: true, + imports: [ + CoreSharedModule, + ], }) export class AddonModBookTocComponent implements OnInit { diff --git a/src/addons/mod/book/pages/contents/contents.ts b/src/addons/mod/book/pages/contents/contents.ts index 6376ddb63..39b975103 100644 --- a/src/addons/mod/book/pages/contents/contents.ts +++ b/src/addons/mod/book/pages/contents/contents.ts @@ -30,7 +30,6 @@ import { CoreDomUtils } from '@services/utils/dom'; import { CoreTextUtils } from '@services/utils/text'; import { CoreUtils } from '@services/utils/utils'; import { Translate } from '@singletons'; -import { AddonModBookTocComponent } from '../../components/toc/toc'; import { AddonModBook, AddonModBookBookWSData, @@ -251,6 +250,8 @@ export class AddonModBookContentsPage implements OnInit, OnDestroy { // Create the toc modal. const visibleChapter = this.manager?.getSelectedItem(); + const { AddonModBookTocComponent } = await import('../../components/toc/toc'); + const modalData = await CoreModals.openSideModal({ component: AddonModBookTocComponent, componentProps: { diff --git a/src/addons/mod/chat/components/components.module.ts b/src/addons/mod/chat/components/components.module.ts index c0bdb4ad4..4f5182ce3 100644 --- a/src/addons/mod/chat/components/components.module.ts +++ b/src/addons/mod/chat/components/components.module.ts @@ -16,12 +16,10 @@ import { NgModule } from '@angular/core'; import { AddonModChatIndexComponent } from './index/index'; import { CoreSharedModule } from '@/core/shared.module'; import { CoreCourseComponentsModule } from '@features/course/components/components.module'; -import { AddonModChatUsersModalComponent } from './users-modal/users-modal'; @NgModule({ declarations: [ AddonModChatIndexComponent, - AddonModChatUsersModalComponent, ], imports: [ CoreSharedModule, @@ -29,7 +27,6 @@ import { AddonModChatUsersModalComponent } from './users-modal/users-modal'; ], exports: [ AddonModChatIndexComponent, - AddonModChatUsersModalComponent, ], }) export class AddonModChatComponentsModule {} diff --git a/src/addons/mod/chat/components/users-modal/users-modal.ts b/src/addons/mod/chat/components/users-modal/users-modal.ts index 3a8e0b435..1ebd510e7 100644 --- a/src/addons/mod/chat/components/users-modal/users-modal.ts +++ b/src/addons/mod/chat/components/users-modal/users-modal.ts @@ -11,7 +11,6 @@ // 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, Input, OnDestroy, OnInit } from '@angular/core'; import { CoreNetwork } from '@services/network'; import { CoreSites } from '@services/sites'; @@ -19,6 +18,7 @@ import { CoreDomUtils } from '@services/utils/dom'; import { ModalController, NgZone } from '@singletons'; import { Subscription } from 'rxjs'; import { AddonModChat, AddonModChatUser } from '../../services/chat'; +import { CoreSharedModule } from '@/core/shared.module'; /** * MMdal that displays the chat session users. @@ -26,6 +26,10 @@ import { AddonModChat, AddonModChatUser } from '../../services/chat'; @Component({ selector: 'addon-mod-chat-users-modal', templateUrl: 'users-modal.html', + standalone: true, + imports: [ + CoreSharedModule, + ], }) export class AddonModChatUsersModalComponent implements OnInit, OnDestroy { diff --git a/src/addons/mod/chat/pages/chat/chat.ts b/src/addons/mod/chat/pages/chat/chat.ts index aa8de8521..4565e5c72 100644 --- a/src/addons/mod/chat/pages/chat/chat.ts +++ b/src/addons/mod/chat/pages/chat/chat.ts @@ -24,7 +24,7 @@ import { CoreUtils } from '@services/utils/utils'; import { NgZone, Translate } from '@singletons'; import { CoreEvents } from '@singletons/events'; import { Subscription } from 'rxjs'; -import { AddonModChatUsersModalComponent, AddonModChatUsersModalResult } from '../../components/users-modal/users-modal'; +import { AddonModChatUsersModalResult } from '../../components/users-modal/users-modal'; import { AddonModChat, AddonModChatUser } from '../../services/chat'; import { AddonModChatFormattedMessage, AddonModChatHelper } from '../../services/chat-helper'; import { CoreTime } from '@singletons/time'; @@ -187,6 +187,8 @@ export class AddonModChatChatPage implements OnInit, OnDestroy, CanLeave { * Display the chat users modal. */ async showChatUsers(): Promise { + const { AddonModChatUsersModalComponent } = await import('../../components/users-modal/users-modal'); + // Create the toc modal. const modalData = await CoreModals.openSideModal({ component: AddonModChatUsersModalComponent, diff --git a/src/addons/mod/choice/components/components.module.ts b/src/addons/mod/choice/components/components.module.ts index 9a1aeb7fe..bef917cc3 100644 --- a/src/addons/mod/choice/components/components.module.ts +++ b/src/addons/mod/choice/components/components.module.ts @@ -25,8 +25,6 @@ import { AddonModChoiceIndexComponent } from './index/index'; CoreSharedModule, CoreCourseComponentsModule, ], - providers: [ - ], exports: [ AddonModChoiceIndexComponent, ], diff --git a/src/addons/mod/feedback/components/components.module.ts b/src/addons/mod/feedback/components/components.module.ts index ce2b48392..4db944b52 100644 --- a/src/addons/mod/feedback/components/components.module.ts +++ b/src/addons/mod/feedback/components/components.module.ts @@ -25,8 +25,6 @@ import { AddonModFeedbackIndexComponent } from './index/index'; CoreSharedModule, CoreCourseComponentsModule, ], - providers: [ - ], exports: [ AddonModFeedbackIndexComponent, ], diff --git a/src/addons/mod/glossary/components/components.module.ts b/src/addons/mod/glossary/components/components.module.ts index b35fa3942..f425cfff7 100644 --- a/src/addons/mod/glossary/components/components.module.ts +++ b/src/addons/mod/glossary/components/components.module.ts @@ -29,8 +29,6 @@ import { CoreSearchComponentsModule } from '@features/search/components/componen CoreCourseComponentsModule, CoreSearchComponentsModule, ], - providers: [ - ], exports: [ AddonModGlossaryIndexComponent, AddonModGlossaryModePickerPopoverComponent, diff --git a/src/addons/mod/h5pactivity/components/components.module.ts b/src/addons/mod/h5pactivity/components/components.module.ts index f0dc5a0ca..4147400cc 100644 --- a/src/addons/mod/h5pactivity/components/components.module.ts +++ b/src/addons/mod/h5pactivity/components/components.module.ts @@ -28,8 +28,6 @@ import { CoreH5PComponentsModule } from '@features/h5p/components/components.mod CoreCourseComponentsModule, CoreH5PComponentsModule, ], - providers: [ - ], exports: [ AddonModH5PActivityIndexComponent, ], diff --git a/src/addons/mod/imscp/components/components.module.ts b/src/addons/mod/imscp/components/components.module.ts index afd155ba6..de14a9f5a 100644 --- a/src/addons/mod/imscp/components/components.module.ts +++ b/src/addons/mod/imscp/components/components.module.ts @@ -18,12 +18,10 @@ import { CoreSharedModule } from '@/core/shared.module'; import { CoreCourseComponentsModule } from '@features/course/components/components.module'; import { AddonModImscpIndexComponent } from './index'; -import { AddonModImscpTocComponent } from './toc/toc'; @NgModule({ declarations: [ AddonModImscpIndexComponent, - AddonModImscpTocComponent, ], imports: [ CoreSharedModule, @@ -31,7 +29,6 @@ import { AddonModImscpTocComponent } from './toc/toc'; ], exports: [ AddonModImscpIndexComponent, - AddonModImscpTocComponent, ], }) export class AddonModImscpComponentsModule {} diff --git a/src/addons/mod/imscp/components/toc/toc.ts b/src/addons/mod/imscp/components/toc/toc.ts index 9378ba647..22527d45b 100644 --- a/src/addons/mod/imscp/components/toc/toc.ts +++ b/src/addons/mod/imscp/components/toc/toc.ts @@ -15,6 +15,7 @@ import { Component, Input } from '@angular/core'; import { ModalController } from '@singletons'; import { AddonModImscpTocItem } from '../../services/imscp'; +import { CoreSharedModule } from '@/core/shared.module'; /** * Modal to display the TOC of a imscp. @@ -22,6 +23,10 @@ import { AddonModImscpTocItem } from '../../services/imscp'; @Component({ selector: 'addon-mod-imscp-toc', templateUrl: 'toc.html', + standalone: true, + imports: [ + CoreSharedModule, + ], }) export class AddonModImscpTocComponent { diff --git a/src/addons/mod/imscp/pages/view/view.ts b/src/addons/mod/imscp/pages/view/view.ts index 60e1cf34c..41d3b782c 100644 --- a/src/addons/mod/imscp/pages/view/view.ts +++ b/src/addons/mod/imscp/pages/view/view.ts @@ -26,7 +26,6 @@ import { CoreDomUtils } from '@services/utils/dom'; import { CoreTextUtils } from '@services/utils/text'; import { CoreUtils } from '@services/utils/utils'; import { Translate } from '@singletons'; -import { AddonModImscpTocComponent } from '../../components/toc/toc'; import { AddonModImscp, AddonModImscpImscp, AddonModImscpTocItem } from '../../services/imscp'; import { CoreModals } from '@services/modals'; @@ -273,6 +272,8 @@ export class AddonModImscpViewPage implements OnInit { * Show the TOC. */ async showToc(): Promise { + const { AddonModImscpTocComponent } = await import('../../components/toc/toc'); + // Create the toc modal. const itemHref = await CoreModals.openSideModal({ component: AddonModImscpTocComponent, diff --git a/src/addons/mod/lesson/components/components.module.ts b/src/addons/mod/lesson/components/components.module.ts index 6daa4ec51..6822ef34b 100644 --- a/src/addons/mod/lesson/components/components.module.ts +++ b/src/addons/mod/lesson/components/components.module.ts @@ -17,22 +17,17 @@ import { NgModule } from '@angular/core'; import { CoreSharedModule } from '@/core/shared.module'; import { CoreCourseComponentsModule } from '@features/course/components/components.module'; import { AddonModLessonIndexComponent } from './index/index'; -import { AddonModLessonMenuModalPage } from './menu-modal/menu-modal'; @NgModule({ declarations: [ AddonModLessonIndexComponent, - AddonModLessonMenuModalPage, ], imports: [ CoreSharedModule, CoreCourseComponentsModule, ], - providers: [ - ], exports: [ AddonModLessonIndexComponent, - AddonModLessonMenuModalPage, ], }) export class AddonModLessonComponentsModule {} diff --git a/src/addons/mod/lesson/components/menu-modal/menu-modal.ts b/src/addons/mod/lesson/components/menu-modal/menu-modal.ts index 73b257e79..f448e86f1 100644 --- a/src/addons/mod/lesson/components/menu-modal/menu-modal.ts +++ b/src/addons/mod/lesson/components/menu-modal/menu-modal.ts @@ -16,6 +16,7 @@ import { Component, Input } from '@angular/core'; import { ModalController } from '@singletons'; import { AddonModLessonPlayerPage } from '../../pages/player/player'; +import { CoreSharedModule } from '@/core/shared.module'; /** * Modal that renders the lesson menu and media file. @@ -23,6 +24,10 @@ import { AddonModLessonPlayerPage } from '../../pages/player/player'; @Component({ selector: 'page-addon-mod-lesson-menu-modal', templateUrl: 'menu-modal.html', + standalone: true, + imports: [ + CoreSharedModule, + ], }) export class AddonModLessonMenuModalPage { diff --git a/src/addons/mod/lesson/pages/player/player.ts b/src/addons/mod/lesson/pages/player/player.ts index 85203bfba..57c984ef3 100644 --- a/src/addons/mod/lesson/pages/player/player.ts +++ b/src/addons/mod/lesson/pages/player/player.ts @@ -28,7 +28,6 @@ import { CoreUtils } from '@services/utils/utils'; import { CoreWSExternalFile } from '@services/ws'; import { ModalController, Translate } from '@singletons'; import { CoreEvents } from '@singletons/events'; -import { AddonModLessonMenuModalPage } from '../../components/menu-modal/menu-modal'; import { AddonModLesson, AddonModLessonEOLPageDataEntry, @@ -830,6 +829,8 @@ export class AddonModLessonPlayerPage implements OnInit, OnDestroy, CanLeave { async showMenu(): Promise { this.menuShown = true; + const { AddonModLessonMenuModalPage } = await import('../../components/menu-modal/menu-modal'); + await CoreModals.openSideModal({ component: AddonModLessonMenuModalPage, componentProps: { diff --git a/src/addons/mod/quiz/components/components.module.ts b/src/addons/mod/quiz/components/components.module.ts index 32f249166..82eb41016 100644 --- a/src/addons/mod/quiz/components/components.module.ts +++ b/src/addons/mod/quiz/components/components.module.ts @@ -18,7 +18,6 @@ import { CoreSharedModule } from '@/core/shared.module'; import { CoreCourseComponentsModule } from '@features/course/components/components.module'; import { AddonModQuizConnectionErrorComponent } from './connection-error/connection-error'; import { AddonModQuizIndexComponent } from './index/index'; -import { AddonModQuizNavigationModalComponent } from './navigation-modal/navigation-modal'; import { AddonModQuizAttemptInfoComponent } from './attempt-info/attempt-info'; import { AddonModQuizAttemptStateComponent } from './attempt-state/attempt-state'; import { AddonModQuizQuestionCardComponent } from './question-card/question-card'; @@ -29,7 +28,6 @@ import { AddonModQuizQuestionCardComponent } from './question-card/question-card AddonModQuizAttemptStateComponent, AddonModQuizIndexComponent, AddonModQuizConnectionErrorComponent, - AddonModQuizNavigationModalComponent, AddonModQuizQuestionCardComponent, ], imports: [ @@ -41,7 +39,6 @@ import { AddonModQuizQuestionCardComponent } from './question-card/question-card AddonModQuizAttemptStateComponent, AddonModQuizIndexComponent, AddonModQuizConnectionErrorComponent, - AddonModQuizNavigationModalComponent, AddonModQuizQuestionCardComponent, ], diff --git a/src/addons/mod/quiz/components/navigation-modal/navigation-modal.ts b/src/addons/mod/quiz/components/navigation-modal/navigation-modal.ts index 711ef23f8..1b71ea075 100644 --- a/src/addons/mod/quiz/components/navigation-modal/navigation-modal.ts +++ b/src/addons/mod/quiz/components/navigation-modal/navigation-modal.ts @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +import { CoreSharedModule } from '@/core/shared.module'; import { Component, Input } from '@angular/core'; import { CoreQuestionQuestionParsed } from '@features/question/services/question'; @@ -23,6 +24,10 @@ import { ModalController } from '@singletons'; @Component({ selector: 'addon-mod-quiz-navigation-modal', templateUrl: 'navigation-modal.html', + standalone: true, + imports: [ + CoreSharedModule, + ], }) export class AddonModQuizNavigationModalComponent { diff --git a/src/addons/mod/quiz/pages/player/player.ts b/src/addons/mod/quiz/pages/player/player.ts index 7a1b56f99..997fd63e4 100644 --- a/src/addons/mod/quiz/pages/player/player.ts +++ b/src/addons/mod/quiz/pages/player/player.ts @@ -33,7 +33,6 @@ import { ModalController, Translate } from '@singletons'; import { CoreEvents } from '@singletons/events'; import { AddonModQuizAutoSave } from '../../classes/auto-save'; import { - AddonModQuizNavigationModalComponent, AddonModQuizNavigationModalReturn, AddonModQuizNavigationQuestion, } from '../../components/navigation-modal/navigation-modal'; @@ -728,6 +727,8 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave { this.reloadNavigation = false; } + const { AddonModQuizNavigationModalComponent } = await import('../../components/navigation-modal/navigation-modal'); + // Create the navigation modal. const modalData = await CoreModals.openSideModal({ component: AddonModQuizNavigationModalComponent, diff --git a/src/addons/mod/quiz/pages/review/review.ts b/src/addons/mod/quiz/pages/review/review.ts index 13c95f478..5cfb5e244 100644 --- a/src/addons/mod/quiz/pages/review/review.ts +++ b/src/addons/mod/quiz/pages/review/review.ts @@ -22,7 +22,6 @@ import { CoreUtils } from '@services/utils/utils'; import { CoreDom } from '@singletons/dom'; import { CoreTime } from '@singletons/time'; import { - AddonModQuizNavigationModalComponent, AddonModQuizNavigationModalReturn, AddonModQuizNavigationQuestion, } from '../../components/navigation-modal/navigation-modal'; @@ -266,6 +265,8 @@ export class AddonModQuizReviewPage implements OnInit { } async openNavigation(): Promise { + const { AddonModQuizNavigationModalComponent } = await import('../../components/navigation-modal/navigation-modal'); + // Create the navigation modal. const modalData = await CoreModals.openSideModal({ component: AddonModQuizNavigationModalComponent, diff --git a/src/addons/mod/resource/components/components.module.ts b/src/addons/mod/resource/components/components.module.ts index a4e217b90..36d0dee31 100644 --- a/src/addons/mod/resource/components/components.module.ts +++ b/src/addons/mod/resource/components/components.module.ts @@ -27,8 +27,6 @@ import { AddonModResourceIndexComponent } from './index/index'; CoreSharedModule, CoreCourseComponentsModule, ], - providers: [ - ], exports: [ AddonModResourceIndexComponent, ], diff --git a/src/addons/mod/scorm/components/components.module.ts b/src/addons/mod/scorm/components/components.module.ts index 407c08e0b..a93ced361 100644 --- a/src/addons/mod/scorm/components/components.module.ts +++ b/src/addons/mod/scorm/components/components.module.ts @@ -16,22 +16,17 @@ import { NgModule } from '@angular/core'; import { AddonModScormIndexComponent } from './index/index'; import { CoreSharedModule } from '@/core/shared.module'; import { CoreCourseComponentsModule } from '@features/course/components/components.module'; -import { AddonModScormTocComponent } from './toc/toc'; @NgModule({ declarations: [ AddonModScormIndexComponent, - AddonModScormTocComponent, ], imports: [ CoreSharedModule, CoreCourseComponentsModule, ], - providers: [ - ], exports: [ AddonModScormIndexComponent, - AddonModScormTocComponent, ], }) export class AddonModScormComponentsModule {} diff --git a/src/addons/mod/scorm/components/toc/toc.ts b/src/addons/mod/scorm/components/toc/toc.ts index d1b48186f..42199ba11 100644 --- a/src/addons/mod/scorm/components/toc/toc.ts +++ b/src/addons/mod/scorm/components/toc/toc.ts @@ -17,6 +17,7 @@ import { ModalController } from '@singletons'; import { AddonModScormGetScormAccessInformationWSResponse } from '../../services/scorm'; import { AddonModScormTOCScoWithIcon } from '../../services/scorm-helper'; import { AddonModScormMode } from '../../constants'; +import { CoreSharedModule } from '@/core/shared.module'; /** * Modal to display the TOC of a SCORM. @@ -24,6 +25,10 @@ import { AddonModScormMode } from '../../constants'; @Component({ selector: 'addon-mod-scorm-toc', templateUrl: 'toc.html', + standalone: true, + imports: [ + CoreSharedModule, + ], }) export class AddonModScormTocComponent implements OnInit { diff --git a/src/addons/mod/scorm/pages/player/player.ts b/src/addons/mod/scorm/pages/player/player.ts index f93df0c48..e3d29384d 100644 --- a/src/addons/mod/scorm/pages/player/player.ts +++ b/src/addons/mod/scorm/pages/player/player.ts @@ -23,7 +23,6 @@ import { CoreTimeUtils } from '@services/utils/time'; import { CoreUtils } from '@services/utils/utils'; import { CoreEventObserver, CoreEvents } from '@singletons/events'; import { AddonModScormDataModel12 } from '../../classes/data-model-12'; -import { AddonModScormTocComponent } from '../../components/toc/toc'; import { AddonModScorm, AddonModScormAttemptCountResult, @@ -514,6 +513,8 @@ export class AddonModScormPlayerPage implements OnInit, OnDestroy { * Show the TOC. */ async openToc(): Promise { + const { AddonModScormTocComponent } = await import('../../components/toc/toc'); + const modalData = await CoreModals.openSideModal({ component: AddonModScormTocComponent, componentProps: { diff --git a/src/addons/mod/wiki/components/components.module.ts b/src/addons/mod/wiki/components/components.module.ts index 92b38259d..23e3e31ff 100644 --- a/src/addons/mod/wiki/components/components.module.ts +++ b/src/addons/mod/wiki/components/components.module.ts @@ -17,14 +17,10 @@ import { NgModule } from '@angular/core'; import { CoreCourseComponentsModule } from '@features/course/components/components.module'; import { CoreTagComponentsModule } from '@features/tag/components/components.module'; import { AddonModWikiIndexComponent } from './index/index'; -import { AddonModWikiMapModalComponent } from './map/map'; -import { AddonModWikiSubwikiPickerComponent } from './subwiki-picker/subwiki-picker'; @NgModule({ declarations: [ AddonModWikiIndexComponent, - AddonModWikiSubwikiPickerComponent, - AddonModWikiMapModalComponent, ], imports: [ CoreSharedModule, @@ -33,8 +29,6 @@ import { AddonModWikiSubwikiPickerComponent } from './subwiki-picker/subwiki-pic ], exports: [ AddonModWikiIndexComponent, - AddonModWikiSubwikiPickerComponent, - AddonModWikiMapModalComponent, ], }) export class AddonModWikiComponentsModule {} diff --git a/src/addons/mod/wiki/components/index/index.ts b/src/addons/mod/wiki/components/index/index.ts index 590245828..853ac31f1 100644 --- a/src/addons/mod/wiki/components/index/index.ts +++ b/src/addons/mod/wiki/components/index/index.ts @@ -664,8 +664,9 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp * Show the map. */ async openMap(): Promise { - // Create the toc modal. const { AddonModWikiMapModalComponent } = await import('../map/map'); + + // Create the map modal. const modalData = await CoreModals.openSideModal({ component: AddonModWikiMapModalComponent, componentProps: { @@ -885,6 +886,7 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp */ async showSubwikiPicker(event: MouseEvent): Promise { const { AddonModWikiSubwikiPickerComponent } = await import('../subwiki-picker/subwiki-picker'); + const subwiki = await CoreDomUtils.openPopover({ component: AddonModWikiSubwikiPickerComponent, componentProps: { diff --git a/src/addons/mod/wiki/components/map/map.ts b/src/addons/mod/wiki/components/map/map.ts index 9884350b0..9af480058 100644 --- a/src/addons/mod/wiki/components/map/map.ts +++ b/src/addons/mod/wiki/components/map/map.ts @@ -17,6 +17,7 @@ import { ModalController } from '@singletons'; import { AddonModWikiPageDBRecord } from '../../services/database/wiki'; import { AddonModWikiSubwikiPage, AddonModWikiWiki } from '../../services/wiki'; import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; +import { CoreSharedModule } from '@/core/shared.module'; /** * Modal to display the map of a Wiki. @@ -24,6 +25,10 @@ import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; @Component({ selector: 'page-addon-mod-wiki-map', templateUrl: 'map.html', + standalone: true, + imports: [ + CoreSharedModule, + ], }) export class AddonModWikiMapModalComponent implements OnInit { diff --git a/src/addons/mod/wiki/components/subwiki-picker/subwiki-picker.ts b/src/addons/mod/wiki/components/subwiki-picker/subwiki-picker.ts index ffbbb93e8..878d918d3 100644 --- a/src/addons/mod/wiki/components/subwiki-picker/subwiki-picker.ts +++ b/src/addons/mod/wiki/components/subwiki-picker/subwiki-picker.ts @@ -15,6 +15,7 @@ import { Component, Input } from '@angular/core'; import { PopoverController } from '@singletons'; import { AddonModWikiSubwiki, AddonModWikiSubwikiListGrouping } from '../../services/wiki'; +import { CoreSharedModule } from '@/core/shared.module'; /** * Component to display the a list of subwikis in a wiki. @@ -22,6 +23,10 @@ import { AddonModWikiSubwiki, AddonModWikiSubwikiListGrouping } from '../../serv @Component({ selector: 'addon-mod-wiki-subwiki-picker', templateUrl: 'addon-mod-wiki-subwiki-picker.html', + standalone: true, + imports: [ + CoreSharedModule, + ], }) export class AddonModWikiSubwikiPickerComponent { diff --git a/src/core/features/block/components/components.module.ts b/src/core/features/block/components/components.module.ts index 62552af04..7ebf07bd9 100644 --- a/src/core/features/block/components/components.module.ts +++ b/src/core/features/block/components/components.module.ts @@ -17,7 +17,6 @@ import { CoreBlockComponent } from './block/block'; import { CoreBlockOnlyTitleComponent } from './only-title-block/only-title-block'; import { CoreBlockPreRenderedComponent } from './pre-rendered-block/pre-rendered-block'; import { CoreSharedModule } from '@/core/shared.module'; -import { CoreBlockSideBlocksComponent } from './side-blocks/side-blocks'; import { CoreBlockSideBlocksButtonComponent } from './side-blocks-button/side-blocks-button'; import { CoreBlockSideBlocksTourComponent } from './side-blocks-tour/side-blocks-tour'; @@ -26,7 +25,6 @@ import { CoreBlockSideBlocksTourComponent } from './side-blocks-tour/side-blocks CoreBlockComponent, CoreBlockOnlyTitleComponent, CoreBlockPreRenderedComponent, - CoreBlockSideBlocksComponent, CoreBlockSideBlocksButtonComponent, CoreBlockSideBlocksTourComponent, ], @@ -37,7 +35,6 @@ import { CoreBlockSideBlocksTourComponent } from './side-blocks-tour/side-blocks CoreBlockComponent, CoreBlockOnlyTitleComponent, CoreBlockPreRenderedComponent, - CoreBlockSideBlocksComponent, CoreBlockSideBlocksButtonComponent, CoreBlockSideBlocksTourComponent, ], diff --git a/src/core/features/block/components/side-blocks-button/side-blocks-button.ts b/src/core/features/block/components/side-blocks-button/side-blocks-button.ts index 77ca2aacb..a9d9f51ec 100644 --- a/src/core/features/block/components/side-blocks-button/side-blocks-button.ts +++ b/src/core/features/block/components/side-blocks-button/side-blocks-button.ts @@ -19,7 +19,6 @@ import { CoreUserToursAlignment, CoreUserToursSide } from '@features/usertours/s import { CoreModals } from '@services/modals'; import { CoreDom } from '@singletons/dom'; import { CoreBlockSideBlocksTourComponent } from '../side-blocks-tour/side-blocks-tour'; -import { CoreBlockSideBlocksComponent } from '../side-blocks/side-blocks'; import { ContextLevel } from '@/core/constants'; /** @@ -67,7 +66,9 @@ export class CoreBlockSideBlocksButtonComponent implements OnInit, OnDestroy { /** * Open side blocks. */ - openBlocks(): void { + async openBlocks(): Promise { + const { CoreBlockSideBlocksComponent } = await import('@features/block/components/side-blocks/side-blocks'); + CoreModals.openSideModal({ component: CoreBlockSideBlocksComponent, componentProps: { diff --git a/src/core/features/block/components/side-blocks/side-blocks.ts b/src/core/features/block/components/side-blocks/side-blocks.ts index b677274a5..d4ac02876 100644 --- a/src/core/features/block/components/side-blocks/side-blocks.ts +++ b/src/core/features/block/components/side-blocks/side-blocks.ts @@ -24,6 +24,8 @@ import { CoreTextUtils } from '@services/utils/text'; import { CoreDom } from '@singletons/dom'; import { ContextLevel } from '@/core/constants'; import { CoreWait } from '@singletons/wait'; +import { CoreSharedModule } from '@/core/shared.module'; +import { CoreBlockComponentsModule } from '../components.module'; /** * Component that displays the list of side blocks. @@ -31,7 +33,12 @@ import { CoreWait } from '@singletons/wait'; @Component({ selector: 'core-block-side-blocks', templateUrl: 'side-blocks.html', - styleUrls: ['side-blocks.scss'], + styleUrl: 'side-blocks.scss', + standalone: true, + imports: [ + CoreSharedModule, + CoreBlockComponentsModule, + ], }) export class CoreBlockSideBlocksComponent implements OnInit { diff --git a/src/core/features/course/classes/main-resource-component.ts b/src/core/features/course/classes/main-resource-component.ts index a2e8c5d95..e3b994b08 100644 --- a/src/core/features/course/classes/main-resource-component.ts +++ b/src/core/features/course/classes/main-resource-component.ts @@ -24,7 +24,7 @@ import { CoreUtils } from '@services/utils/utils'; import { Translate } from '@singletons'; import { CoreEventObserver, CoreEvents } from '@singletons/events'; import { CoreLogger } from '@singletons/logger'; -import { CoreCourseModuleSummaryComponent, CoreCourseModuleSummaryResult } from '../components/module-summary/module-summary'; +import { CoreCourseModuleSummaryResult } from '../components/module-summary/module-summary'; import { CoreCourseContentsPage } from '../pages/contents/contents'; import { CoreCourse } from '../services/course'; import { CoreCourseHelper, CoreCourseModuleData } from '../services/course-helper'; @@ -425,6 +425,8 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, return; } + const { CoreCourseModuleSummaryComponent } = await import('@features/course/components/module-summary/module-summary'); + const data = await CoreModals.openSideModal({ component: CoreCourseModuleSummaryComponent, componentProps: { diff --git a/src/core/features/course/components/components.module.ts b/src/core/features/course/components/components.module.ts index 4a8a1d4f1..dcac3d294 100644 --- a/src/core/features/course/components/components.module.ts +++ b/src/core/features/course/components/components.module.ts @@ -24,7 +24,6 @@ import { CoreCourseUnsupportedModuleComponent } from './unsupported-module/unsup import { CoreCourseModuleCompletionLegacyComponent } from './module-completion-legacy/module-completion-legacy'; import { CoreCourseModuleInfoComponent } from './module-info/module-info'; import { CoreCourseModuleNavigationComponent } from './module-navigation/module-navigation'; -import { CoreCourseModuleSummaryComponent } from './module-summary/module-summary'; import { CoreCourseCourseIndexTourComponent } from './course-index-tour/course-index-tour'; import { CoreRemindersComponentsModule } from '@features/reminders/components/components.module'; import { CoreCourseModuleCompletionDetailsComponent } from './module-completion-details/module-completion-details'; @@ -40,7 +39,6 @@ import { CoreCourseModuleCompletionDetailsComponent } from './module-completion- CoreCourseTagAreaComponent, CoreCourseUnsupportedModuleComponent, CoreCourseModuleNavigationComponent, - CoreCourseModuleSummaryComponent, CoreCourseModuleCompletionDetailsComponent, ], imports: [ @@ -58,7 +56,6 @@ import { CoreCourseModuleCompletionDetailsComponent } from './module-completion- CoreCourseTagAreaComponent, CoreCourseUnsupportedModuleComponent, CoreCourseModuleNavigationComponent, - CoreCourseModuleSummaryComponent, CoreCourseModuleCompletionDetailsComponent, ], }) diff --git a/src/core/features/course/components/course-format/course-format.ts b/src/core/features/course/components/course-format/course-format.ts index e836d75d7..e89377e26 100644 --- a/src/core/features/course/components/course-format/course-format.ts +++ b/src/core/features/course/components/course-format/course-format.ts @@ -51,7 +51,6 @@ import { CoreCourseCourseIndexTourComponent } from '../course-index-tour/course- import { CoreDom } from '@singletons/dom'; import { CoreUserTourDirectiveOptions } from '@directives/user-tour'; import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; -import { CoreBlockSideBlocksComponent } from '@features/block/components/side-blocks/side-blocks'; import { ContextLevel } from '@/core/constants'; import { CoreModals } from '@services/modals'; @@ -317,6 +316,8 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { this.sectionChanged(section); } } else if (this.initialBlockInstanceId && this.displayBlocks && this.hasBlocks) { + const { CoreBlockSideBlocksComponent } = await import('@features/block/components/side-blocks/side-blocks'); + CoreModals.openSideModal({ component: CoreBlockSideBlocksComponent, componentProps: { diff --git a/src/core/features/course/components/module-summary/module-summary.ts b/src/core/features/course/components/module-summary/module-summary.ts index edaae3b37..45c6fe5d9 100644 --- a/src/core/features/course/components/module-summary/module-summary.ts +++ b/src/core/features/course/components/module-summary/module-summary.ts @@ -33,6 +33,7 @@ import { CoreUtils } from '@services/utils/utils'; import { ModalController, NgZone } from '@singletons'; import { CoreEventObserver, CoreEvents } from '@singletons/events'; import { Subscription } from 'rxjs'; +import { CoreSharedModule } from '@/core/shared.module'; /** * Component to display a module summary modal. @@ -40,7 +41,11 @@ import { Subscription } from 'rxjs'; @Component({ selector: 'core-course-module-summary', templateUrl: 'module-summary.html', - styleUrls: ['module-summary.scss'], + styleUrl: 'module-summary.scss', + standalone: true, + imports: [ + CoreSharedModule, + ], }) export class CoreCourseModuleSummaryComponent implements OnInit, OnDestroy { diff --git a/src/core/features/course/pages/module-preview/module-preview.ts b/src/core/features/course/pages/module-preview/module-preview.ts index 86219e53e..c47db58f3 100644 --- a/src/core/features/course/pages/module-preview/module-preview.ts +++ b/src/core/features/course/pages/module-preview/module-preview.ts @@ -13,10 +13,7 @@ // limitations under the License. import { Component, OnInit } from '@angular/core'; -import { - CoreCourseModuleSummaryResult, - CoreCourseModuleSummaryComponent, -} from '@features/course/components/module-summary/module-summary'; +import { CoreCourseModuleSummaryResult } from '@features/course/components/module-summary/module-summary'; import { CoreCourse } from '@features/course/services/course'; import { CoreCourseHelper, CoreCourseModuleData, CoreCourseSection } from '@features/course/services/course-helper'; import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate'; @@ -107,6 +104,8 @@ export class CoreCourseModulePreviewPage implements OnInit { return; } + const { CoreCourseModuleSummaryComponent } = await import('@features/course/components/module-summary/module-summary'); + const data = await CoreModals.openSideModal({ component: CoreCourseModuleSummaryComponent, componentProps: { diff --git a/src/core/features/course/services/course-helper.ts b/src/core/features/course/services/course-helper.ts index f0671b3f4..109ab4dbd 100644 --- a/src/core/features/course/services/course-helper.ts +++ b/src/core/features/course/services/course-helper.ts @@ -69,7 +69,6 @@ import { CoreNavigationOptions, CoreNavigator } from '@services/navigator'; import { CoreSiteHomeHomeHandlerService } from '@features/sitehome/services/handlers/sitehome-home'; import { CoreStatusWithWarningsWSResponse } from '@services/ws'; import { CoreCourseWithImageAndColor } from '@features/courses/services/courses-helper'; -import { CoreCourseSummaryPage } from '../pages/course-summary/course-summary.page'; import { CoreRemindersPushNotificationData } from '@features/reminders/services/reminders'; import { CoreLocalNotifications } from '@services/local-notifications'; import { CoreEnrol } from '@features/enrol/services/enrol'; @@ -1997,6 +1996,7 @@ export class CoreCourseHelperProvider { /** * Retrieves course summary page module. + * This is meant to be here so it can be overriden. * * @returns Course summary page module. */ @@ -2009,7 +2009,9 @@ export class CoreCourseHelperProvider { * * @param course Course selected */ - openCourseSummary(course: CoreCourseWithImageAndColor & CoreCourseAnyCourseData): void { + async openCourseSummary(course: CoreCourseWithImageAndColor & CoreCourseAnyCourseData): Promise { + const { CoreCourseSummaryPage } = await import('../pages/course-summary/course-summary.page'); + CoreModals.openSideModal({ component: CoreCourseSummaryPage, componentProps: { diff --git a/src/core/features/editor/components/components.module.ts b/src/core/features/editor/components/components.module.ts index 8bc9e4105..b5e3bfb65 100644 --- a/src/core/features/editor/components/components.module.ts +++ b/src/core/features/editor/components/components.module.ts @@ -24,8 +24,6 @@ import { CoreSharedModule } from '@/core/shared.module'; imports: [ CoreSharedModule, ], - providers: [ - ], exports: [ CoreEditorRichTextEditorComponent, ], diff --git a/src/core/features/h5p/components/components.module.ts b/src/core/features/h5p/components/components.module.ts index 509b92edb..8e806fce4 100644 --- a/src/core/features/h5p/components/components.module.ts +++ b/src/core/features/h5p/components/components.module.ts @@ -26,8 +26,6 @@ import { CoreH5PIframeComponent } from './h5p-iframe/h5p-iframe'; imports: [ CoreSharedModule, ], - providers: [ - ], exports: [ CoreH5PPlayerComponent, CoreH5PIframeComponent, diff --git a/src/core/features/login/components/components.module.ts b/src/core/features/login/components/components.module.ts index 6407dd57b..1d2fc3fa7 100644 --- a/src/core/features/login/components/components.module.ts +++ b/src/core/features/login/components/components.module.ts @@ -14,14 +14,12 @@ import { NgModule } from '@angular/core'; import { CoreSharedModule } from '@/core/shared.module'; -import { CoreLoginSitesModalComponent } from './sites-modal/sites-modal'; import { CoreLoginMethodsComponent } from './login-methods/login-methods'; import { CoreLoginExceededAttemptsComponent } from '@features/login/components/exceeded-attempts/exceeded-attempts'; @NgModule({ declarations: [ CoreLoginExceededAttemptsComponent, - CoreLoginSitesModalComponent, CoreLoginMethodsComponent, ], imports: [ @@ -29,7 +27,6 @@ import { CoreLoginExceededAttemptsComponent } from '@features/login/components/e ], exports: [ CoreLoginExceededAttemptsComponent, - CoreLoginSitesModalComponent, CoreLoginMethodsComponent, ], }) diff --git a/src/core/features/login/components/sites-modal/sites-modal.ts b/src/core/features/login/components/sites-modal/sites-modal.ts index e938c4f7e..897f27487 100644 --- a/src/core/features/login/components/sites-modal/sites-modal.ts +++ b/src/core/features/login/components/sites-modal/sites-modal.ts @@ -21,6 +21,7 @@ import { CoreNavigator } from '@services/navigator'; import { CoreFilter } from '@features/filter/services/filter'; import { CoreAnimations } from '@components/animations'; import { ModalController } from '@singletons'; +import { CoreSharedModule } from '@/core/shared.module'; /** * Modal that displays a list of sites to be able to enter or delete a site. @@ -29,6 +30,10 @@ import { ModalController } from '@singletons'; selector: 'core-login-sites-modal', templateUrl: 'sites-modal.html', animations: [CoreAnimations.SLIDE_IN_OUT, CoreAnimations.SHOW_HIDE], + standalone: true, + imports: [ + CoreSharedModule, + ], }) export class CoreLoginSitesModalComponent implements OnInit { diff --git a/src/core/features/mainmenu/components/components.module.ts b/src/core/features/mainmenu/components/components.module.ts index c81a44b1d..763cbd86a 100644 --- a/src/core/features/mainmenu/components/components.module.ts +++ b/src/core/features/mainmenu/components/components.module.ts @@ -15,14 +15,12 @@ import { NgModule } from '@angular/core'; import { CoreSharedModule } from '@/core/shared.module'; import { CoreMainMenuUserButtonComponent } from './user-menu-button/user-menu-button'; -import { CoreMainMenuUserMenuComponent } from './user-menu/user-menu'; import { CoreLoginComponentsModule } from '@features/login/components/components.module'; import { CoreMainMenuUserMenuTourComponent } from './user-menu-tour/user-menu-tour'; @NgModule({ declarations: [ CoreMainMenuUserButtonComponent, - CoreMainMenuUserMenuComponent, CoreMainMenuUserMenuTourComponent, ], imports: [ @@ -31,7 +29,6 @@ import { CoreMainMenuUserMenuTourComponent } from './user-menu-tour/user-menu-to ], exports: [ CoreMainMenuUserButtonComponent, - CoreMainMenuUserMenuComponent, CoreMainMenuUserMenuTourComponent, ], }) diff --git a/src/core/features/mainmenu/components/user-menu-button/user-menu-button.ts b/src/core/features/mainmenu/components/user-menu-button/user-menu-button.ts index b520be854..369dfc41e 100644 --- a/src/core/features/mainmenu/components/user-menu-button/user-menu-button.ts +++ b/src/core/features/mainmenu/components/user-menu-button/user-menu-button.ts @@ -21,7 +21,6 @@ import { CoreScreen } from '@services/screen'; import { CoreSites } from '@services/sites'; import { CoreModals } from '@services/modals'; import { CoreMainMenuUserMenuTourComponent } from '../user-menu-tour/user-menu-tour'; -import { CoreMainMenuUserMenuComponent } from '../user-menu/user-menu'; import { CoreMainMenuPage } from '@features/mainmenu/pages/menu/menu'; /** @@ -62,10 +61,12 @@ export class CoreMainMenuUserButtonComponent implements OnInit { * * @param event Click event. */ - openUserMenu(event: Event): void { + async openUserMenu(event: Event): Promise { event.preventDefault(); event.stopPropagation(); + const { CoreMainMenuUserMenuComponent } = await import('../user-menu/user-menu'); + CoreModals.openSideModal({ component: CoreMainMenuUserMenuComponent, }); diff --git a/src/core/features/mainmenu/components/user-menu/user-menu.ts b/src/core/features/mainmenu/components/user-menu/user-menu.ts index 1387bea70..6cedcae32 100644 --- a/src/core/features/mainmenu/components/user-menu/user-menu.ts +++ b/src/core/features/mainmenu/components/user-menu/user-menu.ts @@ -13,11 +13,11 @@ // limitations under the License. import { CoreConstants } from '@/core/constants'; +import { CoreSharedModule } from '@/core/shared.module'; import { Component, OnDestroy, OnInit } from '@angular/core'; import { CoreSite } from '@classes/sites/site'; import { CoreSiteInfo } from '@classes/sites/unauthenticated-site'; import { CoreFilter } from '@features/filter/services/filter'; -import { CoreLoginSitesModalComponent } from '@features/login/components/sites-modal/sites-modal'; import { CoreLoginHelper } from '@features/login/services/login-helper'; import { CoreUserAuthenticatedSupportConfig } from '@features/user/classes/support/authenticated-support-config'; import { CoreUserSupport } from '@features/user/services/support'; @@ -42,7 +42,11 @@ import { Subscription } from 'rxjs'; @Component({ selector: 'core-main-menu-user-menu', templateUrl: 'user-menu.html', - styleUrls: ['user-menu.scss'], + styleUrl: 'user-menu.scss', + standalone: true, + imports: [ + CoreSharedModule, + ], }) export class CoreMainMenuUserMenuComponent implements OnInit, OnDestroy { @@ -249,6 +253,8 @@ export class CoreMainMenuUserMenuComponent implements OnInit, OnDestroy { event.preventDefault(); event.stopPropagation(); + const { CoreLoginSitesModalComponent } = await import('@features/login/components/sites-modal/sites-modal'); + const closeAll = await CoreModals.openSideModal({ component: CoreLoginSitesModalComponent, cssClass: 'core-modal-lateral core-modal-lateral-sm', diff --git a/src/core/features/reportbuilder/components/components.module.ts b/src/core/features/reportbuilder/components/components.module.ts index f8f269fc3..958aaaf4f 100644 --- a/src/core/features/reportbuilder/components/components.module.ts +++ b/src/core/features/reportbuilder/components/components.module.ts @@ -16,7 +16,6 @@ import { NgModule } from '@angular/core'; import { CoreSharedModule } from '@/core/shared.module'; import { CoreReportBuilderReportColumnComponent } from './report-column/report-column'; import { CoreReportBuilderReportDetailComponent } from './report-detail/report-detail'; -import { CoreReportBuilderReportSummaryComponent } from './report-summary/report-summary'; @NgModule({ imports: [ @@ -25,12 +24,10 @@ import { CoreReportBuilderReportSummaryComponent } from './report-summary/report declarations: [ CoreReportBuilderReportDetailComponent, CoreReportBuilderReportColumnComponent, - CoreReportBuilderReportSummaryComponent, ], exports: [ CoreReportBuilderReportDetailComponent, CoreReportBuilderReportColumnComponent, - CoreReportBuilderReportSummaryComponent, ], }) export class CoreReportBuilderComponentsModule {} diff --git a/src/core/features/reportbuilder/components/report-summary/report-summary.ts b/src/core/features/reportbuilder/components/report-summary/report-summary.ts index a08e8cd95..13b3650c5 100644 --- a/src/core/features/reportbuilder/components/report-summary/report-summary.ts +++ b/src/core/features/reportbuilder/components/report-summary/report-summary.ts @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +import { CoreSharedModule } from '@/core/shared.module'; import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core'; import { CoreReportBuilderReportDetail } from '@features/reportbuilder/services/reportbuilder'; import { CoreFormatDatePipe } from '@pipes/format-date'; @@ -21,8 +22,12 @@ import { ModalController } from '@singletons'; @Component({ selector: 'core-report-builder-report-summary', templateUrl: './report-summary.html', - styleUrls: ['./report-summary.scss'], + styleUrl: './report-summary.scss', changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [ + CoreSharedModule, + ], }) export class CoreReportBuilderReportSummaryComponent implements OnInit { @@ -30,6 +35,9 @@ export class CoreReportBuilderReportSummaryComponent implements OnInit { reportUrl!: string; reportDetailToDisplay!: { title: string; text: string }[]; + /** + * @inheritdoc + */ ngOnInit(): void { const formatDate = new CoreFormatDatePipe(); const site = CoreSites.getRequiredCurrentSite(); @@ -54,6 +62,9 @@ export class CoreReportBuilderReportSummaryComponent implements OnInit { ]; } + /** + * Close the modal. + */ closeModal(): void { ModalController.dismiss(); } diff --git a/src/core/features/reportbuilder/pages/report/report.ts b/src/core/features/reportbuilder/pages/report/report.ts index c44252df5..1321e4629 100644 --- a/src/core/features/reportbuilder/pages/report/report.ts +++ b/src/core/features/reportbuilder/pages/report/report.ts @@ -13,7 +13,6 @@ // limitations under the License. import { Component, OnInit } from '@angular/core'; -import { CoreReportBuilderReportSummaryComponent } from '@features/reportbuilder/components/report-summary/report-summary'; import { CoreReportBuilderReportDetail } from '@features/reportbuilder/services/reportbuilder'; import { CoreModals } from '@services/modals'; import { CoreNavigator } from '@services/navigator'; @@ -26,6 +25,7 @@ export class CoreReportBuilderReportPage implements OnInit { reportId!: string; reportDetail?: CoreReportBuilderReportDetail; + /** * @inheritdoc */ @@ -42,7 +42,13 @@ export class CoreReportBuilderReportPage implements OnInit { this.reportDetail = reportDetail; } - openInfo(): void { + /** + * Open the report info modal. + */ + async openInfo(): Promise { + const { CoreReportBuilderReportSummaryComponent } = + await import('@features/reportbuilder/components/report-summary/report-summary'); + CoreModals.openSideModal({ component: CoreReportBuilderReportSummaryComponent, componentProps: { reportDetail: this.reportDetail }, diff --git a/src/core/features/search/components/global-search-filters/global-search-filters.component.ts b/src/core/features/search/components/global-search-filters/global-search-filters.component.ts index 75eae485b..dbd15dc0a 100644 --- a/src/core/features/search/components/global-search-filters/global-search-filters.component.ts +++ b/src/core/features/search/components/global-search-filters/global-search-filters.component.ts @@ -23,6 +23,7 @@ import { import { CoreEvents } from '@singletons/events'; import { ModalController } from '@singletons'; import { CoreUtils } from '@services/utils/utils'; +import { CoreSharedModule } from '@/core/shared.module'; type Filter = T & { checked: boolean }; @@ -30,6 +31,10 @@ type Filter = T & { checked: boolean }; selector: 'core-search-global-search-filters', templateUrl: 'global-search-filters.html', styleUrls: ['./global-search-filters.scss'], + standalone: true, + imports: [ + CoreSharedModule, + ], }) export class CoreSearchGlobalSearchFiltersComponent implements OnInit { diff --git a/src/core/features/search/components/global-search-filters/global-search-filters.module.ts b/src/core/features/search/components/global-search-filters/global-search-filters.module.ts deleted file mode 100644 index 0cd203468..000000000 --- a/src/core/features/search/components/global-search-filters/global-search-filters.module.ts +++ /dev/null @@ -1,30 +0,0 @@ -// (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 { CoreSharedModule } from '@/core/shared.module'; -import { NgModule } from '@angular/core'; - -import { CoreSearchGlobalSearchFiltersComponent } from './global-search-filters.component'; - -export { CoreSearchGlobalSearchFiltersComponent }; - -@NgModule({ - imports: [ - CoreSharedModule, - ], - declarations: [ - CoreSearchGlobalSearchFiltersComponent, - ], -}) -export class CoreSearchGlobalSearchFiltersComponentModule {} diff --git a/src/core/features/search/pages/global-search/global-search.ts b/src/core/features/search/pages/global-search/global-search.ts index 059899d8c..00451e924 100644 --- a/src/core/features/search/pages/global-search/global-search.ts +++ b/src/core/features/search/pages/global-search/global-search.ts @@ -138,7 +138,7 @@ export class CoreSearchGlobalSearchPage implements OnInit, OnDestroy, AfterViewI */ async openFilters(): Promise { const { CoreSearchGlobalSearchFiltersComponent } = - await import('@features/search/components/global-search-filters/global-search-filters.module'); + await import('@features/search/components/global-search-filters/global-search-filters.component'); await CoreModals.openSideModal({ component: CoreSearchGlobalSearchFiltersComponent, diff --git a/src/core/features/sitehome/pages/index/index.ts b/src/core/features/sitehome/pages/index/index.ts index 30ff59412..8e116aabf 100644 --- a/src/core/features/sitehome/pages/index/index.ts +++ b/src/core/features/sitehome/pages/index/index.ts @@ -30,7 +30,6 @@ import { CoreBlockHelper } from '@features/block/services/block-helper'; import { CoreUtils } from '@services/utils/utils'; import { CoreTime } from '@singletons/time'; import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; -import { CoreBlockSideBlocksComponent } from '@features/block/components/side-blocks/side-blocks'; import { ContextLevel } from '@/core/constants'; import { CoreModals } from '@services/modals'; @@ -228,12 +227,14 @@ export class CoreSiteHomeIndexPage implements OnInit, OnDestroy { /** * Check whether there is a focused instance in the page parameters and open it. */ - private openFocusedInstance() { + private async openFocusedInstance() { const blockInstanceId = CoreNavigator.getRouteNumberParam('blockInstanceId'); if (!blockInstanceId) { return; } + const { CoreBlockSideBlocksComponent } = await import('@features/block/components/side-blocks/side-blocks'); + CoreModals.openSideModal({ component: CoreBlockSideBlocksComponent, componentProps: { 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 970092bb7..6d6291235 100644 --- a/src/core/features/siteplugins/components/module-index/module-index.ts +++ b/src/core/features/siteplugins/components/module-index/module-index.ts @@ -15,10 +15,7 @@ import { Component, OnInit, OnDestroy, Input, ViewChild, HostBinding } from '@angular/core'; import { CoreSiteWSPreSets } from '@classes/sites/authenticated-site'; -import { - CoreCourseModuleSummaryResult, - CoreCourseModuleSummaryComponent, -} from '@features/course/components/module-summary/module-summary'; +import { CoreCourseModuleSummaryResult } from '@features/course/components/module-summary/module-summary'; import { CoreCourse } from '@features/course/services/course'; import { CoreCourseModuleData } from '@features/course/services/course-helper'; import { @@ -157,6 +154,8 @@ export class CoreSitePluginsModuleIndexComponent implements OnInit, OnDestroy, C return; } + const { CoreCourseModuleSummaryComponent } = await import('@features/course/components/module-summary/module-summary'); + const data = await CoreModals.openSideModal({ component: CoreCourseModuleSummaryComponent, componentProps: { From 4b950ac891c47bab849a324f82a3114348118c73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Fri, 19 Jul 2024 16:21:12 +0200 Subject: [PATCH 6/8] MOBILE-4616 chore: Remove some usages of old Service.instance --- .../features/course/services/course-helper.ts | 3 +- .../settings/pages/deviceinfo/deviceinfo.ts | 64 ++++++++----------- .../settings/services/settings-helper.ts | 7 +- src/core/singletons/window.ts | 2 +- 4 files changed, 32 insertions(+), 44 deletions(-) diff --git a/src/core/features/course/services/course-helper.ts b/src/core/features/course/services/course-helper.ts index 109ab4dbd..ae96fadb4 100644 --- a/src/core/features/course/services/course-helper.ts +++ b/src/core/features/course/services/course-helper.ts @@ -645,9 +645,8 @@ export class CoreCourseHelperProvider { // Now determine the status of the whole list. let status = statuses[0]; - const filepool = CoreFilepool.instance; for (let i = 1; i < statuses.length; i++) { - status = filepool.determinePackagesStatus(status, statuses[i]); + status = CoreFilepool.determinePackagesStatus(status, statuses[i]); } return status; diff --git a/src/core/features/settings/pages/deviceinfo/deviceinfo.ts b/src/core/features/settings/pages/deviceinfo/deviceinfo.ts index 38b5a45ea..fac179b89 100644 --- a/src/core/features/settings/pages/deviceinfo/deviceinfo.ts +++ b/src/core/features/settings/pages/deviceinfo/deviceinfo.ts @@ -86,9 +86,6 @@ export class CoreSettingsDeviceInfoPage implements OnDestroy { protected onlineObserver?: Subscription; constructor() { - const sitesProvider = CoreSites.instance; - const device = Device.instance; - const translate = Translate.instance; const navigator = window.navigator; this.deviceInfo = { @@ -128,7 +125,7 @@ export class CoreSettingsDeviceInfoPage implements OnDestroy { this.deviceOsTranslated = matches[1]; } else { this.deviceInfo.deviceOs = 'unknown'; - this.deviceOsTranslated = translate.instant('core.unknown'); + this.deviceOsTranslated = Translate.instant('core.unknown'); } } } else { @@ -139,39 +136,35 @@ export class CoreSettingsDeviceInfoPage implements OnDestroy { this.deviceOsTranslated = matches[1]; } else { this.deviceInfo.deviceOs = 'unknown'; - this.deviceOsTranslated = translate.instant('core.unknown'); + this.deviceOsTranslated = Translate.instant('core.unknown'); } } - if (navigator) { - if (navigator.userAgent) { - this.deviceInfo.userAgent = navigator.userAgent; - } - - if (navigator.language) { - this.deviceInfo.browserLanguage = navigator.language; - } + if (navigator.userAgent) { + this.deviceInfo.userAgent = navigator.userAgent; } - if (device) { - if (device.cordova) { - this.deviceInfo.cordovaVersion = device.cordova; - } - if (device.platform) { - this.deviceInfo.platform = device.platform; - } - if (device.version) { - this.deviceInfo.osVersion = device.version; - } - if (device.model) { - this.deviceInfo.model = device.model; - } - if (device.uuid) { - this.deviceInfo.uuid = device.uuid; - } + if (navigator.language) { + this.deviceInfo.browserLanguage = navigator.language; } - const currentSite = sitesProvider.getCurrentSite(); + if (Device.cordova) { + this.deviceInfo.cordovaVersion = Device.cordova; + } + if (Device.platform) { + this.deviceInfo.platform = Device.platform; + } + if (Device.version) { + this.deviceInfo.osVersion = Device.version; + } + if (Device.model) { + this.deviceInfo.model = Device.model; + } + if (Device.uuid) { + this.deviceInfo.uuid = Device.uuid; + } + + const currentSite = CoreSites.getCurrentSite(); this.deviceInfo.siteId = currentSite?.getId(); this.deviceInfo.siteVersion = currentSite?.getInfo()?.release; @@ -190,14 +183,11 @@ export class CoreSettingsDeviceInfoPage implements OnDestroy { * Async part of the constructor. */ protected async asyncInit(): Promise { - const sitesProvider = CoreSites.instance; - const fileProvider = CoreFile.instance; - const lang = await CoreLang.getCurrentLanguage(); this.deviceInfo.currentLanguage = lang; this.currentLangName = CoreConstants.CONFIG.languages[lang]; - const currentSite = sitesProvider.getCurrentSite(); + const currentSite = CoreSites.getCurrentSite(); const isSingleFixedSite = await CoreLoginHelper.isSingleFixedSite(); const sites = await CoreLoginHelper.getAvailableSites(); const firstUrl = isSingleFixedSite && sites[0].url; @@ -207,10 +197,10 @@ export class CoreSettingsDeviceInfoPage implements OnDestroy { this.displaySiteUrl = !!this.deviceInfo.siteUrl && (currentSite ?? CoreSitesFactory.makeUnauthenticatedSite(this.deviceInfo.siteUrl)).shouldDisplayInformativeLinks(); - if (fileProvider.isAvailable()) { - const basepath = await fileProvider.getBasePath(); + if (CoreFile.isAvailable()) { + const basepath = await CoreFile.getBasePath(); this.deviceInfo.fileSystemRoot = basepath; - this.fsClickable = fileProvider.usesHTMLAPI(); + this.fsClickable = CoreFile.usesHTMLAPI(); } const showDevOptionsOnConfig = await CoreConfig.get('showDevOptions', 0); diff --git a/src/core/features/settings/services/settings-helper.ts b/src/core/features/settings/services/settings-helper.ts index 47444868b..25bc2400a 100644 --- a/src/core/features/settings/services/settings-helper.ts +++ b/src/core/features/settings/services/settings-helper.ts @@ -136,11 +136,10 @@ export class CoreSettingsHelperProvider { // Clear cache tables. const cleanSchemas = CoreSites.getSiteTableSchemasToClear(site); const promises: Promise[] = cleanSchemas.map((name) => site.getDb().deleteRecords(name)); - const filepoolService = CoreFilepool.instance; promises.push(site.deleteFolder().then(() => { - filepoolService.clearAllPackagesStatus(siteId); - filepoolService.clearFilepool(siteId); + CoreFilepool.clearAllPackagesStatus(siteId); + CoreFilepool.clearFilepool(siteId); CoreCourse.clearAllCoursesStatus(siteId); siteInfo.spaceUsage = 0; @@ -149,7 +148,7 @@ export class CoreSettingsHelperProvider { }).catch(async (error) => { if (error && error.code === FileError.NOT_FOUND_ERR) { // Not found, set size 0. - filepoolService.clearAllPackagesStatus(siteId); + CoreFilepool.clearAllPackagesStatus(siteId); siteInfo.spaceUsage = 0; } else { // Error, recalculate the site usage. diff --git a/src/core/singletons/window.ts b/src/core/singletons/window.ts index c520b9741..c14b8de93 100644 --- a/src/core/singletons/window.ts +++ b/src/core/singletons/window.ts @@ -82,7 +82,7 @@ export class CoreWindow { if (!CoreFileHelper.isOpenableInApp({ filename })) { try { await CoreFileHelper.showConfirmOpenUnsupportedFile(false, { filename }); - } catch (error) { + } catch { return; // Cancelled, stop. } } From 21ac11cb35ee0f7ace52505ee5e0f550e3b5c34d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Fri, 26 Jul 2024 17:20:46 +0200 Subject: [PATCH 7/8] MOBILE-4616 chore: Update circular dependencies number --- .github/workflows/testing.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index f44b2ae89..538b5de28 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -69,7 +69,7 @@ jobs: cat circular-dependencies lines=$(cat circular-dependencies | wc -l) echo "Total circular dependencies: $lines" - test $lines -eq 135 + test $lines -eq 129 - name: JavaScript code compatibility run: | npx check-es-compat www/*.js --polyfills="\{Array,String,TypedArray\}.prototype.at,Object.hasOwn" From 0e6baa9802396b68cd039dbedcc03c7c0287decf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Mon, 29 Jul 2024 12:37:04 +0200 Subject: [PATCH 8/8] MOBILE-4616 chore: Change snapshot width on quiz behats It seems Selenium or Chrome version is managing the width with scroll bars including the bars --- ...ubmit-a-quiz--review-a-quiz-attempt_27.png | Bin 21278 -> 21120 bytes ...ubmit-a-quiz--review-a-quiz-attempt_42.png | Bin 38201 -> 38061 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/src/addons/mod/quiz/tests/behat/snapshots/attempt-a-quiz-in-app-submit-a-quiz--review-a-quiz-attempt_27.png b/src/addons/mod/quiz/tests/behat/snapshots/attempt-a-quiz-in-app-submit-a-quiz--review-a-quiz-attempt_27.png index 63ba23ad2edbcea16bea08656774e2709de5c110..46c036744857321b5e9469f11483532cd103ade8 100644 GIT binary patch literal 21120 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 WlXjCw4SvTZazpu+Qi+0T@c#fgYzy@O diff --git a/src/addons/mod/quiz/tests/behat/snapshots/attempt-a-quiz-in-app-submit-a-quiz--review-a-quiz-attempt_42.png b/src/addons/mod/quiz/tests/behat/snapshots/attempt-a-quiz-in-app-submit-a-quiz--review-a-quiz-attempt_42.png index f8affc29e57b028eafaf4249aa4d07d8d4fec8c1..e62984bde1dfdf7574363e6a4b5a3c0367d1341c 100644 GIT binary patch literal 38061 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¾gKm_h#0@Ci!F@d&1N)+FpV(`;u1yJVfPd~ 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 bVv#M({%EFt%={?|{y8nHbn?B7&i(%d4Pz$O literal 38201 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 Xdeq$6Rt