MOBILE-3643 forum: Infer event payloads

main
Noel De Martin 2021-03-01 13:40:17 +01:00
parent 205fb9e7cb
commit a695f9cb66
7 changed files with 139 additions and 28 deletions

View File

@ -22,6 +22,8 @@ import {
AddonModForumProvider,
AddonModForumSortOrder,
AddonModForumDiscussion,
AddonModForumNewDiscussionData,
AddonModForumReplyDiscussionData,
} from '@addons/mod/forum/services/forum.service';
import { AddonModForumOffline, AddonModForumOfflineDiscussion } from '@addons/mod/forum/services/offline.service';
import { ModalController, PopoverController, Translate } from '@singletons';
@ -127,7 +129,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
AddonModForumProvider.REPLY_DISCUSSION_EVENT,
this.eventReceived.bind(this, false),
);
this.changeDiscObserver = CoreEvents.on(AddonModForumProvider.CHANGE_DISCUSSION_EVENT, (data: any) => {
this.changeDiscObserver = CoreEvents.on(AddonModForumProvider.CHANGE_DISCUSSION_EVENT, data => {
if ((this.forum && this.forum.id === data.forumId) || data.cmId === this.module!.id) {
AddonModForum.instance.invalidateDiscussionsList(this.forum!.id).finally(() => {
if (data.discussionId) {
@ -152,7 +154,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
}
if (typeof data.deleted != 'undefined' && data.deleted) {
if (data.post.parentid == 0 && CoreScreen.instance.isTablet && !this.discussions.empty) {
if (data.post?.parentid == 0 && CoreScreen.instance.isTablet && !this.discussions.empty) {
// Discussion deleted, clear details page.
this.discussions.select(this.discussions[0]);
}
@ -275,7 +277,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
if (updated) {
// Sync successful, send event.
CoreEvents.trigger<AddonModForumManualSyncData>(AddonModForumSyncProvider.MANUAL_SYNCED, {
CoreEvents.trigger(AddonModForumSyncProvider.MANUAL_SYNCED, {
forumId: forum.id,
userId: CoreSites.instance.getCurrentSiteUserId(),
source: 'index',
@ -553,18 +555,22 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
* @param isNewDiscussion Whether it's a new discussion event.
* @param data Event data.
*/
protected eventReceived(isNewDiscussion: boolean, data: any): void {
protected eventReceived(
isNewDiscussion: boolean,
data: AddonModForumNewDiscussionData | AddonModForumReplyDiscussionData,
): void {
if ((this.forum && this.forum.id === data.forumId) || data.cmId === this.module?.id) {
this.showLoadingAndRefresh(false).finally(() => {
// If it's a new discussion in tablet mode, try to open it.
if (isNewDiscussion && CoreScreen.instance.isTablet) {
const newDiscussionData = data as AddonModForumNewDiscussionData;
const discussion = this.discussions.items.find(disc => {
if (this.discussions.isOfflineDiscussion(disc)) {
return disc.timecreated === data.discTimecreated;
return disc.timecreated === newDiscussionData.discTimecreated;
}
if (this.discussions.isOnlineDiscussion(disc)) {
return CoreArray.contains(data.discussionIds, disc.discussion);
return CoreArray.contains(newDiscussionData.discussionIds ?? [], disc.discussion);
}
return false;

View File

@ -37,7 +37,7 @@ import {
} from '../../services/forum.service';
import { AddonModForumHelper } from '../../services/helper.service';
import { AddonModForumOffline } from '../../services/offline.service';
import { AddonModForumManualSyncData, AddonModForumSync, AddonModForumSyncProvider } from '../../services/sync.service';
import { AddonModForumSync, AddonModForumSyncProvider } from '../../services/sync.service';
type SortType = 'flat-newest' | 'flat-oldest' | 'nested';
@ -170,7 +170,7 @@ export class AddonModForumDiscussionPage implements OnInit, AfterViewInit, OnDes
}
// Refresh data if this discussion is synchronized automatically.
this.syncObserver = CoreEvents.on(AddonModForumSyncProvider.AUTO_SYNCED, (data: any) => {
this.syncObserver = CoreEvents.on(AddonModForumSyncProvider.AUTO_SYNCED, data => {
if (data.forumId == this.forumId && this.discussionId == data.discussionId
&& data.userId == CoreSites.instance.getCurrentSiteUserId()) {
// Refresh the data.
@ -180,7 +180,7 @@ export class AddonModForumDiscussionPage implements OnInit, AfterViewInit, OnDes
}, CoreSites.instance.getCurrentSiteId());
// Refresh data if this forum discussion is synchronized from discussions list.
this.syncManualObserver = CoreEvents.on(AddonModForumSyncProvider.MANUAL_SYNCED, (data: AddonModForumManualSyncData) => {
this.syncManualObserver = CoreEvents.on(AddonModForumSyncProvider.MANUAL_SYNCED, data => {
if (data.source != 'discussion' && data.forumId == this.forumId &&
data.userId == CoreSites.instance.getCurrentSiteUserId()) {
// Refresh the data.
@ -196,7 +196,7 @@ export class AddonModForumDiscussionPage implements OnInit, AfterViewInit, OnDes
// @todo Listen for offline ratings saved and synced.
this.changeDiscObserver = CoreEvents.on(AddonModForumProvider.CHANGE_DISCUSSION_EVENT, (data: any) => {
this.changeDiscObserver = CoreEvents.on(AddonModForumProvider.CHANGE_DISCUSSION_EVENT, data => {
if ((this.forumId && this.forumId === data.forumId) || data.cmId === this.cmId) {
AddonModForum.instance.invalidateDiscussionsList(this.forumId).finally(() => {
if (typeof data.locked != 'undefined') {
@ -210,7 +210,7 @@ export class AddonModForumDiscussionPage implements OnInit, AfterViewInit, OnDes
}
if (typeof data.deleted != 'undefined' && data.deleted) {
if (!data.post.parentid) {
if (!data.post?.parentid) {
// @todo
// if (this.svComponent && this.svComponent.isOn()) {
// this.svComponent.emptyDetails();
@ -545,7 +545,7 @@ export class AddonModForumDiscussionPage implements OnInit, AfterViewInit, OnDes
if (result && result.updated) {
// Sync successful, send event.
CoreEvents.trigger<AddonModForumManualSyncData>(AddonModForumSyncProvider.MANUAL_SYNCED, {
CoreEvents.trigger(AddonModForumSyncProvider.MANUAL_SYNCED, {
forumId: this.forumId,
userId: CoreSites.instance.getCurrentSiteUserId(),
source: 'discussion',

View File

@ -120,7 +120,7 @@ export class AddonModForumNewDiscussionPage implements OnInit, OnDestroy, CanLea
}
// Refresh data if this discussion is synchronized automatically.
this.syncObserver = CoreEvents.on(AddonModForumSyncProvider.AUTO_SYNCED, (data: any) => {
this.syncObserver = CoreEvents.on(AddonModForumSyncProvider.AUTO_SYNCED, data => {
if (data.forumId == this.forumId && data.userId == CoreSites.instance.getCurrentSiteUserId()) {
CoreDomUtils.instance.showAlertTranslated('core.notice', 'core.contenteditingsynced');
this.returnToDiscussions();

View File

@ -30,6 +30,22 @@ import { AddonModForumOffline, AddonModForumOfflineDiscussion, AddonModForumRepl
const ROOT_CACHE_KEY = 'mmaModForum:';
declare module '@singletons/events' {
/**
* Augment CoreEventsData interface with events specific to this service.
*
* @see https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation
*/
export interface CoreEventsData {
[AddonModForumProvider.NEW_DISCUSSION_EVENT]: AddonModForumNewDiscussionData;
[AddonModForumProvider.REPLY_DISCUSSION_EVENT]: AddonModForumReplyDiscussionData;
[AddonModForumProvider.CHANGE_DISCUSSION_EVENT]: AddonModForumChangeDiscussionData;
[AddonModForumProvider.MARK_READ_EVENT]: AddonModForumMarkReadData;
}
}
/**
* Service that provides some features for forums.
*
@ -2151,3 +2167,44 @@ export type AddonModForumUpdateDiscussionPostWSParams = {
* Data returned by mod_forum_update_discussion_post WS.
*/
export type AddonModForumUpdateDiscussionPostWSResponse = CoreStatusWithWarningsWSResponse;
/**
* Data passed to NEW_DISCUSSION_EVENT event.
*/
export type AddonModForumNewDiscussionData = {
forumId: number;
cmId: number;
discussionIds?: number[] | null;
discTimecreated?: number;
};
/**
* Data passed to REPLY_DISCUSSION_EVENT event.
*/
export type AddonModForumReplyDiscussionData = {
forumId: number;
discussionId: number;
cmId: number;
};
/**
* Data passed to CHANGE_DISCUSSION_EVENT event.
*/
export type AddonModForumChangeDiscussionData = {
forumId: number;
discussionId: number;
cmId: number;
deleted?: boolean;
post?: AddonModForumPost;
locked?: boolean;
pinned?: boolean;
starred?: boolean;
};
/**
* Data passed to MARK_READ_EVENT event.
*/
export type AddonModForumMarkReadData = {
courseId: number;
moduleId: number;
};

View File

@ -95,7 +95,7 @@ export class AddonModForumModuleHandlerService implements CoreCourseModuleHandle
const event = CoreEvents.on(
AddonModForumProvider.MARK_READ_EVENT,
(eventData: { courseId?: number; moduleId?: number; siteId?: string }) => {
eventData => {
if (eventData.courseId !== courseId || eventData.moduleId !== module.id) {
return;
}

View File

@ -35,6 +35,20 @@ import {
import { AddonModForumHelper } from './helper.service';
import { AddonModForumOffline, AddonModForumOfflineDiscussion, AddonModForumOfflineReply } from './offline.service';
declare module '@singletons/events' {
/**
* Augment CoreEventsData interface with events specific to this service.
*
* @see https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation
*/
export interface CoreEventsData {
[AddonModForumSyncProvider.AUTO_SYNCED]: AddonModForumAutoSyncData;
[AddonModForumSyncProvider.MANUAL_SYNCED]: AddonModForumManualSyncData;
}
}
/**
* Service to sync forums.
*/
@ -95,7 +109,7 @@ export class AddonModForumSyncProvider extends CoreSyncBaseProvider<AddonModForu
if (result && result.updated) {
// Sync successful, send event.
CoreEvents.trigger<AddonModForumAutoSyncData>(AddonModForumSyncProvider.AUTO_SYNCED, {
CoreEvents.trigger(AddonModForumSyncProvider.AUTO_SYNCED, {
forumId: discussion.forumid,
userId: discussion.userid,
warnings: result.warnings,
@ -127,7 +141,7 @@ export class AddonModForumSyncProvider extends CoreSyncBaseProvider<AddonModForu
if (result && result.updated) {
// Sync successful, send event.
CoreEvents.trigger<AddonModForumAutoSyncData>(AddonModForumSyncProvider.AUTO_SYNCED, {
CoreEvents.trigger(AddonModForumSyncProvider.AUTO_SYNCED, {
forumId: reply.forumid,
discussionId: reply.discussionid,
userId: reply.userid,

View File

@ -28,6 +28,25 @@ export interface CoreEventObserver {
off: () => void;
}
/**
* Event payloads.
*/
export interface CoreEventsData {
[CoreEvents.SITE_UPDATED]: CoreEventSiteUpdatedData;
[CoreEvents.SITE_ADDED]: CoreEventSiteAddedData;
[CoreEvents.SESSION_EXPIRED]: CoreEventSessionExpiredData;
[CoreEvents.CORE_LOADING_CHANGED]: CoreEventLoadingChangedData;
[CoreEvents.COURSE_STATUS_CHANGED]: CoreEventCourseStatusChanged;
[CoreEvents.PACKAGE_STATUS_CHANGED]: CoreEventPackageStatusChanged;
[CoreEvents.USER_DELETED]: CoreEventUserDeletedData;
[CoreEvents.FORM_ACTION]: CoreEventFormActionData;
[CoreEvents.NOTIFICATION_SOUND_CHANGED]: CoreEventNotificationSoundChangedData;
[CoreEvents.SELECT_COURSE_TAB]: CoreEventSelectCourseTabData;
[CoreEvents.COMPLETION_MODULE_VIEWED]: CoreEventCompletionModuleViewedData;
[CoreEvents.SECTION_STATUS_CHANGED]: CoreEventSectionStatusChangedData;
[CoreEvents.ACTIVITY_DATA_SENT]: CoreEventActivityDataSentData;
};
/*
* Service to send and listen to events.
*/
@ -84,15 +103,15 @@ export class CoreEvents {
* @param siteId Site where to trigger the event. Undefined won't check the site.
* @return Observer to stop listening.
*/
static on<T = unknown>(
eventName: string,
callBack: (value: T & { siteId?: string }) => void,
static on<Fallback = unknown, Event extends string = string>(
eventName: Event,
callBack: (value: CoreEventData<Event, Fallback> & { siteId?: string }) => void,
siteId?: string,
): 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(<T> this.uniqueEvents[eventName].data);
callBack(this.uniqueEvents[eventName].data as CoreEventData<Event, Fallback> & { siteId?: string });
// Return a fake observer to prevent errors.
return {
@ -106,14 +125,16 @@ export class CoreEvents {
if (typeof this.observables[eventName] == 'undefined') {
// No observable for this event, create a new one.
this.observables[eventName] = new Subject<T>();
this.observables[eventName] = new Subject();
}
const subscription = this.observables[eventName].subscribe((value: T & {siteId?: string}) => {
if (!siteId || value.siteId == siteId) {
callBack(value);
}
});
const subscription = this.observables[eventName].subscribe(
(value: CoreEventData<Event, Fallback> & { siteId?: string }) => {
if (!siteId || value.siteId == siteId) {
callBack(value);
}
},
);
// Create and return a CoreEventObserver.
return {
@ -155,7 +176,11 @@ export class CoreEvents {
* @param data Data to pass to the observers.
* @param siteId Site where to trigger the event. Undefined means no Site.
*/
static trigger<T = unknown>(eventName: string, data?: T, siteId?: string): void {
static trigger<Fallback = unknown, Event extends string = string>(
eventName: Event,
data?: CoreEventData<Event, Fallback>,
siteId?: string,
): void {
this.logger.debug(`Event '${eventName}' triggered.`);
if (this.observables[eventName]) {
if (siteId) {
@ -172,7 +197,11 @@ export class CoreEvents {
* @param data Data to pass to the observers.
* @param siteId Site where to trigger the event. Undefined means no Site.
*/
static triggerUnique<T = unknown>(eventName: string, data: T, siteId?: string): void {
static triggerUnique<Fallback = unknown, Event extends string = string>(
eventName: Event,
data: CoreEventData<Event, Fallback>,
siteId?: string,
): void {
if (this.uniqueEvents[eventName]) {
this.logger.debug(`Unique event '${eventName}' ignored because it was already triggered.`);
} else {
@ -196,6 +225,11 @@ export class CoreEvents {
}
/**
* Resolve payload type for a given event.
*/
export type CoreEventData<Event, Fallback> = Event extends keyof CoreEventsData ? CoreEventsData[Event] : Fallback;
/**
* Some events contains siteId added by the trigger function. This type is intended to be combined with others.
*/