MOBILE-3565 login: Initial implementation of init page

main
Dani Palou 2020-10-06 10:48:26 +02:00
parent cf9a261e21
commit 81ce1a7d01
18 changed files with 963 additions and 20 deletions

5
package-lock.json generated
View File

@ -9525,6 +9525,11 @@
"minimist": "^1.2.5"
}
},
"moment": {
"version": "2.29.0",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.0.tgz",
"integrity": "sha512-z6IJ5HXYiuxvFTI6eiQ9dm77uE0gyy1yXNApVHqTcnIKfY9tIwEjlzsZ6u1LQXvVgKeTnv9Xm7NDvJ7lso3MtA=="
},
"move-concurrently": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",

View File

@ -33,7 +33,7 @@
"@angular/platform-browser-dynamic": "~10.0.0",
"@angular/router": "~10.0.0",
"@ionic-native/core": "^5.0.0",
"@ionic-native/splash-screen": "^5.0.0",
"@ionic-native/splash-screen": "^5.28.0",
"@ionic-native/status-bar": "^5.0.0",
"@ionic/angular": "^5.0.0",
"com-darryncampbell-cordova-plugin-intent": "^2.0.0",
@ -73,6 +73,7 @@
"cordova-support-google-services": "^1.2.1",
"cordova.plugins.diagnostic": "^6.0.2",
"es6-promise-plugin": "^4.2.2",
"moment": "^2.29.0",
"nl.kingsquare.cordova.background-audio": "^1.0.1",
"phonegap-plugin-multidex": "^1.0.0",
"phonegap-plugin-push": "git+https://github.com/moodlemobile/phonegap-plugin-push.git#moodle-v3",

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { NgModule } from '@angular/core';
import { NgModule, Injector } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouteReuseStrategy } from '@angular/router';
@ -21,13 +21,42 @@ import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';
import { CoreAppProvider } from '@services/app';
import { CoreInitDelegate } from '@services/init';
import { CoreUtilsProvider } from '@services/utils/utils';
import { CoreEmulatorModule } from '@core/emulator/emulator.module';
import { CoreLoginModule } from '@core/login/login.module';
import { setSingletonsInjector } from '@singletons/core.singletons';
@NgModule({
declarations: [AppComponent],
entryComponents: [],
imports: [BrowserModule, IonicModule.forRoot(), AppRoutingModule],
imports: [
BrowserModule,
IonicModule.forRoot(),
AppRoutingModule,
CoreEmulatorModule,
CoreLoginModule,
],
providers: [
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
CoreAppProvider,
CoreInitDelegate,
CoreUtilsProvider,
],
bootstrap: [AppComponent],
})
export class AppModule {}
export class AppModule {
constructor(injector: Injector,
initDelegate: CoreInitDelegate,
) {
// Set the injector.
setSingletonsInjector(injector);
// Execute the init processes.
initDelegate.executeInitProcesses();
}
}

View File

@ -0,0 +1,76 @@
// (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 { Injector, Type } from '@angular/core';
/**
* Stub class used to type anonymous classes created in CoreSingletonsFactory#makeSingleton method.
*/
class CoreSingleton {}
/**
* Token that can be used to resolve instances from the injector.
*/
export type CoreInjectionToken<Service> = Type<Service> | Type<any> | string;
/**
* Singleton class created using the factory.
*/
export type CoreSingletonClass<Service> = typeof CoreSingleton & { instance: Service };
/**
* Factory used to create CoreSingleton classes that get instances from an injector.
*/
export class CoreSingletonsFactory {
/**
* Angular injector used to resolve singleton instances.
*/
private injector: Injector;
/**
* Set the injector that will be used to resolve instances in the singletons created with this factory.
*
* @param injector Injector.
*/
setInjector(injector: Injector): void {
this.injector = injector;
}
/**
* Make a singleton that will hold an instance resolved from the factory injector.
*
* @param injectionToken Injection token used to resolve the singleton instance. This is usually the service class if the
* provider was defined using a class or the string used in the `provide` key if it was defined using an object.
*/
makeSingleton<Service>(injectionToken: CoreInjectionToken<Service>): CoreSingletonClass<Service> {
// tslint:disable: no-this-assignment
const factory = this;
return class {
private static _instance: Service;
static get instance(): Service {
// Initialize instances lazily.
if (!this._instance) {
this._instance = factory.injector.get(injectionToken);
}
return this._instance;
}
};
}
}

