Merge pull request #3428 from dpalou/MOBILE-3817

Mobile 3817
main
Pau Ferrer Ocaña 2022-10-31 11:38:58 +01:00 committed by GitHub
commit 4e57e31284
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 94 additions and 29 deletions

View File

@ -711,10 +711,10 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
* @param newCourses New courses. * @param newCourses New courses.
* @return Whether it has meaningful changes. * @return Whether it has meaningful changes.
*/ */
protected coursesHaveMeaningfulChanges( protected async coursesHaveMeaningfulChanges(
previousCourses: CoreEnrolledCourseDataWithExtraInfoAndOptions[], previousCourses: CoreEnrolledCourseDataWithExtraInfoAndOptions[],
newCourses: CoreEnrolledCourseDataWithExtraInfoAndOptions[], newCourses: CoreEnrolledCourseDataWithExtraInfoAndOptions[],
): boolean { ): Promise<boolean> {
if (previousCourses.length !== newCourses.length) { if (previousCourses.length !== newCourses.length) {
return true; return true;
} }
@ -746,10 +746,10 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
* @param newCourses New courses. * @param newCourses New courses.
* @return Whether it has meaningful changes. * @return Whether it has meaningful changes.
*/ */
protected customFilterCoursesHaveMeaningfulChanges( protected async customFilterCoursesHaveMeaningfulChanges(
previousCourses: CoreCourseSummaryData[], previousCourses: CoreCourseSummaryData[],
newCourses: CoreCourseSummaryData[], newCourses: CoreCourseSummaryData[],
): boolean { ): Promise<boolean> {
if (previousCourses.length !== newCourses.length) { if (previousCourses.length !== newCourses.length) {
return true; return true;
} }

View File

@ -29,6 +29,7 @@ export class PageLoadWatcher {
protected ongoingRequests = 0; protected ongoingRequests = 0;
protected components = new Set<AsyncComponent>(); protected components = new Set<AsyncComponent>();
protected loadedTimeout?: number; protected loadedTimeout?: number;
protected hasChangesPromises: Promise<boolean>[] = [];
constructor( constructor(
protected loadsManager: PageLoadsManager, protected loadsManager: PageLoadsManager,
@ -97,7 +98,7 @@ export class PageLoadWatcher {
*/ */
watchRequest<T>( watchRequest<T>(
observable: WSObservable<T>, observable: WSObservable<T>,
hasMeaningfulChanges?: (previousValue: T, newValue: T) => boolean, hasMeaningfulChanges?: (previousValue: T, newValue: T) => Promise<boolean>,
): Promise<T> { ): Promise<T> {
const promisedValue = new CorePromisedValue<T>(); const promisedValue = new CorePromisedValue<T>();
let subscription: Subscription | null = null; let subscription: Subscription | null = null;
@ -124,9 +125,11 @@ export class PageLoadWatcher {
} }
// Second value, it means data was updated in background. Compare data. // Second value, it means data was updated in background. Compare data.
if (hasMeaningfulChanges?.(firstValue, value)) { if (!hasMeaningfulChanges) {
this.hasChanges = true; return;
} }
this.hasChangesPromises.push(CoreUtils.ignoreErrors(hasMeaningfulChanges(firstValue, value), false));
}, },
error: (error) => { error: (error) => {
promisedValue.reject(error); promisedValue.reject(error);
@ -150,8 +153,11 @@ export class PageLoadWatcher {
// It seems load has finished. Wait to make sure no new component has been rendered and started loading. // It seems load has finished. Wait to make sure no new component has been rendered and started loading.
// If a new component or a new request starts the timeout will be cancelled, no need to double check it. // If a new component or a new request starts the timeout will be cancelled, no need to double check it.
clearTimeout(this.loadedTimeout); clearTimeout(this.loadedTimeout);
this.loadedTimeout = window.setTimeout(() => { this.loadedTimeout = window.setTimeout(async () => {
// Loading finished. // Loading finished. Calculate has changes.
const values = await Promise.all(this.hasChangesPromises);
this.hasChanges = this.hasChanges || values.includes(true);
this.loadsManager.onPageLoaded(this); this.loadsManager.onPageLoaded(this);
}, 100); }, 100);
} }

View File

@ -125,7 +125,7 @@ export class CoreSite {
protected lastAutoLogin = 0; protected lastAutoLogin = 0;
protected offlineDisabled = false; protected offlineDisabled = false;
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
protected ongoingRequests: { [cacheId: string]: WSObservable<any> } = {}; protected ongoingRequests: Record<string, Record<OngoingRequestType, WSObservable<any> | undefined>> = {};
protected requestQueue: RequestQueueItem[] = []; protected requestQueue: RequestQueueItem[] = [];
protected requestQueueTimeout: number | null = null; protected requestQueueTimeout: number | null = null;
protected tokenPluginFileWorks?: boolean; protected tokenPluginFileWorks?: boolean;
@ -636,9 +636,10 @@ export class CoreSite {
const cacheId = this.getCacheId(method, data); const cacheId = this.getCacheId(method, data);
// Check for an ongoing identical request if we're not ignoring cache. // Check for an ongoing identical request.
if (preSets.getFromCache && this.ongoingRequests[cacheId] !== undefined) { const ongoingRequest = this.getOngoingRequest<T>(cacheId, preSets);
return this.ongoingRequests[cacheId]; if (ongoingRequest) {
return ongoingRequest;
} }
const observable = this.performRequest<T>(method, data, preSets, wsPreSets).pipe( const observable = this.performRequest<T>(method, data, preSets, wsPreSets).pipe(
@ -646,18 +647,68 @@ export class CoreSite {
map((data) => CoreUtils.clone(data)), map((data) => CoreUtils.clone(data)),
); );
this.ongoingRequests[cacheId] = observable; this.setOngoingRequest(cacheId, preSets, observable);
return observable.pipe( return observable.pipe(
finalize(() => { finalize(() => {
// Clear the ongoing request unless it has changed (e.g. a new request that ignores cache). this.clearOngoingRequest(cacheId, preSets, observable);
if (this.ongoingRequests[cacheId] === observable) {
delete this.ongoingRequests[cacheId];
}
}), }),
); );
} }
/**
* Get an ongoing request if there's one already.
*
* @param cacheId Cache ID.
* @param preSets Presets.
* @return Ongoing request if it exists.
*/
protected getOngoingRequest<T = unknown>(cacheId: string, preSets: CoreSiteWSPreSets): WSObservable<T> | undefined {
if (preSets.updateInBackground) {
return this.ongoingRequests[cacheId]?.[OngoingRequestType.UPDATE_IN_BACKGROUND];
} else if (preSets.getFromCache) { // Only reuse ongoing request when using cache.
return this.ongoingRequests[cacheId]?.[OngoingRequestType.STANDARD];
}
}
/**
* Store an ongoing request in memory.
*
* @param cacheId Cache ID.
* @param preSets Presets.
* @param request Request to store.
*/
protected setOngoingRequest<T = unknown>(cacheId: string, preSets: CoreSiteWSPreSets, request: WSObservable<T>): void {
this.ongoingRequests[cacheId] = this.ongoingRequests[cacheId] ?? {};
if (preSets.updateInBackground) {
this.ongoingRequests[cacheId][OngoingRequestType.UPDATE_IN_BACKGROUND] = request;
} else {
this.ongoingRequests[cacheId][OngoingRequestType.STANDARD] = request;
}
}
/**
* Clear the ongoing request unless it has changed (e.g. a new request that ignores cache).
*
* @param cacheId Cache ID.
* @param preSets Presets.
* @param request Current request.
*/
protected clearOngoingRequest<T = unknown>(cacheId: string, preSets: CoreSiteWSPreSets, request: WSObservable<T>): void {
this.ongoingRequests[cacheId] = this.ongoingRequests[cacheId] ?? {};
if (preSets.updateInBackground) {
if (this.ongoingRequests[cacheId][OngoingRequestType.UPDATE_IN_BACKGROUND] === request) {
delete this.ongoingRequests[cacheId][OngoingRequestType.UPDATE_IN_BACKGROUND];
}
} else {
if (this.ongoingRequests[cacheId][OngoingRequestType.STANDARD] === request) {
delete this.ongoingRequests[cacheId][OngoingRequestType.STANDARD];
}
}
}
/** /**
* Perform a request, getting the response either from cache or WebService. * Perform a request, getting the response either from cache or WebService.
* *
@ -1640,8 +1691,11 @@ export class CoreSite {
} }
// Check for an ongoing identical request if we're not ignoring cache. // Check for an ongoing identical request if we're not ignoring cache.
if (cachePreSets.getFromCache && this.ongoingRequests[cacheId] !== undefined) {
return await firstValueFrom(this.ongoingRequests[cacheId]); // Check for an ongoing identical request.
const ongoingRequest = this.getOngoingRequest<CoreSitePublicConfigResponse>(cacheId, cachePreSets);
if (ongoingRequest) {
return firstValueFrom(ongoingRequest);
} }
const subject = new Subject<CoreSitePublicConfigResponse>(); const subject = new Subject<CoreSitePublicConfigResponse>();
@ -1649,14 +1703,11 @@ export class CoreSite {
// Return a clone of the original object, this may prevent errors if in the callback the object is modified. // Return a clone of the original object, this may prevent errors if in the callback the object is modified.
map((data) => CoreUtils.clone(data)), map((data) => CoreUtils.clone(data)),
finalize(() => { finalize(() => {
// Clear the ongoing request unless it has changed (e.g. a new request that ignores cache). this.clearOngoingRequest(cacheId, cachePreSets, observable);
if (this.ongoingRequests[cacheId] === observable) {
delete this.ongoingRequests[cacheId];
}
}), }),
); );
this.ongoingRequests[cacheId] = observable; this.setOngoingRequest(cacheId, cachePreSets, observable);
this.getFromCache<CoreSitePublicConfigResponse>(method, {}, cachePreSets, false) this.getFromCache<CoreSitePublicConfigResponse>(method, {}, cachePreSets, false)
.then(cachedData => cachedData.response) .then(cachedData => cachedData.response)
@ -1916,16 +1967,16 @@ export class CoreSite {
// Return the requested setting. // Return the requested setting.
for (const x in config.settings) { for (const x in config.settings) {
if (config.settings[x].name == name) { if (config.settings[x].name == name) {
return config.settings[x].value; return String(config.settings[x].value);
} }
} }
throw new CoreError('Site config not found: ' + name); throw new CoreError('Site config not found: ' + name);
} else { } else {
// Return all settings in the same array. // Return all settings in the same array.
const settings = {}; const settings: CoreSiteConfig = {};
config.settings.forEach((setting) => { config.settings.forEach((setting) => {
settings[setting.name] = setting.value; settings[setting.name] = String(setting.value);
}); });
return settings; return settings;
@ -2686,7 +2737,7 @@ export enum CoreSiteInfoUserHomepage {
export type CoreSiteConfigResponse = { export type CoreSiteConfigResponse = {
settings: { // Settings. settings: { // Settings.
name: string; // The name of the setting. name: string; // The name of the setting.
value: string; // The value of the setting. value: string | number; // The value of the setting.
}[]; }[];
warnings?: CoreWSExternalWarning[]; warnings?: CoreWSExternalWarning[];
}; };
@ -2804,3 +2855,11 @@ type WSCachedError = {
* Otherwise, it will only return 1 value, either coming from cache or from the server. After this, it will complete. * Otherwise, it will only return 1 value, either coming from cache or from the server. After this, it will complete.
*/ */
export type WSObservable<T> = Observable<T>; export type WSObservable<T> = Observable<T>;
/**
* Type of ongoing requests stored in memory to avoid duplicating them.
*/
enum OngoingRequestType {
STANDARD = 0,
UPDATE_IN_BACKGROUND = 1,
}