From 1412a5571cf41b1f0178f915c4efd6eedf2e4b20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 18 Jul 2024 14:43:22 +0200 Subject: [PATCH] MOBILE-4616 viewer: Create a service to have all the viewer functions --- .../components/submission/submission.ts | 3 +- .../feedback/comments/component/comments.ts | 4 +- .../onlinetext/component/onlinetext.ts | 3 +- .../services/handlers/push-click.ts | 4 +- src/core/directives/format-text.ts | 3 +- .../login/pages/email-signup/email-signup.ts | 3 +- src/core/features/mainmenu/pages/more/more.ts | 5 +- .../question/services/question-helper.ts | 3 +- .../features/viewer/pages/iframe/iframe.ts | 17 ++- src/core/features/viewer/services/viewer.ts | 116 ++++++++++++++++++ src/core/services/file.ts | 81 ++++++------ src/core/services/utils/dom.ts | 20 +-- src/core/services/utils/text.ts | 42 ++----- 13 files changed, 201 insertions(+), 103 deletions(-) create mode 100644 src/core/features/viewer/services/viewer.ts diff --git a/src/addons/mod/assign/components/submission/submission.ts b/src/addons/mod/assign/components/submission/submission.ts index 5d4148030..cee76d906 100644 --- a/src/addons/mod/assign/components/submission/submission.ts +++ b/src/addons/mod/assign/components/submission/submission.ts @@ -67,6 +67,7 @@ import { ADDON_MOD_ASSIGN_SUBMITTED_FOR_GRADING_EVENT, ADDON_MOD_ASSIGN_UNLIMITED_ATTEMPTS, } from '../../constants'; +import { CoreViewer } from '@features/viewer/services/viewer'; /** * Component that displays an assignment submission. @@ -866,7 +867,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can */ showAdvancedGrade(): void { if (this.feedback && this.feedback.advancedgrade) { - CoreTextUtils.viewText( + CoreViewer.viewText( Translate.instant('core.grades.grade'), this.feedback.gradefordisplay, { diff --git a/src/addons/mod/assign/feedback/comments/component/comments.ts b/src/addons/mod/assign/feedback/comments/component/comments.ts index d23cd85dd..e6f44ae02 100644 --- a/src/addons/mod/assign/feedback/comments/component/comments.ts +++ b/src/addons/mod/assign/feedback/comments/component/comments.ts @@ -27,6 +27,8 @@ import { CoreUtils } from '@services/utils/utils'; import { AddonModAssignFeedbackPluginBaseComponent } from '@addons/mod/assign/classes/base-feedback-plugin-component'; import { ContextLevel } from '@/core/constants'; import { ADDON_MOD_ASSIGN_COMPONENT } from '@addons/mod/assign/constants'; +import { CoreViewer } from '@features/viewer/services/viewer'; + /** * Component to render a comments feedback plugin. */ @@ -67,7 +69,7 @@ export class AddonModAssignFeedbackCommentsComponent extends AddonModAssignFeedb if (this.text) { // Open a new state with the text. - CoreTextUtils.viewText(this.plugin.name, this.text, { + CoreViewer.viewText(this.plugin.name, this.text, { component: this.component, componentId: this.assign.cmid, filter: true, diff --git a/src/addons/mod/assign/submission/onlinetext/component/onlinetext.ts b/src/addons/mod/assign/submission/onlinetext/component/onlinetext.ts index 2feffbd82..5b4390a98 100644 --- a/src/addons/mod/assign/submission/onlinetext/component/onlinetext.ts +++ b/src/addons/mod/assign/submission/onlinetext/component/onlinetext.ts @@ -23,6 +23,7 @@ import { CoreUtils } from '@services/utils/utils'; import { AddonModAssignSubmissionOnlineTextPluginData } from '../services/handler'; import { ContextLevel } from '@/core/constants'; import { ADDON_MOD_ASSIGN_COMPONENT } from '@addons/mod/assign/constants'; +import { CoreViewer } from '@features/viewer/services/viewer'; /** * Component to render an onlinetext submission plugin. @@ -84,7 +85,7 @@ export class AddonModAssignSubmissionOnlineTextComponent extends AddonModAssignS if (this.text) { // Open a new state with the interpolated contents. - CoreTextUtils.viewText(this.plugin.name, this.text, { + CoreViewer.viewText(this.plugin.name, this.text, { component: this.component, componentId: this.assign.cmid, filter: true, diff --git a/src/addons/notifications/services/handlers/push-click.ts b/src/addons/notifications/services/handlers/push-click.ts index 8354de06a..9866bd5f4 100644 --- a/src/addons/notifications/services/handlers/push-click.ts +++ b/src/addons/notifications/services/handlers/push-click.ts @@ -15,7 +15,6 @@ import { Injectable } from '@angular/core'; import { CoreNavigator } from '@services/navigator'; -import { CoreTextUtils } from '@services/utils/text'; import { CoreUtils } from '@services/utils/utils'; import { makeSingleton } from '@singletons'; import { CorePushNotificationsClickHandler } from '@features/pushnotifications/services/push-delegate'; @@ -24,6 +23,7 @@ import { CoreContentLinksHelper } from '@features/contentlinks/services/contentl import { AddonNotifications } from '../notifications'; import { AddonNotificationsMainMenuHandlerService } from './mainmenu'; import { AddonNotificationsHelper } from '../notifications-helper'; +import { CoreViewer } from '@features/viewer/services/viewer'; /** * Handler for non-messaging push notifications clicks. @@ -77,7 +77,7 @@ export class AddonNotificationsPushClickHandlerService implements CorePushNotifi if (notification.customdata?.extendedtext) { // Display the text in a modal. - return CoreTextUtils.viewText(notification.title || '', notification.customdata.extendedtext, { + return CoreViewer.viewText(notification.title || '', notification.customdata.extendedtext, { displayCopyButton: true, modalOptions: { cssClass: 'core-modal-fullscreen' }, }); diff --git a/src/core/directives/format-text.ts b/src/core/directives/format-text.ts index 2af1d794c..9c4f4691b 100644 --- a/src/core/directives/format-text.ts +++ b/src/core/directives/format-text.ts @@ -57,6 +57,7 @@ import { CoreIcons } from '@singletons/icons'; import { ContextLevel } from '../constants'; import { CoreWait } from '@singletons/wait'; import { toBoolean } from '../transforms/boolean'; +import { CoreViewer } from '@features/viewer/services/viewer'; /** * Directive to format text rendered. It renders the HTML and treats all links and media, using CoreLinkDirective @@ -294,7 +295,7 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncDirec e.preventDefault(); e.stopPropagation(); - CoreDomUtils.viewImage(imgSrc, img.getAttribute('alt'), this.component, this.componentId); + CoreViewer.viewImage(imgSrc, img.getAttribute('alt'), this.component, this.componentId); }); img.parentNode?.appendChild(button); diff --git a/src/core/features/login/pages/email-signup/email-signup.ts b/src/core/features/login/pages/email-signup/email-signup.ts index 6851ebe72..0d4c3d952 100644 --- a/src/core/features/login/pages/email-signup/email-signup.ts +++ b/src/core/features/login/pages/email-signup/email-signup.ts @@ -36,6 +36,7 @@ import { CoreDom } from '@singletons/dom'; import { CoreSitesFactory } from '@services/sites-factory'; import { EMAIL_SIGNUP_FEATURE_NAME } from '@features/login/constants'; import { CoreInputErrorsMessages } from '@components/input-errors/input-errors'; +import { CoreViewer } from '@features/viewer/services/viewer'; /** * Page to signup using email. @@ -385,7 +386,7 @@ export class CoreLoginEmailSignupPage implements OnInit { * Show authentication instructions. */ showAuthInstructions(): void { - CoreTextUtils.viewText(Translate.instant('core.login.instructions'), this.authInstructions); + CoreViewer.viewText(Translate.instant('core.login.instructions'), this.authInstructions); } /** diff --git a/src/core/features/mainmenu/pages/more/more.ts b/src/core/features/mainmenu/pages/more/more.ts index af302e7ad..afa15de98 100644 --- a/src/core/features/mainmenu/pages/more/more.ts +++ b/src/core/features/mainmenu/pages/more/more.ts @@ -26,6 +26,7 @@ import { CoreTextUtils } from '@services/utils/text'; import { Translate } from '@singletons'; import { CoreMainMenuDeepLinkManager } from '@features/mainmenu/classes/deep-link-manager'; import { CoreDom } from '@singletons/dom'; +import { CoreViewer } from '@features/viewer/services/viewer'; /** * Page that displays the more page of the app. @@ -133,7 +134,7 @@ export class CoreMainMenuMorePage implements OnInit, OnDestroy { * @param item Item to open. */ openItem(item: CoreMainMenuCustomItem): void { - CoreNavigator.navigateToSitePath('viewer/iframe', { params: { title: item.label, url: item.url } }); + CoreViewer.openIframeViewer(item.label, item.url); } /** @@ -166,7 +167,7 @@ export class CoreMainMenuMorePage implements OnInit, OnDestroy { }); } else { // It's not a URL, open it in a modal so the user can see it and copy it. - CoreTextUtils.viewText(Translate.instant('core.qrscanner'), text, { + CoreViewer.viewText(Translate.instant('core.qrscanner'), text, { displayCopyButton: true, }); } diff --git a/src/core/features/question/services/question-helper.ts b/src/core/features/question/services/question-helper.ts index ccd5e56f1..1b917ee4e 100644 --- a/src/core/features/question/services/question-helper.ts +++ b/src/core/features/question/services/question-helper.ts @@ -30,6 +30,7 @@ import { CoreIcons } from '@singletons/icons'; import { CoreUrl } from '@singletons/url'; import { ContextLevel } from '@/core/constants'; import { CoreIonicColorNames } from '@singletons/colors'; +import { CoreViewer } from '@features/viewer/services/viewer'; /** * Service with some common functions to handle questions. @@ -914,7 +915,7 @@ export class CoreQuestionHelperProvider { event.preventDefault(); event.stopPropagation(); - CoreTextUtils.viewText(title, target.html ?? '', { + CoreViewer.viewText(title, target.html ?? '', { component: component, componentId: componentId, filter: true, diff --git a/src/core/features/viewer/pages/iframe/iframe.ts b/src/core/features/viewer/pages/iframe/iframe.ts index ed468be0b..85487da0f 100644 --- a/src/core/features/viewer/pages/iframe/iframe.ts +++ b/src/core/features/viewer/pages/iframe/iframe.ts @@ -14,6 +14,7 @@ import { Component, OnInit } from '@angular/core'; import { CoreNavigator } from '@services/navigator'; +import { CoreDomUtils } from '@services/utils/dom'; /** * Page to display a URL in an iframe. @@ -28,9 +29,21 @@ export class CoreViewerIframePage implements OnInit { url?: string; // Iframe URL. autoLogin?: boolean; // Whether to try to use auto-login. + /** + * @inheritdoc + */ async ngOnInit(): Promise { - this.title = CoreNavigator.getRouteParam('title'); - this.url = CoreNavigator.getRouteParam('url'); + try { + this.title = CoreNavigator.getRequiredRouteParam('title'); + this.url = CoreNavigator.getRequiredRouteParam('url'); + } catch (error) { + CoreDomUtils.showErrorModal(error); + + CoreNavigator.back(); + + return; + } + this.autoLogin = CoreNavigator.getRouteBooleanParam('autoLogin') ?? true; } diff --git a/src/core/features/viewer/services/viewer.ts b/src/core/features/viewer/services/viewer.ts new file mode 100644 index 000000000..98b38f676 --- /dev/null +++ b/src/core/features/viewer/services/viewer.ts @@ -0,0 +1,116 @@ +// (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 { ContextLevel } from '@/core/constants'; +import { Injectable } from '@angular/core'; +import { ModalOptions } from '@ionic/angular'; +import { CoreModals } from '@services/modals'; +import { CoreNavigator } from '@services/navigator'; +import { CoreWSFile } from '@services/ws'; +import { makeSingleton } from '@singletons'; + +/** + * Viewer services. + */ +@Injectable({ providedIn: 'root' }) +export class CoreViewerService { + + /** + * View an image in a modal. + * + * @param image URL of the image. + * @param title Title of the page or modal. + * @param component Component to link the image to if needed. + * @param componentId An ID to use in conjunction with the component. + */ + async viewImage( + image: string, + title?: string | null, + component?: string, + componentId?: string | number, + ): Promise { + if (!image) { + return; + } + const { CoreViewerImageComponent } = await import('@features/viewer/components/image/image'); + + await CoreModals.openModal({ + component: CoreViewerImageComponent, + componentProps: { + title, + image, + component, + componentId, + }, + cssClass: 'core-modal-transparent', + }); + + } + + /** + * Shows a text on a new page. + * + * @param title Title of the new state. + * @param content Content of the text to be expanded. + * @param options Options. + */ + async viewText(title: string, content: string, options?: CoreViewerTextOptions): Promise { + if (!content.length) { + return; + } + + options = options || {}; + const { CoreViewerTextComponent } = await import('@features/viewer/components/text/text'); + + const modalOptions: ModalOptions = Object.assign(options.modalOptions || {}, { + component: CoreViewerTextComponent, + }); + delete options.modalOptions; + modalOptions.componentProps = { + title, + content, + ...options, + }; + + await CoreModals.openModal(modalOptions); + } + + /** + * Navigate to iframe viewer. + * + * @param title Page title. + * @param url Iframe URL. + * @param autoLogin Whether to try to use auto-login. + */ + async openIframeViewer(title: string, url: string, autoLogin?: boolean): Promise { + await CoreNavigator.navigateToSitePath('viewer/iframe', { params: { title, url, autoLogin } }); + } + +} +export const CoreViewer = makeSingleton(CoreViewerService); + +/** + * Options for viewText. + */ +export type CoreViewerTextOptions = { + component?: string; // Component to link the embedded files to. + componentId?: string | number; // An ID to use in conjunction with the component. + files?: CoreWSFile[]; // List of files to display along with the text. + filter?: boolean; // Whether the text should be filtered. + contextLevel?: ContextLevel; // The context level. + instanceId?: number; // The instance ID related to the context. + courseId?: number; // Course ID the text belongs to. It can be used to improve performance with filters. + displayCopyButton?: boolean; // Whether to display a button to copy the text. + modalOptions?: Partial; // Modal options. +}; diff --git a/src/core/services/file.ts b/src/core/services/file.ts index aa369f185..57be3a3e3 100644 --- a/src/core/services/file.ts +++ b/src/core/services/file.ts @@ -148,12 +148,11 @@ export class CoreFileProvider { * @param path Relative path to the file. * @returns Promise resolved when the file is retrieved. */ - getFile(path: string): Promise { - return this.init().then(() => { - this.logger.debug('Get file: ' + path); + async getFile(path: string): Promise { + await this.init(); + this.logger.debug('Get file: ' + path); - return File.resolveLocalFilesystemUrl(this.addBasePathIfNeeded(path)); - }).then((entry) => entry); + return await File.resolveLocalFilesystemUrl(this.addBasePathIfNeeded(path)); } /** @@ -162,12 +161,12 @@ export class CoreFileProvider { * @param path Relative path to the directory. * @returns Promise resolved when the directory is retrieved. */ - getDir(path: string): Promise { - return this.init().then(() => { - this.logger.debug('Get directory: ' + path); + async getDir(path: string): Promise { + await this.init(); - return File.resolveDirectoryUrl(this.addBasePathIfNeeded(path)); - }); + this.logger.debug('Get directory: ' + path); + + return await File.resolveDirectoryUrl(this.addBasePathIfNeeded(path)); } /** @@ -377,12 +376,14 @@ export class CoreFileProvider { * @param path Relative path to the directory. * @returns Promise to be resolved when the size is calculated. */ - getDirectorySize(path: string): Promise { + async getDirectorySize(path: string): Promise { path = this.removeBasePath(path); this.logger.debug('Get size of dir: ' + path); - return this.getDir(path).then((dirEntry) => this.getSize(dirEntry)); + const dirEntry = await this.getDir(path); + + return this.getSize(dirEntry); } /** @@ -391,12 +392,14 @@ export class CoreFileProvider { * @param path Relative path to the file. * @returns Promise to be resolved when the size is calculated. */ - getFileSize(path: string): Promise { + async getFileSize(path: string): Promise { path = this.removeBasePath(path); this.logger.debug('Get size of file: ' + path); - return this.getFile(path).then((fileEntry) => this.getSize(fileEntry)); + const fileEntry = await this.getFile(path); + + return this.getSize(fileEntry); } /** @@ -418,16 +421,15 @@ export class CoreFileProvider { * * @returns Promise resolved with the estimated free space in bytes. */ - calculateFreeSpace(): Promise { - return File.getFreeDiskSpace().then((size) => { - if (CorePlatform.isIOS()) { - // In iOS the size is in bytes. - return Number(size); - } + async calculateFreeSpace(): Promise { + const size = await File.getFreeDiskSpace(); - // The size is in KB, convert it to bytes. - return Number(size) * 1024; - }); + if (CorePlatform.isIOS()) { + // In iOS the size is in bytes. + return Number(size); + } + + return Number(size) * 1024; } /** @@ -642,8 +644,10 @@ export class CoreFileProvider { * @param fullPath Absolute path to the file. * @returns Promise to be resolved when the file is retrieved. */ - getExternalFile(fullPath: string): Promise { - return File.resolveLocalFilesystemUrl(fullPath).then((entry) => entry); + async getExternalFile(fullPath: string): Promise { + const entry = await File.resolveLocalFilesystemUrl(fullPath); + + return entry; } /** @@ -676,14 +680,14 @@ export class CoreFileProvider { * * @returns Promise to be resolved when the base path is retrieved. */ - getBasePath(): Promise { - return this.init().then(() => { - if (this.basePath.slice(-1) == '/') { - return this.basePath; - } else { - return this.basePath + '/'; - } - }); + async getBasePath(): Promise { + await this.init(); + + if (this.basePath.slice(-1) === '/') { + return this.basePath; + } else { + return this.basePath + '/'; + } } /** @@ -1004,15 +1008,10 @@ export class CoreFileProvider { * @param isDir True if directory, false if file. * @returns Promise resolved with metadata. */ - getMetadataFromPath(path: string, isDir?: boolean): Promise { - let promise; - if (isDir) { - promise = this.getDir(path); - } else { - promise = this.getFile(path); - } + async getMetadataFromPath(path: string, isDir?: boolean): Promise { + const entry = isDir ? await this.getDir(path) : await this.getFile(path); - return promise.then((entry) => this.getMetadata(entry)); + return this.getMetadata(entry); } /** diff --git a/src/core/services/utils/dom.ts b/src/core/services/utils/dom.ts index e9423a928..212909601 100644 --- a/src/core/services/utils/dom.ts +++ b/src/core/services/utils/dom.ts @@ -52,6 +52,7 @@ import { CoreToasts, ToastDuration, ShowToastOptions } from '../toasts'; import { fixOverlayAriaHidden } from '@/core/utils/fix-aria-hidden'; import { CoreModals, OpenModalOptions } from '@services/modals'; import { CorePopovers, OpenPopoverOptions } from '@services/popovers'; +import { CoreViewer } from '@features/viewer/services/viewer'; /* * "Utils" service with helper functions for UI, DOM elements and HTML code. @@ -1525,6 +1526,8 @@ export class CoreDomUtilsProvider { * @param title Title of the page or modal. * @param component Component to link the image to if needed. * @param componentId An ID to use in conjunction with the component. + * + * @deprecated since 4.5. Use CoreViewer.viewImage instead. */ async viewImage( image: string, @@ -1532,22 +1535,7 @@ export class CoreDomUtilsProvider { component?: string, componentId?: string | number, ): Promise { - if (!image) { - return; - } - const { CoreViewerImageComponent } = await import('@features/viewer/components/image/image'); - - await CoreModals.openModal({ - component: CoreViewerImageComponent, - componentProps: { - title, - image, - component, - componentId, - }, - cssClass: 'core-modal-transparent', - }); - + await CoreViewer.viewImage(image, title, component, componentId); } /** diff --git a/src/core/services/utils/text.ts b/src/core/services/utils/text.ts index 5215f87d0..5cb5b553d 100644 --- a/src/core/services/utils/text.ts +++ b/src/core/services/utils/text.ts @@ -14,21 +14,19 @@ import { Injectable } from '@angular/core'; import { SafeUrl } from '@angular/platform-browser'; -import { ModalOptions } from '@ionic/core'; import { CoreAnyError, CoreError } from '@classes/errors/error'; import { DomSanitizer, makeSingleton, Translate } from '@singletons'; import { CoreWSFile } from '@services/ws'; import { Locutus } from '@singletons/locutus'; import { CoreFileHelper } from '@services/file-helper'; -import { CoreModals } from '@services/modals'; import { CoreUrl } from '@singletons/url'; import { AlertButton } from '@ionic/angular'; 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'; +import { CoreViewer, CoreViewerTextOptions } from '@features/viewer/services/viewer'; /** * Different type of errors the app can treat. @@ -1030,27 +1028,11 @@ export class CoreTextUtilsProvider { * @param title Title of the new state. * @param content Content of the text to be expanded. * @param options Options. - * @returns Promise resolved when the modal is displayed. + * + * @deprecated since 4.5. Use CoreViewer.viewText instead. */ - async viewText(title: string, content: string, options?: CoreTextUtilsViewTextOptions): Promise { - if (!content.length) { - return; - } - - options = options || {}; - const { CoreViewerTextComponent } = await import('@features/viewer/components/text/text'); - - const modalOptions: ModalOptions = Object.assign(options.modalOptions || {}, { - component: CoreViewerTextComponent, - }); - delete options.modalOptions; - modalOptions.componentProps = { - title, - content, - ...options, - }; - - await CoreModals.openModal(modalOptions); + async viewText(title: string, content: string, options?: CoreViewerTextOptions): Promise { + await CoreViewer.viewText(title, content, options); } } @@ -1058,18 +1040,10 @@ export const CoreTextUtils = makeSingleton(CoreTextUtilsProvider); /** * Options for viewText. + * + * @deprecated since 4.5. Use CoreViewerTextOptions instead. */ -export type CoreTextUtilsViewTextOptions = { - component?: string; // Component to link the embedded files to. - componentId?: string | number; // An ID to use in conjunction with the component. - files?: CoreWSFile[]; // List of files to display along with the text. - filter?: boolean; // Whether the text should be filtered. - contextLevel?: ContextLevel; // The context level. - instanceId?: number; // The instance ID related to the context. - courseId?: number; // Course ID the text belongs to. It can be used to improve performance with filters. - displayCopyButton?: boolean; // Whether to display a button to copy the text. - modalOptions?: Partial; // Modal options. -}; +export type CoreTextUtilsViewTextOptions = CoreViewerTextOptions; /** * Define text formatting types.