View File

@ -0,0 +1,106 @@
// (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.
/**
* Context levels enumeration.
*/
export const enum ContextLevel {
SYSTEM = 'system',
USER = 'user',
COURSECAT = 'coursecat',
COURSE = 'course',
MODULE = 'module',
BLOCK = 'block',
}
/**
* Static class to contain all the core constants.
*/
export class CoreConstants {
static SECONDS_YEAR = 31536000;
static SECONDS_WEEK = 604800;
static SECONDS_DAY = 86400;
static SECONDS_HOUR = 3600;
static SECONDS_MINUTE = 60;
static WIFI_DOWNLOAD_THRESHOLD = 104857600; // 100MB.
static DOWNLOAD_THRESHOLD = 10485760; // 10MB.
static MINIMUM_FREE_SPACE = 10485760; // 10MB.
static IOS_FREE_SPACE_THRESHOLD = 524288000; // 500MB.
static DONT_SHOW_ERROR = 'CoreDontShowError';
static NO_SITE_ID = 'NoSite';
// Settings constants.
static SETTINGS_RICH_TEXT_EDITOR = 'CoreSettingsRichTextEditor';
static SETTINGS_NOTIFICATION_SOUND = 'CoreSettingsNotificationSound';
static SETTINGS_SYNC_ONLY_ON_WIFI = 'CoreSettingsSyncOnlyOnWifi';
static SETTINGS_DEBUG_DISPLAY = 'CoreSettingsDebugDisplay';
static SETTINGS_REPORT_IN_BACKGROUND = 'CoreSettingsReportInBackground'; // @deprecated since 3.5.0
static SETTINGS_SEND_ON_ENTER = 'CoreSettingsSendOnEnter';
static SETTINGS_FONT_SIZE = 'CoreSettingsFontSize';
static SETTINGS_COLOR_SCHEME = 'CoreSettingsColorScheme';
static SETTINGS_ANALYTICS_ENABLED = 'CoreSettingsAnalyticsEnabled';
// WS constants.
static WS_TIMEOUT = 30000; // Timeout when not in WiFi.
static WS_TIMEOUT_WIFI = 30000; // Timeout when in WiFi.
static WS_PREFIX = 'local_mobile_';
// Login constants.
static LOGIN_SSO_CODE = 2; // SSO in browser window is required.
static LOGIN_SSO_INAPP_CODE = 3; // SSO in embedded browser is required.
static LOGIN_LAUNCH_DATA = 'CoreLoginLaunchData';
// Download status constants.
static DOWNLOADED = 'downloaded';
static DOWNLOADING = 'downloading';
static NOT_DOWNLOADED = 'notdownloaded';
static OUTDATED = 'outdated';
static NOT_DOWNLOADABLE = 'notdownloadable';
// Constants from Moodle's resourcelib.
static RESOURCELIB_DISPLAY_AUTO = 0; // Try the best way.
static RESOURCELIB_DISPLAY_EMBED = 1; // Display using object tag.
static RESOURCELIB_DISPLAY_FRAME = 2; // Display inside frame.
static RESOURCELIB_DISPLAY_NEW = 3; // Display normal link in new window.
static RESOURCELIB_DISPLAY_DOWNLOAD = 4; // Force download of file instead of display.
static RESOURCELIB_DISPLAY_OPEN = 5; // Open directly.
static RESOURCELIB_DISPLAY_POPUP = 6; // Open in "emulated" pop-up without navigation.
// Feature constants. Used to report features that are, or are not, supported by a module.
static FEATURE_GRADE_HAS_GRADE = 'grade_has_grade'; // True if module can provide a grade.
static FEATURE_GRADE_OUTCOMES = 'outcomes'; // True if module supports outcomes.
static FEATURE_ADVANCED_GRADING = 'grade_advanced_grading'; // True if module supports advanced grading methods.
static FEATURE_CONTROLS_GRADE_VISIBILITY = 'controlsgradevisbility'; // True if module controls grade visibility over gradebook.
static FEATURE_PLAGIARISM = 'plagiarism'; // True if module supports plagiarism plugins.
static FEATURE_COMPLETION_TRACKS_VIEWS = 'completion_tracks_views'; // True if module tracks whether somebody viewed it.
static FEATURE_COMPLETION_HAS_RULES = 'completion_has_rules'; // True if module has custom completion rules.
static FEATURE_NO_VIEW_LINK = 'viewlink'; // True if module has no 'view' page (like label).
static FEATURE_IDNUMBER = 'idnumber'; // True if module wants support for setting the ID number for grade calculation purposes.
static FEATURE_GROUPS = 'groups'; // True if module supports groups.
static FEATURE_GROUPINGS = 'groupings'; // True if module supports groupings.
static FEATURE_MOD_ARCHETYPE = 'mod_archetype'; // Type of module.
static FEATURE_MOD_INTRO = 'mod_intro'; // True if module supports intro editor.
static FEATURE_MODEDIT_DEFAULT_COMPLETION = 'modedit_default_completion'; // True if module has default completion.
static FEATURE_COMMENT = 'comment';
static FEATURE_RATE = 'rate';
static FEATURE_BACKUP_MOODLE2 = 'backup_moodle2'; // True if module supports backup/restore of moodle2 format.
static FEATURE_SHOW_DESCRIPTION = 'showdescription'; // True if module can show description on course main page.
static FEATURE_USES_QUESTIONS = 'usesquestions'; // True if module uses the question bank.
// Pssobile archetypes for modules.
static MOD_ARCHETYPE_OTHER = 0; // Unspecified module archetype.
static MOD_ARCHETYPE_RESOURCE = 1; // Resource-like type module.
static MOD_ARCHETYPE_ASSIGNMENT = 2; // Assignment module archetype.
static MOD_ARCHETYPE_SYSTEM = 3; // System (not user-addable) module archetype.
}

