MOBILE-4653 chore: Fix singleton constructors and compiler

main
Pau Ferrer Ocaña 2024-11-11 14:43:09 +01:00
parent 43606fbfb1
commit 0a37db2151
17 changed files with 146 additions and 70 deletions

View File

@ -69,7 +69,7 @@ jobs:
cat circular-dependencies
lines=$(cat circular-dependencies | wc -l)
echo "Total circular dependencies: $lines"
test $lines -eq 130
test $lines -eq 90
- name: JavaScript code compatibility
run: |
npx check-es-compat www/*.js --polyfills="\{Array,String,TypedArray\}.prototype.at,Object.hasOwn"

View File

@ -79,23 +79,30 @@ import { Md5 } from 'ts-md5/dist/md5';
// Import core classes that can be useful for site plugins.
import { CoreSyncBaseProvider } from '@classes/base-sync';
import { CoreArray } from '@singletons/array';
import { CoreCache } from '@classes/cache';
import { CoreColors } from '@singletons/colors';
import { CoreCountries } from '@singletons/countries';
import { CoreDelegate } from '@classes/delegate';
import { CoreDirectivesRegistry } from '@singletons/directives-registry';
import { CoreDom } from '@singletons/dom';
import { CoreFileUtils } from '@singletons/file-utils';
import { CoreForms } from '@singletons/form';
import { CoreGeolocationError, CoreGeolocationErrorReason } from '@services/geolocation';
import { CoreKeyboard } from '@singletons/keyboard';
import { CoreMedia } from '@singletons/media';
import { CoreNetwork } from '@services/network';
import { CoreObject } from '@singletons/object';
import { CoreOpener } from '@singletons/opener';
import { CorePath } from '@singletons/path';
import { CorePromiseUtils } from '@singletons/promise-utils';
import { CoreSSO } from '@singletons/sso';
import { CoreText } from '@singletons/text';
import { CoreTime } from '@singletons/time';
import { CoreUrl } from '@singletons/url';
import { CoreUtils } from '@singletons/utils';
import { CoreWait } from '@singletons/wait';
import { CoreWindow } from '@singletons/window';
import { CoreCache } from '@classes/cache';
import { CoreDelegate } from '@classes/delegate';
import { CoreGeolocationError, CoreGeolocationErrorReason } from '@services/geolocation';
import { getCoreErrorsExportedObjects } from '@classes/errors/errors';
import { CoreNetwork } from '@services/network';
// Import all core modules that define components, directives and pipes.
import { CoreSharedModule } from '@/core/shared.module';
@ -312,19 +319,26 @@ export class CoreCompileProvider {
*/
instance['Network'] = CoreNetwork.instance;
instance['CoreNetwork'] = CoreNetwork.instance;
instance['CorePlatform'] = CorePlatform.instance;
instance['CoreSyncBaseProvider'] = CoreSyncBaseProvider;
instance['CoreArray'] = CoreArray;
instance['CoreColors'] = CoreColors;
instance['CoreCountries'] = CoreCountries;
instance['CoreDirectivesRegistry'] = CoreDirectivesRegistry;
instance['CoreDom'] = CoreDom;
instance['CoreFileUtils'] = CoreFileUtils;
instance['CoreForms'] = CoreForms;
instance['CoreKeyboard'] = CoreKeyboard;
instance['CoreMedia'] = CoreMedia;
instance['CoreObject'] = CoreObject;
instance['CoreOpener'] = CoreOpener;
instance['CorePath'] = CorePath;
instance['CorePlatform'] = CorePlatform.instance;
instance['CorePromiseUtils'] = CorePromiseUtils;
instance['CoreSSO'] = CoreSSO;
instance['CoreSyncBaseProvider'] = CoreSyncBaseProvider;
instance['CoreText'] = CoreText;
instance['CoreTime'] = CoreTime;
instance['CoreUrl'] = CoreUrl;
instance['CoreUtils'] = CoreUtils;
instance['CoreWait'] = CoreWait;
instance['CoreWindow'] = CoreWindow;
instance['CoreCache'] = CoreCache; // @deprecated since 4.4, plugins should use plain objects instead.

