diff --git a/src/core/components/bs-tooltip/bs-tooltip.ts b/src/core/components/bs-tooltip/bs-tooltip.ts new file mode 100644 index 000000000..f3738b099 --- /dev/null +++ b/src/core/components/bs-tooltip/bs-tooltip.ts @@ -0,0 +1,29 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Component, Input } from '@angular/core'; + +/** + * Component to display a Bootstrap Tooltip in a popover. + */ +@Component({ + selector: 'core-bs-tooltip', + templateUrl: 'core-bs-tooltip.html', +}) +export class CoreBSTooltipComponent { + + @Input() content = ''; + @Input() html?: boolean; + +} diff --git a/src/core/components/bs-tooltip/core-bs-tooltip.html b/src/core/components/bs-tooltip/core-bs-tooltip.html new file mode 100644 index 000000000..cc5e8e36c --- /dev/null +++ b/src/core/components/bs-tooltip/core-bs-tooltip.html @@ -0,0 +1,6 @@ + + +

+

{{content}}

+
+
diff --git a/src/core/components/components.module.ts b/src/core/components/components.module.ts index 02fc1268d..4e37a8a76 100644 --- a/src/core/components/components.module.ts +++ b/src/core/components/components.module.ts @@ -51,6 +51,7 @@ import { CorePipesModule } from '@pipes/pipes.module'; import { CoreAttachmentsComponent } from './attachments/attachments'; import { CoreFilesComponent } from './files/files'; import { CoreLocalFileComponent } from './local-file/local-file'; +import { CoreBSTooltipComponent } from './bs-tooltip/bs-tooltip'; @NgModule({ declarations: [ @@ -84,6 +85,7 @@ import { CoreLocalFileComponent } from './local-file/local-file'; CoreAttachmentsComponent, CoreFilesComponent, CoreLocalFileComponent, + CoreBSTooltipComponent, ], imports: [ CommonModule, @@ -124,6 +126,7 @@ import { CoreLocalFileComponent } from './local-file/local-file'; CoreAttachmentsComponent, CoreFilesComponent, CoreLocalFileComponent, + CoreBSTooltipComponent, ], }) export class CoreComponentsModule {} diff --git a/src/core/components/iframe/iframe.scss b/src/core/components/iframe/iframe.scss index d99e54aae..67a48e598 100644 --- a/src/core/components/iframe/iframe.scss +++ b/src/core/components/iframe/iframe.scss @@ -1,5 +1,6 @@ -ion-app.app-root core-iframe { +@import "~theme/globals"; +:host { > div { max-width: 100%; max-height: 100%; @@ -8,7 +9,7 @@ ion-app.app-root core-iframe { border: 0; display: block; max-width: 100%; - background-color: $gray-light; + background-color: var(--gray-light); } .core-loading-container { diff --git a/src/core/components/iframe/iframe.ts b/src/core/components/iframe/iframe.ts index 3269f7629..70ffdffcc 100644 --- a/src/core/components/iframe/iframe.ts +++ b/src/core/components/iframe/iframe.ts @@ -27,6 +27,7 @@ import { CoreLogger } from '@singletons/logger'; @Component({ selector: 'core-iframe', templateUrl: 'core-iframe.html', + styleUrls: ['iframe.scss'], }) export class CoreIframeComponent implements OnChanges { diff --git a/src/core/features/course/components/module/core-course-module.html b/src/core/features/course/components/module/core-course-module.html index cab6e929c..cf25c9990 100644 --- a/src/core/features/course/components/module/core-course-module.html +++ b/src/core/features/course/components/module/core-course-module.html @@ -22,10 +22,10 @@
- + { e.preventDefault(); @@ -247,22 +246,62 @@ export class CoreCoursesCourseProgressComponent implements OnInit, OnDestroy { * Hide/Unhide the course from the course list. * * @param hide True to hide and false to show. - * @todo CoreUser */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - protected setCourseHidden(hide: boolean): void { - return; + protected async setCourseHidden(hide: boolean): Promise { + this.showSpinner = true; + + // We should use null to unset the preference. + try { + await CoreUser.updateUserPreference( + 'block_myoverview_hidden_course_' + this.course.id, + hide ? '1' : undefined, + ); + + this.course.hidden = hide; + CoreEvents.trigger(CoreCoursesProvider.EVENT_MY_COURSES_UPDATED, { + courseId: this.course.id, + course: this.course, + action: CoreCoursesProvider.ACTION_STATE_CHANGED, + state: CoreCoursesProvider.STATE_HIDDEN, + value: hide, + }, CoreSites.getCurrentSiteId()); + + } catch (error) { + if (!this.isDestroyed) { + CoreDomUtils.showErrorModalDefault(error, 'Error changing course visibility.'); + } + } finally { + this.showSpinner = false; + } } /** * Favourite/Unfavourite the course from the course list. * * @param favourite True to favourite and false to unfavourite. - * @todo CoreUser */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - protected setCourseFavourite(favourite: boolean): void { - return; + protected async setCourseFavourite(favourite: boolean): Promise { + this.showSpinner = true; + + try { + await CoreCourses.setFavouriteCourse(this.course.id, favourite); + + this.course.isfavourite = favourite; + CoreEvents.trigger(CoreCoursesProvider.EVENT_MY_COURSES_UPDATED, { + courseId: this.course.id, + course: this.course, + action: CoreCoursesProvider.ACTION_STATE_CHANGED, + state: CoreCoursesProvider.STATE_FAVOURITE, + value: favourite, + }, CoreSites.getCurrentSiteId()); + + } catch (error) { + if (!this.isDestroyed) { + CoreDomUtils.showErrorModalDefault(error, 'Error changing course favourite attribute.'); + } + } finally { + this.showSpinner = false; + } } /** diff --git a/src/core/features/courses/pages/course-preview/course-preview.ts b/src/core/features/courses/pages/course-preview/course-preview.ts index 1bad9c1ee..0b95eb3bc 100644 --- a/src/core/features/courses/pages/course-preview/course-preview.ts +++ b/src/core/features/courses/pages/course-preview/course-preview.ts @@ -450,11 +450,11 @@ export class CoreCoursesCoursePreviewPage implements OnInit, OnDestroy { * Prefetch the course. */ prefetchCourse(): void { - /* @todo CoreCourseHelper.confirmAndPrefetchCourse(this.prefetchCourseData, this.course).catch((error) => { + CoreCourseHelper.confirmAndPrefetchCourse(this.prefetchCourseData, this.course!).catch((error) => { if (!this.pageDestroyed) { CoreDomUtils.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true); } - });*/ + }); } /** diff --git a/src/core/features/login/pages/sites/sites.ts b/src/core/features/login/pages/sites/sites.ts index 3bf3222c7..7c5b778c5 100644 --- a/src/core/features/login/pages/sites/sites.ts +++ b/src/core/features/login/pages/sites/sites.ts @@ -20,6 +20,8 @@ import { CoreSiteBasicInfo, CoreSites } from '@services/sites'; import { CoreLogger } from '@singletons/logger'; import { CoreLoginHelper } from '@features/login/services/login-helper'; import { CoreNavigator } from '@services/navigator'; +import { CorePushNotifications } from '@features/pushnotifications/services/pushnotifications'; +import { CoreFilter } from '@features/filter/services/filter'; /** * Page that displays a "splash screen" while the app is being initialized. @@ -48,13 +50,12 @@ export class CoreLoginSitesPage implements OnInit { const sites = await CoreUtils.ignoreErrors(CoreSites.getSortedSites(), [] as CoreSiteBasicInfo[]); // Remove protocol from the url to show more url text. - this.sites = sites.map((site) => { + this.sites = await Promise.all(sites.map(async (site) => { site.siteUrl = site.siteUrl.replace(/^https?:\/\//, ''); - site.badge = 0; - // @todo: getSiteCounter. + site.badge = await CoreUtils.ignoreErrors(CorePushNotifications.getSiteCounter(site.id)) || 0; return site; - }); + })); this.showDelete = false; } @@ -76,9 +77,9 @@ export class CoreLoginSitesPage implements OnInit { async deleteSite(e: Event, site: CoreSiteBasicInfo): Promise { e.stopPropagation(); - const siteName = site.siteName || ''; + let siteName = site.siteName || ''; - // @todo: Format text: siteName. + siteName = await CoreFilter.formatText(siteName, { clean: true, singleLine: true, filter: false }, [], site.id); try { await CoreDomUtils.showDeleteConfirm('core.login.confirmdeletesite', { sitename: siteName }); diff --git a/src/core/features/mainmenu/pages/more/more.ts b/src/core/features/mainmenu/pages/more/more.ts index 3c72b8885..506855dca 100644 --- a/src/core/features/mainmenu/pages/more/more.ts +++ b/src/core/features/mainmenu/pages/more/more.ts @@ -146,10 +146,7 @@ export class CoreMainMenuMorePage implements OnInit, OnDestroy { * @param item Item to open. */ openItem(item: CoreMainMenuCustomItem): void { - // @todo CoreNavigator.navigateToSitePath('CoreViewerIframePage', {title: item.label, url: item.url}); - - // eslint-disable-next-line no-console - console.error('openItem not implemented', item); + CoreNavigator.navigateToSitePath('viewer/iframe', { params: { title: item.label, url: item.url } }); } /** diff --git a/src/core/features/question/services/question-helper.ts b/src/core/features/question/services/question-helper.ts index 253069ab5..40f87a862 100644 --- a/src/core/features/question/services/question-helper.ts +++ b/src/core/features/question/services/question-helper.ts @@ -819,7 +819,6 @@ export class CoreQuestionHelperProvider { if (span.innerHTML) { // There's a hidden feedback. Mark the icon as tappable. // The click listener is only added if treatCorrectnessIconsClicks is called. - // @todo: Check if another attribute needs to be used now instead of tappable. icon.setAttribute('tappable', ''); } }); @@ -843,9 +842,7 @@ export class CoreQuestionHelperProvider { contextInstanceId?: number, courseId?: number, ): void { - - // @todo: Check if another attribute needs to be used now instead of tappable. - const icons = Array.from(element.querySelectorAll('i.icon.questioncorrectnessicon[tappable]')); + const icons = Array.from(element.querySelectorAll('ion-icon.questioncorrectnessicon[tappable]')); const title = Translate.instant('core.question.feedback'); icons.forEach((icon) => { diff --git a/src/core/features/settings/pages/general/general.ts b/src/core/features/settings/pages/general/general.ts index 5c248c56e..9f9f8aebf 100644 --- a/src/core/features/settings/pages/general/general.ts +++ b/src/core/features/settings/pages/general/general.ts @@ -153,8 +153,6 @@ export class CoreSettingsGeneralPage { /** * Called when the analytics setting is enabled or disabled. - * - * @todo */ async analyticsEnabledChanged(): Promise { await CorePushNotifications.enableAnalytics(this.analyticsEnabled); diff --git a/src/core/features/tag/services/tag-area-delegate.ts b/src/core/features/tag/services/tag-area-delegate.ts index 0ef6d446f..e2f0e464d 100644 --- a/src/core/features/tag/services/tag-area-delegate.ts +++ b/src/core/features/tag/services/tag-area-delegate.ts @@ -37,7 +37,6 @@ export interface CoreTagAreaHandler extends CoreDelegateHandler { * Get the component to use to display items. * * @return The component (or promise resolved with component) to use, undefined if not found. - * @todo, check return types. */ getComponent(): Type | Promise>; } @@ -85,7 +84,6 @@ export class CoreTagAreaDelegateService extends CoreDelegate * @param component Component name. * @param itemType Item type. * @return The component (or promise resolved with component) to use, undefined if not found. - * @todo, check return types. */ async getComponent(component: string, itemType: string): Promise | undefined> { const type = component + '/' + itemType; diff --git a/src/core/features/user/pages/profile/profile.page.ts b/src/core/features/user/pages/profile/profile.page.ts index 37764fec0..10d326490 100644 --- a/src/core/features/user/pages/profile/profile.page.ts +++ b/src/core/features/user/pages/profile/profile.page.ts @@ -35,6 +35,7 @@ import { CoreFileUploaderHelper } from '@features/fileuploader/services/fileuplo import { CoreIonLoadingElement } from '@classes/ion-loading'; import { CoreUtils } from '@services/utils/utils'; import { CoreNavigator } from '@services/navigator'; +import { CoreCourses } from '@features/courses/services/courses'; @Component({ selector: 'page-core-user-profile', @@ -239,8 +240,8 @@ export class CoreUserProfilePage implements OnInit, OnDestroy { async refreshUser(event?: CustomEvent): Promise { await CoreUtils.ignoreErrors(Promise.all([ CoreUser.invalidateUserCache(this.userId), - // @todo this.coursesProvider.invalidateUserNavigationOptions(), - // this.coursesProvider.invalidateUserAdministrationOptions() + CoreCourses.invalidateUserNavigationOptions(), + CoreCourses.invalidateUserAdministrationOptions(), ])); await this.fetchUser(); diff --git a/src/core/features/user/services/user-delegate.ts b/src/core/features/user/services/user-delegate.ts index 00eb18b9a..ae44378b4 100644 --- a/src/core/features/user/services/user-delegate.ts +++ b/src/core/features/user/services/user-delegate.ts @@ -20,7 +20,8 @@ import { CoreUtils } from '@services/utils/utils'; import { CoreEvents } from '@singletons/events'; import { CoreUserProfile } from './user'; import { makeSingleton } from '@singletons'; -import { CoreCourseUserAdminOrNavOptionIndexed } from '@features/courses/services/courses'; +import { CoreCourses, CoreCourseUserAdminOrNavOptionIndexed } from '@features/courses/services/courses'; +import { CoreSites } from '@services/sites'; /** * Interface that all user profile handlers must implement. @@ -241,9 +242,22 @@ export class CoreUserDelegateService extends CoreDelegate { - // @todo: Get Course admin/nav options. - let navOptions; - let admOptions; + let navOptions: CoreCourseUserAdminOrNavOptionIndexed | undefined; + let admOptions: CoreCourseUserAdminOrNavOptionIndexed | undefined; + + if (CoreCourses.canGetAdminAndNavOptions()) { + // Get course options. + const courses = await CoreCourses.getUserCourses(true); + const courseIds = courses.map((course) => course.id); + + const options = await CoreCourses.getCoursesAdminAndNavOptions(courseIds); + + // For backwards compatibility we don't modify the courseId. + const courseIdForOptions = courseId || CoreSites.getCurrentSiteHomeId(); + + navOptions = options.navOptions[courseIdForOptions]; + admOptions = options.admOptions[courseIdForOptions]; + } const userData = this.userHandlers[user.id]; userData.handlers = []; diff --git a/src/core/features/user/services/user.ts b/src/core/features/user/services/user.ts index 19d8312b8..1439f184a 100644 --- a/src/core/features/user/services/user.ts +++ b/src/core/features/user/services/user.ts @@ -759,7 +759,7 @@ export class CoreUserProvider { * @param siteId Site ID. If not defined, current site. * @return Promise resolved if success. */ - updateUserPreference(name: string, value: string, userId?: number, siteId?: string): Promise { + updateUserPreference(name: string, value: string | undefined, userId?: number, siteId?: string): Promise { const preferences = [ { type: name, @@ -780,7 +780,7 @@ export class CoreUserProvider { * @return Promise resolved if success. */ async updateUserPreferences( - preferences: { type: string; value: string }[], + preferences: { type: string; value: string | undefined }[], disableNotifications?: boolean, userId?: number, siteId?: string, diff --git a/src/core/features/viewer/components/components.module.ts b/src/core/features/viewer/components/components.module.ts index 5b1af2909..cb1428d05 100644 --- a/src/core/features/viewer/components/components.module.ts +++ b/src/core/features/viewer/components/components.module.ts @@ -15,16 +15,19 @@ import { NgModule } from '@angular/core'; import { CoreSharedModule } from '@/core/shared.module'; +import { CoreViewerImageComponent } from './image/image'; import { CoreViewerTextComponent } from './text/text'; @NgModule({ declarations: [ + CoreViewerImageComponent, CoreViewerTextComponent, ], imports: [ CoreSharedModule, ], exports: [ + CoreViewerImageComponent, CoreViewerTextComponent, ], }) diff --git a/src/core/features/viewer/components/image/image.html b/src/core/features/viewer/components/image/image.html new file mode 100644 index 000000000..d293205fb --- /dev/null +++ b/src/core/features/viewer/components/image/image.html @@ -0,0 +1,14 @@ + + + {{ title }} + + + + + + + + + + + diff --git a/src/core/features/viewer/components/image/image.scss b/src/core/features/viewer/components/image/image.scss new file mode 100644 index 000000000..c8b0d2f62 --- /dev/null +++ b/src/core/features/viewer/components/image/image.scss @@ -0,0 +1,9 @@ +:host { + .core-zoom-pane { + height: 100%; + + img { + max-width: none; + } + } +} diff --git a/src/core/features/viewer/components/image/image.ts b/src/core/features/viewer/components/image/image.ts new file mode 100644 index 000000000..ed6a64f0b --- /dev/null +++ b/src/core/features/viewer/components/image/image.ts @@ -0,0 +1,44 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Component, Input, OnInit } from '@angular/core'; + +import { ModalController, Translate } from '@singletons'; + +/** + * Modal component to view an image. + */ +@Component({ + selector: 'core-viewer-image', + templateUrl: 'image.html', +}) +export class CoreViewerImageComponent implements OnInit { + + @Input() title?: string; // Modal title. + @Input() image?: string; // Image URL. + @Input() component?: string; // Component to use in external-content. + @Input() componentId?: string | number; // Component ID to use in external-content. + + ngOnInit(): void { + this.title = this.title || Translate.instant('core.imageviewer'); + } + + /** + * Close modal. + */ + closeModal(): void { + ModalController.dismiss(); + } + +} diff --git a/src/core/features/viewer/pages/iframe/iframe.html b/src/core/features/viewer/pages/iframe/iframe.html new file mode 100644 index 000000000..f7bd2fecb --- /dev/null +++ b/src/core/features/viewer/pages/iframe/iframe.html @@ -0,0 +1,13 @@ + + + + + + {{ title }} + + + + + + + diff --git a/src/core/features/viewer/pages/iframe/iframe.module.ts b/src/core/features/viewer/pages/iframe/iframe.module.ts new file mode 100644 index 000000000..9a8e7380c --- /dev/null +++ b/src/core/features/viewer/pages/iframe/iframe.module.ts @@ -0,0 +1,38 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +import { CoreSharedModule } from '@/core/shared.module'; +import { CoreViewerIframePage } from './iframe'; + +const routes: Routes = [ + { + path: '', + component: CoreViewerIframePage, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + CoreSharedModule, + ], + declarations: [ + CoreViewerIframePage, + ], + exports: [RouterModule], +}) +export class CoreViewerIframePageModule {} diff --git a/src/core/features/viewer/pages/iframe/iframe.ts b/src/core/features/viewer/pages/iframe/iframe.ts new file mode 100644 index 000000000..fca24169a --- /dev/null +++ b/src/core/features/viewer/pages/iframe/iframe.ts @@ -0,0 +1,57 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Component, OnInit } from '@angular/core'; +import { CoreNavigator } from '@services/navigator'; + +import { CoreSites } from '@services/sites'; + +/** + * Page to display a URL in an iframe. + */ +@Component({ + selector: 'core-viewer-iframe', + templateUrl: 'iframe.html', +}) +export class CoreViewerIframePage implements OnInit { + + title?: string; // Page title. + url?: string; // Iframe URL. + /* Whether the URL should be open with auto-login. Accepts the following values: + "yes" -> Always auto-login. + "no" -> Never auto-login. + "check" -> Auto-login only if it points to the current site. Default value. */ + autoLogin?: string; + finalUrl?: string; + + async ngOnInit(): Promise { + this.title = CoreNavigator.getRouteParam('title'); + this.url = CoreNavigator.getRouteParam('url'); + this.autoLogin = CoreNavigator.getRouteParam('autoLogin') || 'check'; + + if (!this.url) { + return; + } + + const currentSite = CoreSites.getCurrentSite(); + + if (currentSite && (this.autoLogin == 'yes' || (this.autoLogin == 'check' && currentSite.containsUrl(this.url)))) { + // Format the URL to add auto-login. + this.finalUrl = await currentSite.getAutoLoginUrl(this.url, false); + } else { + this.finalUrl = this.url; + } + } + +} diff --git a/src/core/features/viewer/viewer-lazy.module.ts b/src/core/features/viewer/viewer-lazy.module.ts new file mode 100644 index 000000000..99649b6af --- /dev/null +++ b/src/core/features/viewer/viewer-lazy.module.ts @@ -0,0 +1,28 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +const routes: Routes = [ + { + path: 'iframe', + loadChildren: () => import('./pages/iframe/iframe.module').then( m => m.CoreViewerIframePageModule), + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], +}) +export class CoreViewerLazyModule {} diff --git a/src/core/features/viewer/viewer.module.ts b/src/core/features/viewer/viewer.module.ts index ae9dc3816..bfa5806c6 100644 --- a/src/core/features/viewer/viewer.module.ts +++ b/src/core/features/viewer/viewer.module.ts @@ -13,11 +13,21 @@ // limitations under the License. import { NgModule } from '@angular/core'; +import { Routes } from '@angular/router'; +import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module'; import { CoreViewerComponentsModule } from './components/components.module'; +const routes: Routes = [ + { + path: 'viewer', + loadChildren: () => import('./viewer-lazy.module').then(m => m.CoreViewerLazyModule), + }, +]; + @NgModule({ imports: [ + CoreMainMenuTabRoutingModule.forChild(routes), CoreViewerComponentsModule, ], }) diff --git a/src/core/services/groups.ts b/src/core/services/groups.ts index e5b19bd01..cedb82af9 100644 --- a/src/core/services/groups.ts +++ b/src/core/services/groups.ts @@ -19,6 +19,7 @@ import { CoreSite, CoreSiteWSPreSets } from '@classes/site'; import { CoreError } from '@classes/errors/error'; import { makeSingleton, Translate } from '@singletons'; import { CoreWSExternalWarning } from '@services/ws'; +import { CoreCourses } from '@features/courses/services/courses'; const ROOT_CACHE_KEY = 'mmGroups:'; @@ -242,8 +243,11 @@ export class CoreGroupsProvider { return this.getUserGroupsInCourse(0, siteId); } - // @todo Get courses. - return []; + const courses = await CoreCourses.getUserCourses(false, siteId); + + courses.push({ id: site.getSiteHomeId() }); // Add site home. + + return this.getUserGroups(courses, siteId); } /** diff --git a/src/core/services/update-manager.ts b/src/core/services/update-manager.ts index 5d11fd4fc..cf608e954 100644 --- a/src/core/services/update-manager.ts +++ b/src/core/services/update-manager.ts @@ -18,6 +18,7 @@ import { CoreConfig } from '@services/config'; import { CoreConstants } from '@/core/constants'; import { CoreLogger } from '@singletons/logger'; import { makeSingleton } from '@singletons'; +import { CoreH5P } from '@features/h5p/services/h5p'; const VERSION_APPLIED = 'version_applied'; @@ -42,13 +43,13 @@ export class CoreUpdateManagerProvider { * @return Promise resolved when the update process finishes. */ async load(): Promise { - const promises = []; + const promises: Promise[] = []; const versionCode = CoreConstants.CONFIG.versioncode; const versionApplied = await CoreConfig.get(VERSION_APPLIED, 0); if (versionCode >= 3900 && versionApplied < 3900 && versionApplied > 0) { - // @todo: H5P update. + promises.push(CoreH5P.h5pPlayer.deleteAllContentIndexes()); } try { diff --git a/src/core/services/utils/dom.ts b/src/core/services/utils/dom.ts index 6212a6678..52b82602b 100644 --- a/src/core/services/utils/dom.ts +++ b/src/core/services/utils/dom.ts @@ -31,11 +31,20 @@ import { CoreIonLoadingElement } from '@classes/ion-loading'; import { CoreCanceledError } from '@classes/errors/cancelederror'; import { CoreError } from '@classes/errors/error'; import { CoreSilentError } from '@classes/errors/silenterror'; - -import { makeSingleton, Translate, AlertController, LoadingController, ToastController } from '@singletons'; +import { + makeSingleton, + Translate, + AlertController, + LoadingController, + ToastController, + PopoverController, + ModalController, +} 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 { CoreViewerImageComponent } from '@features/viewer/components/image/image'; /* * "Utils" service with helper functions for UI, DOM elements and HTML code. @@ -810,8 +819,18 @@ export class CoreDomUtilsProvider { el.setAttribute('data-original-title', content); el.setAttribute('title', ''); - el.addEventListener('click', () => { - // @todo + el.addEventListener('click', async (ev: Event) => { + const html = el.getAttribute('data-html'); + + const popover = await PopoverController.create({ + component: CoreBSTooltipComponent, + componentProps: { + content, + html: html === 'true', + }, + event: ev, + }); + await popover.present(); }); }); } @@ -1554,12 +1573,32 @@ export class CoreDomUtilsProvider { * @param message Modal message. * @param buttons Buttons to pass to the modal. * @param placeholder Placeholder of the input element if any. - * @return Promise resolved when modal presented. + * @return Promise resolved with the entered text if any. */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - showTextareaPrompt(title: string, message: string, buttons: (string | unknown)[], placeholder?: string): Promise { - // @todo - return Promise.resolve(); + async showTextareaPrompt( + title: string, + message: string, + buttons: AlertButton[], + placeholder?: string, + ): Promise { + const alert = await AlertController.create({ + header: title, + message, + inputs: [ + { + name: 'textarea-prompt', + type: 'textarea', + placeholder: placeholder, + }, + ], + buttons, + }); + + await alert.present(); + + const result = await alert.onWillDismiss(); + + return result.data?.values?.['textarea-prompt']; } /** @@ -1671,9 +1710,30 @@ export class CoreDomUtilsProvider { * @param componentId An ID to use in conjunction with the component. * @param fullScreen Whether the modal should be full screen. */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - viewImage(image: string, title?: string | null, component?: string, componentId?: string | number, fullScreen?: boolean): void { - // @todo + async viewImage( + image: string, + title?: string | null, + component?: string, + componentId?: string | number, + fullScreen?: boolean, + ): Promise { + if (!image) { + return; + } + + const modal = await ModalController.create({ + component: CoreViewerImageComponent, + componentProps: { + title, + image, + component, + componentId, + }, + cssClass: fullScreen ? 'core-modal-fullscreen' : '', + }); + + + await modal.present(); } /**