View File

@ -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';
// Ionic Native services.
import { SplashScreen } from '@ionic-native/splash-screen/ngx';
/**
* This module handles the emulation of Cordova plugins in browser and desktop.
*
* It includes the "mock" of all the Ionic Native services that should be supported in browser and desktop,
* otherwise those features would only work in a Cordova environment.
*
* This module also determines if the app should use the original service or the mock. In each of the "useFactory"
* functions we check if the app is running in mobile or not, and then provide the right service to use.
*/
@NgModule({
declarations: [
],
imports: [
],
providers: [
SplashScreen,
]
})
export class CoreEmulatorModule { }

View File

@ -14,13 +14,19 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { CoreLoginInitPage } from './pages/init/init.page';
import { CoreLoginSitePage } from './pages/site/site.page';
const routes: Routes = [
{
path: '',
component: CoreLoginInitPage,
},
{
path: 'site',
component: CoreLoginSitePage,
},
];
@NgModule({

View File

@ -19,6 +19,7 @@ import { IonicModule } from '@ionic/angular';
import { CoreLoginRoutingModule } from './login-routing.module';
import { CoreLoginInitPage } from './pages/init/init.page';
import { CoreLoginSitePage } from './pages/site/site.page';
@NgModule({
@ -29,6 +30,7 @@ import { CoreLoginInitPage } from './pages/init/init.page';
],
declarations: [
CoreLoginInitPage,
CoreLoginSitePage,
],
})
export class CoreLoginModule {}

View File

@ -12,7 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component } from '@angular/core';
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { CoreApp } from '@services/app';
import { CoreInit } from '@services/init';
import { CoreConstants } from '@core/constants';
import { SplashScreen } from '@singletons/core.singletons';
/**
* Page that displays a "splash screen" while the app is being initialized.
@ -22,4 +28,69 @@ import { Component } from '@angular/core';
templateUrl: 'init.html',
styleUrls: ['init.scss'],
})
export class CoreLoginInitPage { }
export class CoreLoginInitPage implements OnInit {
constructor(protected router: Router) {}
/**
* Initialize the component.
*/
ngOnInit(): void {
// Wait for the app to be ready.
CoreInit.instance.ready().then(() => {
// Check if there was a pending redirect.
const redirectData = CoreApp.instance.getRedirect();
if (redirectData.siteId) {
// Unset redirect data.
CoreApp.instance.storeRedirect('', '', '');
// Only accept the redirect if it was stored less than 20 seconds ago.
if (Date.now() - redirectData.timemodified < 20000) {
// if (redirectData.siteId != CoreConstants.NO_SITE_ID) {
// // The redirect is pointing to a site, load it.
// return this.sitesProvider.loadSite(redirectData.siteId, redirectData.page, redirectData.params)
// .then((loggedIn) => {
// if (loggedIn) {
// return this.loginHelper.goToSiteInitialPage(this.navCtrl, redirectData.page, redirectData.params,
// { animate: false });
// }
// }).catch(() => {
// // Site doesn't exist.
// return this.loadPage();
// });
// } else {
// // No site to load, open the page.
// return this.loginHelper.goToNoSitePage(this.navCtrl, redirectData.page, redirectData.params);
// }
}
}
return this.loadPage();
}).then(() => {
// If we hide the splash screen now, the init view is still seen for an instant. Wait a bit to make sure it isn't seen.
setTimeout(() => {
SplashScreen.instance.hide();
}, 100);
});
}
/**
* Load the right page.
*
* @return Promise resolved when done.
*/
protected async loadPage(): Promise<void> {
// if (this.sitesProvider.isLoggedIn()) {
// if (this.loginHelper.isSiteLoggedOut()) {
// return this.sitesProvider.logout().then(() => {
// return this.loadPage();
// });
// }
// return this.loginHelper.goToSiteInitialPage();
// }
await this.router.navigate(['/login/site']);
}
}