View File

@ -14,9 +14,16 @@
/**
* Helpers to interact with Browser APIs.
*
* This singleton is not necessary to be exported for site plugins.
*/
export class CoreBrowser {
// Avoid creating singleton instances.
private constructor() {
// Nothing to do.
}
/**
* Check whether the given cookie is set.
*
@ -34,9 +41,9 @@ export class CoreBrowser {
* @returns Whether the development setting is set.
*/
static hasDevelopmentSetting(name: string): boolean {
const setting = this.getDevelopmentSettingKey(name);
const setting = CoreBrowser.getDevelopmentSettingKey(name);
return this.hasCookie(setting) || this.hasLocalStorage(setting);
return CoreBrowser.hasCookie(setting) || CoreBrowser.hasLocalStorage(setting);
}
/**
@ -84,9 +91,9 @@ export class CoreBrowser {
* @returns Development setting value.
*/
static getDevelopmentSetting(name: string): string | null {
const setting = this.getDevelopmentSettingKey(name);
const setting = CoreBrowser.getDevelopmentSettingKey(name);
return this.getCookie(setting) ?? this.getLocalStorage(setting);
return CoreBrowser.getCookie(setting) ?? CoreBrowser.getLocalStorage(setting);
}
/**
@ -96,7 +103,7 @@ export class CoreBrowser {
* @param value Setting value.
*/
static setDevelopmentSetting(name: string, value: string): void {
const setting = this.getDevelopmentSettingKey(name);
const setting = CoreBrowser.getDevelopmentSettingKey(name);
document.cookie = `${setting}=${value};path=/`;
localStorage.setItem(setting, value);
@ -108,7 +115,7 @@ export class CoreBrowser {
* @param name Setting name.
*/
static clearDevelopmentSetting(name: string): void {
const setting = this.getDevelopmentSettingKey(name);
const setting = CoreBrowser.getDevelopmentSettingKey(name);
document.cookie = `${setting}=;path=/;expires=Thu, 01 Jan 1970 00:00:01 GMT`;
localStorage.removeItem(setting);

View File

@ -42,6 +42,11 @@ export enum CoreIonicColorNames {
*/
export class CoreColors {
// Avoid creating singleton instances.
private constructor() {
// Nothing to do.
}
/**
* Returns better contrast color.
*
@ -99,7 +104,7 @@ export class CoreColors {
}
const hex = [0,1,2].map(
(idx) => this.componentToHex(rgba[idx]),
(idx) => CoreColors.componentToHex(rgba[idx]),
).join('');
return '#' + hex;

View File

@ -32,9 +32,9 @@ export class CoreDirectivesRegistry {
* @param instance Directive instance.
*/
static register(element: Element, instance: unknown): void {
const list = this.instances.get(element) ?? [];
const list = CoreDirectivesRegistry.instances.get(element) ?? [];
list.push(instance);
this.instances.set(element, list);
CoreDirectivesRegistry.instances.set(element, list);
}
/**
@ -45,7 +45,7 @@ export class CoreDirectivesRegistry {
* @returns Directive instance.
*/
static resolve<T>(element?: Element | null, directiveClass?: DirectiveConstructor<T>): T | null {
const list = (element && this.instances.get(element) as T[]) ?? [];
const list = (element && CoreDirectivesRegistry.instances.get(element) as T[]) ?? [];
return list.find(instance => !directiveClass || instance instanceof directiveClass) ?? null;
}
@ -58,7 +58,7 @@ export class CoreDirectivesRegistry {
* @returns Directive instances.
*/
static resolveAll<T>(element?: Element | null, directiveClass?: DirectiveConstructor<T>): T[] {
const list = (element && this.instances.get(element) as T[]) ?? [];
const list = (element && CoreDirectivesRegistry.instances.get(element) as T[]) ?? [];
return list.filter(instance => !directiveClass || instance instanceof directiveClass) ?? [];
}
@ -71,7 +71,7 @@ export class CoreDirectivesRegistry {
* @returns Directive instance.
*/
static require<T>(element: Element, directiveClass?: DirectiveConstructor<T>): T {
const instance = this.resolve(element, directiveClass);
const instance = CoreDirectivesRegistry.resolve(element, directiveClass);
if (!instance) {
throw new Error('Couldn\'t resolve directive instance');
@ -91,9 +91,9 @@ export class CoreDirectivesRegistry {
element: Element | null,
directiveClass?: DirectiveConstructor<T>,
): Promise<void> {
const instance = this.resolve(element, directiveClass);
const instance = CoreDirectivesRegistry.resolve(element, directiveClass);
if (!instance) {
this.logger.error('No instance registered for element ' + directiveClass, element);
CoreDirectivesRegistry.logger.error('No instance registered for element ' + directiveClass, element);
return;
}
@ -129,7 +129,7 @@ export class CoreDirectivesRegistry {
}
await Promise.all(elements.map(async element => {
const instances = this.resolveAll<T>(element, directiveClass);
const instances = CoreDirectivesRegistry.resolveAll<T>(element, directiveClass);
await Promise.all(instances.map(instance => instance.ready()));
}));
@ -139,7 +139,7 @@ export class CoreDirectivesRegistry {
// Check if there are new elements now that the found elements are ready (there could be nested elements).
if (elements.length !== findElements().length) {
await this.waitDirectivesReady(element, selector, directiveClass);
await CoreDirectivesRegistry.waitDirectivesReady(element, selector, directiveClass);
}
}
@ -174,7 +174,7 @@ export class CoreDirectivesRegistry {
allElements = allElements.concat(elements);
await Promise.all(elements.map(async element => {
const instances = this.resolveAll<AsyncDirective>(element, directive.class);
const instances = CoreDirectivesRegistry.resolveAll<AsyncDirective>(element, directive.class);
await Promise.all(instances.map(instance => instance.ready()));
}));
@ -191,7 +191,7 @@ export class CoreDirectivesRegistry {
}, <Element[]> []);
if (allElements.length !== elementsAfterReady.length) {
await this.waitMultipleDirectivesReady(element, directives);
await CoreDirectivesRegistry.waitMultipleDirectivesReady(element, directives);
}
}

View File

@ -512,7 +512,7 @@ export class CoreDom {
return resolve();
}
unsubscribe = this.watchElementInViewport(element, intersectionRatio, inViewport => {
unsubscribe = CoreDom.watchElementInViewport(element, intersectionRatio, inViewport => {
if (!inViewport) {
return;
}
@ -632,7 +632,7 @@ export class CoreDom {
const value = styles.getPropertyValue(property);
if (property === 'font-size') {
if (this.fontSizeZoom === null) {
if (CoreDom.fontSizeZoom === null) {
const baseFontSize = 20;
const span = document.createElement('span');
span.style.opacity = '0';
@ -640,13 +640,13 @@ export class CoreDom {
document.body.append(span);
this.fontSizeZoom = baseFontSize / Number(getComputedStyle(span).fontSize.slice(0, -2));
CoreDom.fontSizeZoom = baseFontSize / Number(getComputedStyle(span).fontSize.slice(0, -2));
span.remove();
}
if (this.fontSizeZoom !== 1) {
return `calc(${this.fontSizeZoom} * ${value})`;
if (CoreDom.fontSizeZoom !== 1) {
return `calc(${CoreDom.fontSizeZoom} * ${value})`;
}
}

View File

@ -11,24 +11,26 @@
// 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';
/**
* Service that stores error logs in memory.
*/
@Injectable({ providedIn: 'root' })
export class CoreErrorLogsService {
export class CoreErrorLogs {
protected errorLogs: CoreSettingsErrorLog[] = [];
protected static errorLogs: CoreSettingsErrorLog[] = [];
// Avoid creating singleton instances.
private constructor() {
// Nothing to do.
}
/**
* Retrieve error logs displayed in the DOM.
*
* @returns Error logs
*/
getErrorLogs(): CoreSettingsErrorLog[] {
return this.errorLogs;
static getErrorLogs(): CoreSettingsErrorLog[] {
return CoreErrorLogs.errorLogs;
}
/**
@ -36,14 +38,12 @@ export class CoreErrorLogsService {
*
* @param error Error.
*/
addErrorLog(error: CoreSettingsErrorLog): void {
this.errorLogs.push(error);
static addErrorLog(error: CoreSettingsErrorLog): void {
CoreErrorLogs.errorLogs.push(error);
}
}
export const CoreErrorLogs = makeSingleton(CoreErrorLogsService);
export type CoreSettingsErrorLog = {
data?: unknown;
message: string;

View File

@ -122,6 +122,11 @@ export class CoreEvents {
protected static observables: { [eventName: string]: Subject<unknown> } = {};
protected static uniqueEvents: { [eventName: string]: {data: unknown} } = {};
// Avoid creating singleton instances.
private constructor() {
// Nothing to do.
}
/**
* Listen for a certain event. To stop listening to the event:
* let observer = eventsProvider.on('something', myCallBack);
@ -140,8 +145,8 @@ export class CoreEvents {
): CoreEventObserver {
// If it's a unique event and has been triggered already, call the callBack.
// We don't need to create an observer because the event won't be triggered again.
if (this.uniqueEvents[eventName]) {
callBack(this.uniqueEvents[eventName].data as CoreEventData<Event, Fallback> & CoreEventSiteData);
if (CoreEvents.uniqueEvents[eventName]) {
callBack(CoreEvents.uniqueEvents[eventName].data as CoreEventData<Event, Fallback> & CoreEventSiteData);
// Return a fake observer to prevent errors.
return {
@ -151,14 +156,14 @@ export class CoreEvents {
};
}
this.logger.debug(`New observer listening to event '${eventName}'`);
CoreEvents.logger.debug(`New observer listening to event '${eventName}'`);
if (this.observables[eventName] === undefined) {
if (CoreEvents.observables[eventName] === undefined) {
// No observable for this event, create a new one.
this.observables[eventName] = new Subject();
CoreEvents.observables[eventName] = new Subject();
}
const subscription = this.observables[eventName].subscribe(
const subscription = CoreEvents.observables[eventName].subscribe(
(value: CoreEventData<Event, Fallback> & CoreEventSiteData) => {
if (!siteId || value.siteId == siteId) {
callBack(value);
@ -169,7 +174,7 @@ export class CoreEvents {
// Create and return a CoreEventObserver.
return {
off: (): void => {
this.logger.debug(`Stop listening to event '${eventName}'`);
CoreEvents.logger.debug(`Stop listening to event '${eventName}'`);
subscription.unsubscribe();
},
};
@ -211,7 +216,7 @@ export class CoreEvents {
* @returns Observer to stop listening.
*/
static onMultiple<T = unknown>(eventNames: string[], callBack: (value: T) => void, siteId?: string): CoreEventObserver {
const observers = eventNames.map((name) => this.on<T>(name, callBack, siteId));
const observers = eventNames.map((name) => CoreEvents.on<T>(name, callBack, siteId));
// Create and return a CoreEventObserver.
return {
@ -235,12 +240,12 @@ export class CoreEvents {
data?: CoreEventData<Event, Fallback>,
siteId?: string,
): void {
this.logger.debug(`Event '${eventName}' triggered.`);
if (this.observables[eventName]) {
CoreEvents.logger.debug(`Event '${eventName}' triggered.`);
if (CoreEvents.observables[eventName]) {
if (siteId) {
Object.assign(data || {}, { siteId });
}
this.observables[eventName].next(data || {});
CoreEvents.observables[eventName].next(data || {});
}
}
@ -256,23 +261,23 @@ export class CoreEvents {
data: CoreEventData<Event, Fallback>,
siteId?: string,
): void {
if (this.uniqueEvents[eventName]) {
this.logger.debug(`Unique event '${eventName}' ignored because it was already triggered.`);
if (CoreEvents.uniqueEvents[eventName]) {
CoreEvents.logger.debug(`Unique event '${eventName}' ignored because it was already triggered.`);
} else {
this.logger.debug(`Unique event '${eventName}' triggered.`);
CoreEvents.logger.debug(`Unique event '${eventName}' triggered.`);
if (siteId) {
Object.assign(data || {}, { siteId });
}
// Store the data so it can be passed to observers that register from now on.
this.uniqueEvents[eventName] = {
CoreEvents.uniqueEvents[eventName] = {
data,
};
// Now pass the data to observers.
if (this.observables[eventName]) {
this.observables[eventName].next(data);
if (CoreEvents.observables[eventName]) {
CoreEvents.observables[eventName].next(data);
}
}
}
@ -283,7 +288,7 @@ export class CoreEvents {
* @param eventName Event name.
*/
static waitUntil(eventName: string): Promise<void> {
return new Promise(resolve => this.once(eventName, () => resolve()));
return new Promise(resolve => CoreEvents.once(eventName, () => resolve()));
}
}

View File

@ -22,6 +22,11 @@ export class CoreForms {
private static formIds: Record<string, number> = {};
// Avoid creating singleton instances.
private constructor() {
// Nothing to do.
}
/**
* Get the data from a form. It will only collect elements that have a name.
*
@ -102,9 +107,9 @@ export class CoreForms {
* @returns Unique id.
*/
static uniqueId(name: string): string {
const count = this.formIds[name] ?? 0;
const count = CoreForms.formIds[name] ?? 0;
return `${name}-${this.formIds[name] = count + 1}`;
return `${name}-${CoreForms.formIds[name] = count + 1}`;
}
}

View File

@ -29,6 +29,11 @@ export class CoreHTMLClasses {
protected static readonly MOODLEAPP_VERSION_PREFIX = 'moodleapp-';
protected static readonly MOODLE_SITE_THEME_PREFIX = 'theme-site-';
// Avoid creating singleton instances.
private constructor() {
// Nothing to do.
}
/**
* Initialize HTML classes.
*/
@ -101,10 +106,10 @@ export class CoreHTMLClasses {
*/
static addSiteClasses(siteInfo: CoreSiteInfo | CoreSiteInfoResponse): void {
// Add version classes to html tag.
this.removeSiteClasses();
CoreHTMLClasses.removeSiteClasses();
this.addVersionClass(CoreHTMLClasses.MOODLE_VERSION_PREFIX, CoreSites.getReleaseNumber(siteInfo.release || ''));
this.addSiteUrlClass(siteInfo.siteurl);
CoreHTMLClasses.addVersionClass(CoreHTMLClasses.MOODLE_VERSION_PREFIX, CoreSites.getReleaseNumber(siteInfo.release || ''));
CoreHTMLClasses.addSiteUrlClass(siteInfo.siteurl);
if (siteInfo.theme) {
CoreHTMLClasses.toggleModeClass(CoreHTMLClasses.MOODLE_SITE_THEME_PREFIX + siteInfo.theme, true);
@ -116,7 +121,7 @@ export class CoreHTMLClasses {
*/
static removeSiteClasses(): void {
// Remove version classes from html tag.
this.removeModeClasses(
CoreHTMLClasses.removeModeClasses(
[
CoreHTMLClasses.MOODLE_VERSION_PREFIX,
CoreHTMLClasses.MOODLE_SITE_URL_PREFIX,
@ -161,7 +166,7 @@ export class CoreHTMLClasses {
* Convenience function to add site url to html classes.
*/
static addSiteUrlClass(siteUrl: string): void {
const className = this.urlToClassName(siteUrl);
const className = CoreHTMLClasses.urlToClassName(siteUrl);
CoreHTMLClasses.toggleModeClass(CoreHTMLClasses.MOODLE_SITE_URL_PREFIX + className, true);
}

View File

@ -28,6 +28,11 @@ export class CoreIcons {
protected static logger = CoreLogger.getInstance('CoreIcons');
// Avoid creating singleton instances.
private constructor() {
// Nothing to do.
}
/**
* Add custom icons to Ionicons.
*/
@ -47,7 +52,7 @@ export class CoreIcons {
if (CoreIcons.ALIASES[icon]) {
if (isAppIcon) {
this.logger.error(`Icon ${icon} is an alias of ${CoreIcons.ALIASES[icon]}, please use the new name.`);
CoreIcons.logger.error(`Icon ${icon} is an alias of ${CoreIcons.ALIASES[icon]}, please use the new name.`);
}
return { newLibrary, fileName: CoreIcons.ALIASES[icon] };
@ -73,7 +78,7 @@ export class CoreIcons {
CoreIcons.CUSTOM_ICONS[icon] === undefined &&
CoreIcons.CUSTOM_ICONS[CoreIcons.prefixIconName(font, library, icon)] === undefined
) {
this.logger.error(`Icon ${icon} not found`);
CoreIcons.logger.error(`Icon ${icon} not found`);
}
}

View File

@ -25,6 +25,11 @@ export class CoreKeyboard {
protected static keyboardOpening = false;
protected static keyboardClosing = false;
// Avoid creating singleton instances.
private constructor() {
// Nothing to do.
}
/**
* Closes the keyboard.
*/
@ -51,7 +56,7 @@ export class CoreKeyboard {
*/
static onKeyboardShow(keyboardHeight: number): void {
document.body.classList.add('keyboard-is-open');
this.setKeyboardShown(true);
CoreKeyboard.setKeyboardShown(true);
// Error on iOS calculating size.
// More info: https://github.com/ionic-team/ionic-plugin-keyboard/issues/276 .
CoreEvents.trigger(CoreEvents.KEYBOARD_CHANGE, keyboardHeight);
@ -62,7 +67,7 @@ export class CoreKeyboard {
*/
static onKeyboardHide(): void {
document.body.classList.remove('keyboard-is-open');
this.setKeyboardShown(false);
CoreKeyboard.setKeyboardShown(false);
CoreEvents.trigger(CoreEvents.KEYBOARD_CHANGE, 0);
}

View File

@ -17,6 +17,11 @@
*/
export class CoreMath {
// Avoid creating singleton instances.
private constructor() {
// Nothing to do.
}
/**
* Clamp a value between a minimum and a maximum.
*

View File

@ -26,6 +26,11 @@ type Subscribable<T> = EventEmitter<T> | Observable<T>;
*/
export class CoreSubscriptions {
// Avoid creating singleton instances.
private constructor() {
// Nothing to do.
}
/**
* Listen once to a subscribable object.
*

View File

@ -23,6 +23,11 @@ import { SwiperOptions } from 'swiper/types';
*/
export class CoreSwiper {
// Avoid creating singleton instances.
private constructor() {
// Nothing to do.
}
/**
* Initialize a Swiper instance.
* It will return swiper instance if current is not set or destroyed and new is set and not destroyed.

View File

@ -78,6 +78,11 @@ export class CoreTime {
'13.0': 'Etc/GMT-13',
};
// Avoid creating singleton instances.
private constructor() {
// Nothing to do.
}
/**
* Returns years, months, days, hours, minutes and seconds in a human readable format.
*

View File

@ -20,6 +20,11 @@ import { CorePlatform } from '@services/platform';
*/
export class CoreWait {
// Avoid creating singleton instances.
private constructor() {
// Nothing to do.
}
/**
* Wait until the next tick.
*