View File

@ -0,0 +1,3 @@
<ion-content>
Site page.
</ion-content>

View File

@ -0,0 +1,33 @@
// (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';
/**
* Page that displays a "splash screen" while the app is being initialized.
*/
@Component({
selector: 'page-core-login-site',
templateUrl: 'site.html',
styleUrls: ['site.scss'],
})
export class CoreLoginSitePage implements OnInit {
/**
* Initialize the component.
*/
ngOnInit(): void {
}
}

View File

@ -0,0 +1,2 @@
app-root page-core-login-init {
}

View File

@ -0,0 +1,112 @@
// (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 { Injectable } from '@angular/core';
import { makeSingleton } from '@singletons/core.singletons';
import { CoreLogger } from '@singletons/logger';
/**
* Data stored for a redirect to another page/site.
*/
export type CoreRedirectData = {
/**
* ID of the site to load.
*/
siteId?: string;
/**
* Name of the page to redirect to.
*/
page?: string;
/**
* Params to pass to the page.
*/
params?: any;
/**
* Timestamp when this redirect was last modified.
*/
timemodified?: number;
};
/**
* Factory to provide some global functionalities, like access to the global app database.
* @description
* Each service or component should be responsible of creating their own database tables. Example:
*
* constructor(appProvider: CoreAppProvider) {
* this.appDB = appProvider.getDB();
* this.appDB.createTableFromSchema(this.tableSchema);
* }
*/
@Injectable()
export class CoreAppProvider {
protected logger: CoreLogger;
constructor() {
this.logger = CoreLogger.getInstance('CoreAppProvider');
}
/**
* Retrieve redirect data.
*
* @return Object with siteid, state, params and timemodified.
*/
getRedirect(): CoreRedirectData {
if (localStorage && localStorage.getItem) {
try {
const data: CoreRedirectData = {
siteId: localStorage.getItem('CoreRedirectSiteId'),
page: localStorage.getItem('CoreRedirectState'),
params: localStorage.getItem('CoreRedirectParams'),
timemodified: parseInt(localStorage.getItem('CoreRedirectTime'), 10)
};
if (data.params) {
data.params = JSON.parse(data.params);
}
return data;
} catch (ex) {
this.logger.error('Error loading redirect data:', ex);
}
}
return {};
}
/**
* Store redirect params.
*
* @param siteId Site ID.
* @param page Page to go.
* @param params Page params.
*/
storeRedirect(siteId: string, page: string, params: any): void {
if (localStorage && localStorage.setItem) {
try {
localStorage.setItem('CoreRedirectSiteId', siteId);
localStorage.setItem('CoreRedirectState', page);
localStorage.setItem('CoreRedirectParams', JSON.stringify(params));
localStorage.setItem('CoreRedirectTime', String(Date.now()));
} catch (ex) {
// Ignore errors.
}
}
}
}
export class CoreApp extends makeSingleton(CoreAppProvider) {}

View File

@ -0,0 +1,178 @@
// (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 { Injectable } from '@angular/core';
import { CoreUtils } from '@services/utils/utils';
import { CoreLogger } from '@singletons/logger';
import { makeSingleton } from '@singletons/core.singletons';
/**
* Interface that all init handlers must implement.
*/
export type CoreInitHandler = {
/**
* A name to identify the handler.
*/
name: string;
/**
* The highest priority is executed first. You should use values lower than MAX_RECOMMENDED_PRIORITY.
*/
priority?: number;
/**
* Set this to true when this process should be resolved before any following one.
*/
blocking?: boolean;
/**
* Function to execute during the init process.
*
* @return Promise resolved when done.
*/
load(): Promise<void>;
};
/*
* Provider for initialisation mechanisms.
*/
@Injectable()
export class CoreInitDelegate {
static DEFAULT_PRIORITY = 100; // Default priority for init processes.
static MAX_RECOMMENDED_PRIORITY = 600;
protected initProcesses = {};
protected logger: CoreLogger;
protected readiness;
constructor() {
this.logger = CoreLogger.getInstance('CoreInitDelegate');
}
/**
* Executes the registered init processes.
*
* Reserved for core use, do not call directly.
*/
executeInitProcesses(): void {
let ordered = [];
if (typeof this.readiness == 'undefined') {
this.initReadiness();
}
// Re-ordering by priority.
for (const name in this.initProcesses) {
ordered.push(this.initProcesses[name]);
}
ordered.sort((a, b) => {
return b.priority - a.priority;
});
ordered = ordered.map((data: CoreInitHandler) => {
return {
func: this.prepareProcess.bind(this, data),
blocking: !!data.blocking,
};
});
// Execute all the processes in order to solve dependencies.
CoreUtils.instance.executeOrderedPromises(ordered).finally(this.readiness.resolve);
}
/**
* Init the readiness promise.
*/
protected initReadiness(): void {
this.readiness = CoreUtils.instance.promiseDefer();
this.readiness.promise.then(() => this.readiness.resolved = true);
}
/**
* Instantly returns if the app is ready.
*
* @return Whether it's ready.
*/
isReady(): boolean {
return this.readiness.resolved;
}
/**
* Convenience function to return a function that executes the process.
*
* @param data The data of the process.
* @return Promise of the process.
*/
protected prepareProcess(data: CoreInitHandler): Promise<any> {
let promise;
this.logger.debug(`Executing init process '${data.name}'`);
try {
promise = data.load();
} catch (e) {
this.logger.error('Error while calling the init process \'' + data.name + '\'. ' + e);
return;
}
return promise;
}
/**
* Notifies when the app is ready. This returns a promise that is resolved when the app is initialised.
*
* @return Resolved when the app is initialised. Never rejected.
*/
ready(): Promise<any> {
if (typeof this.readiness == 'undefined') {
// Prevent race conditions if this is called before executeInitProcesses.
this.initReadiness();
}
return this.readiness.promise;
}
/**
* Registers an initialisation process.
*
* @description
* Init processes can be used to add initialisation logic to the app. Anything that should block the user interface while
* some processes are done should be an init process. It is recommended to use a priority lower than MAX_RECOMMENDED_PRIORITY
* to make sure that your process does not happen before some essential other core processes.
*
* An init process should never change state or prompt user interaction.
*
* This delegate cannot be used by site plugins.
*
* @param instance The instance of the handler.
*/
registerProcess(handler: CoreInitHandler): void {
if (typeof handler.priority == 'undefined') {
handler.priority = CoreInitDelegate.DEFAULT_PRIORITY;
}
if (typeof this.initProcesses[handler.name] != 'undefined') {
this.logger.log(`Process '${handler.name}' already registered.`);
return;
}
this.logger.log(`Registering process '${handler.name}'.`);
this.initProcesses[handler.name] = handler;
}
}
export class CoreInit extends makeSingleton(CoreInitDelegate) {}

View File

@ -0,0 +1,159 @@
// (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 { Injectable } from '@angular/core';
import { CoreLogger } from '@singletons/logger';
import { makeSingleton } from '@singletons/core.singletons';
/*
* "Utils" service with helper functions.
*/
@Injectable()
export class CoreUtilsProvider {
protected logger: CoreLogger;
constructor() {
this.logger = CoreLogger.getInstance('CoreUtilsProvider');
}
/**
* Similar to Promise.all, but if a promise fails this function's promise won't be rejected until ALL promises have finished.
*
* @param promises Promises.
* @return Promise resolved if all promises are resolved and rejected if at least 1 promise fails.
*/
allPromises(promises: Promise<any>[]): Promise<any> {
if (!promises || !promises.length) {
return Promise.resolve();
}
return new Promise((resolve, reject): void => {
const total = promises.length;
let count = 0;
let hasFailed = false;
let error;
promises.forEach((promise) => {
promise.catch((err) => {
hasFailed = true;
error = err;
}).finally(() => {
count++;
if (count === total) {
// All promises have finished, reject/resolve.
if (hasFailed) {
reject(error);
} else {
resolve();
}
}
});
});
});
}
/**
* Execute promises one depending on the previous.
*
* @param orderedPromisesData Functions to be executed.
* @return Promise resolved when all promises are resolved.
*/
executeOrderedPromises(orderedPromisesData: OrderedPromiseData[]): Promise<any> {
const promises = [];
let dependency = Promise.resolve();
// Execute all the processes in order.
for (const i in orderedPromisesData) {
const data = orderedPromisesData[i];
// Add the process to the dependency stack.
const promise = dependency.finally(() => {
try {
return data.function();
} catch (e) {
this.logger.error(e.message);
return;
}
});
promises.push(promise);
// If the new process is blocking, we set it as the dependency.
if (data.blocking) {
dependency = promise;
}
}
// Return when all promises are done.
return this.allPromises(promises);
}
/**
* Similar to AngularJS $q.defer().
*
* @return The deferred promise.
*/
promiseDefer<T>(): PromiseDefer<T> {
const deferred: PromiseDefer<T> = {};
deferred.promise = new Promise((resolve, reject): void => {
deferred.resolve = resolve;
deferred.reject = reject;
});
return deferred;
}
}
export class CoreUtils extends makeSingleton(CoreUtilsProvider) {}
/**
* Data for each entry of executeOrderedPromises.
*/
export type OrderedPromiseData = {
/**
* Function to execute.
*/
function: () => Promise<any>;
/**
* Whether the promise should block the following one.
*/
blocking?: boolean;
};
/**
* Deferred promise. It's similar to the result of $q.defer() in AngularJS.
*/
export type PromiseDefer<T> = {
/**
* The promise.
*/
promise?: Promise<T>;
/**
* Function to resolve the promise.
*
* @param value The resolve value.
*/
resolve?: (value?: T) => void;
/**
* Function to reject the promise.
*
* @param reason The reject param.
*/
reject?: (reason?: any) => void;
};

View File

@ -0,0 +1,42 @@
// (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 { Injector } from '@angular/core';
import { SplashScreen as SplashScreenPlugin } from '@ionic-native/splash-screen/ngx';
import { CoreSingletonsFactory, CoreInjectionToken, CoreSingletonClass } from '@classes/singletons-factory';
const factory = new CoreSingletonsFactory();
/**
* Set the injector that will be used to resolve instances in the singletons of this module.
*
* @param injector Module injector.
*/
export function setSingletonsInjector(injector: Injector): void {
factory.setInjector(injector);
}
/**
* Make a singleton for this module.
*
* @param injectionToken Injection token used to resolve the singleton instance. This is usually the service class if the
* provider was defined using a class or the string used in the `provide` key if it was defined using an object.
*/
export function makeSingleton<Service>(injectionToken: CoreInjectionToken<Service>): CoreSingletonClass<Service> {
return factory.makeSingleton(injectionToken);
}
export class SplashScreen extends makeSingleton(SplashScreenPlugin) {}

View File

@ -0,0 +1,90 @@
// (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 * as moment from 'moment';
import { environment } from '@/environments/environment';
/**
* Helper service to display messages in the console.
*
* @description
* This service is meant to improve log messages, adding a timestamp and a name to all log messages.
*
* In your class constructor, call getInstance to configure your class name:
* CoreLogger.getInstance('InitPage');
*
* Then you can call the log function you want to use in this logger instance.
*/
export class CoreLogger {
log: LogFunction;
info: LogFunction;
warn: LogFunction;
debug: LogFunction;
error: LogFunction;
/**
* Get a logger instance for a certain class, service or component.
*
* @param className Name to use in the messages.
* @return Instance.
*/
static getInstance(className: string): CoreLogger {
// Disable log on production.
if (environment.production) {
/* tslint:next-line no-console */
console.warn('Log is disabled in production app');
return {
log: () => {},
info: () => {},
warn: () => {},
debug: () => {},
error: () => {},
};
}
className = className || '';
/* tslint:disable no-console */
return {
log: CoreLogger.prepareLogFn(console.log.bind(console), className),
info: CoreLogger.prepareLogFn(console.info.bind(console), className),
warn: CoreLogger.prepareLogFn(console.warn.bind(console), className),
debug: CoreLogger.prepareLogFn(console.debug.bind(console), className),
error: CoreLogger.prepareLogFn(console.error.bind(console), className),
};
}
/**
* Prepare a logging function, concatenating the timestamp and class name to all messages.
*
* @param logFn Log function to use.
* @param className Name to use in the messages.
* @return Prepared function.
*/
private static prepareLogFn(logFn: LogFunction, className: string): LogFunction {
// Return our own function that will call the logging function with the treated message.
return (...args): void => {
const now = moment().format('l LTS');
args[0] = now + ' ' + className + ': ' + args[0]; // Prepend timestamp and className to the original message.
logFn.apply(null, args);
};
}
}
/**
* Log function type.
*/
type LogFunction = (...data: any[]) => void;

View File

@ -1,12 +1,6 @@
{
"extends": "tslint:recommended",
"rules": {
"align": {
"options": [
"parameters",
"statements"
]
},
"array-type": false,
"arrow-return-shorthand": true,
"curly": true,
@ -22,12 +16,6 @@
"app",
"camelCase"
],
"component-selector": [
true,
"element",
"app",
"kebab-case"
],
"eofline": true,
"import-blacklist": [
true,
@ -141,7 +129,9 @@
"template-no-negated-async": true,
"use-lifecycle-interface": true,
"use-pipe-transform-interface": true,
"object-literal-sort-keys": false
"object-literal-sort-keys": false,
"forin": false,
"triple-equals": false
},
"rulesDirectory": [
"codelyzer"