Use source loader for email sprite icons
[ProtonMail-WebClient.git] / packages / calendar / calendars / listener.ts
blob089bbe55842d6b4b8c87f2cb05f8c9b1097c9f4a
1 import { createNextState } from '@reduxjs/toolkit';
3 import { selectAddresses } from '@proton/account/addresses';
4 import { serverEvent } from '@proton/account/eventLoop';
5 import {
6     findMemberIndices,
7     getIsCalendarEventManagerCreate,
8     getIsCalendarEventManagerDelete,
9     getIsCalendarEventManagerUpdate,
10     getIsCalendarMemberEventManagerCreate,
11     getIsCalendarMemberEventManagerDelete,
12     getIsCalendarMemberEventManagerUpdate,
13 } from '@proton/shared/lib/eventManager/calendar/helpers';
15 import {
16     type CalendarsBootstrapState,
17     calendarsBootstrapActions,
18     findCalendarBootstrapID,
19     selectCalendarsBootstrap,
20 } from '../calendarBootstrap';
21 import { deleteCalendarFromKeyCache } from '../calendarBootstrap/keys';
22 import { calendarServerEvent } from '../calendarServerEvent';
23 import type { CalendarStartListening } from '../interface';
24 import type { CalendarsState } from './index';
25 import { calendarsActions, selectCalendars } from './index';
27 // This calendar event listener needs to access the global state to get the addresses, so it's a listener and not a reducer.
28 export const startCalendarEventListener = (
29     startListening: CalendarStartListening<CalendarsState & CalendarsBootstrapState>
30 ) => {
31     startListening({
32         actionCreator: serverEvent,
33         effect: async (action, listenerApi) => {
34             const updateMembers = action.payload.CalendarMembers;
35             if (updateMembers?.length) {
36                 updateMembers.forEach((event) => {
37                     listenerApi.dispatch(calendarsBootstrapActions.memberEvent({ event }));
38                 });
39             }
41             const updateCalendars = action.payload.Calendars;
42             if (updateCalendars?.length) {
43                 updateCalendars.forEach((event) => {
44                     if (getIsCalendarEventManagerDelete(event)) {
45                         const calendarID = event.ID;
46                         listenerApi.dispatch(calendarsBootstrapActions.remove({ id: calendarID }));
47                         deleteCalendarFromKeyCache(calendarID);
48                     }
49                 });
50             }
51         },
52     });
54     startListening({
55         actionCreator: calendarServerEvent,
56         effect: async (action, listenerApi) => {
57             const calendarKeysUpdate = action.payload.CalendarKeys;
58             if (calendarKeysUpdate?.length) {
59                 const deleteCalendarFromCache = (calendarID: string) => {
60                     listenerApi.dispatch(calendarsBootstrapActions.remove({ id: calendarID }));
61                     deleteCalendarFromKeyCache(calendarID);
62                 };
63                 const state = selectCalendarsBootstrap(listenerApi.getState());
64                 calendarKeysUpdate.forEach(({ ID: KeyID, Key }) => {
65                     // When a new calendar key is received, the entire calendar cache is invalidated.
66                     // TODO: Merge the bootstrapped version.
67                     if (Key && Key.CalendarID) {
68                         deleteCalendarFromCache(Key.CalendarID);
69                         return;
70                     }
71                     const calendarID = findCalendarBootstrapID(state, ({ Keys }) => {
72                         return Boolean(Array.isArray(Keys) && Keys.find(({ ID: otherID }) => otherID === KeyID));
73                     });
74                     if (calendarID) {
75                         deleteCalendarFromCache(calendarID);
76                     }
77                 });
78             }
79         },
80     });
82     startListening({
83         actionCreator: serverEvent,
84         effect: async (action, listenerApi) => {
85             if (!action.payload.Calendars && !action.payload.CalendarMembers) {
86                 return;
87             }
89             const state = listenerApi.getState();
90             const currentCalendarsWithMembers = selectCalendars(state).value;
91             if (!currentCalendarsWithMembers) {
92                 return;
93             }
95             const updateCalendars = action.payload.Calendars;
96             const updateMembers = action.payload.CalendarMembers;
97             const calendarEventModelManager = listenerApi.extra.calendarModelEventManager;
99             const oldCalendarsWithMembers = [...currentCalendarsWithMembers];
100             const newCalendarsWithMembers = createNextState(oldCalendarsWithMembers, (newCalendarsWithMembers) => {
101                 if (updateCalendars?.length) {
102                     for (const event of updateCalendars) {
103                         if (getIsCalendarEventManagerDelete(event)) {
104                             const index = newCalendarsWithMembers.findIndex(({ ID }) => ID === event.ID);
105                             if (index !== -1) {
106                                 newCalendarsWithMembers.splice(index, 1);
107                             }
108                             calendarEventModelManager.reset([event.ID]);
109                         } else if (getIsCalendarEventManagerCreate(event)) {
110                             const { ID: calendarID, Calendar } = event;
111                             const index = newCalendarsWithMembers.findIndex(({ ID }) => ID === calendarID);
112                             if (index !== -1) {
113                                 // The calendar already exists for a creation event. Ignore it.
114                                 continue;
115                             }
116                             newCalendarsWithMembers.push({ ...Calendar });
117                         } else if (getIsCalendarEventManagerUpdate(event)) {
118                             const { ID: calendarID, Calendar } = event;
119                             const index = newCalendarsWithMembers.findIndex(({ ID }) => ID === calendarID);
120                             if (index !== -1) {
121                                 // update only the calendar part. Members updated below if needed
122                                 const oldCalendarWithMembers = oldCalendarsWithMembers[index];
123                                 newCalendarsWithMembers.splice(index, 1, { ...oldCalendarWithMembers, ...Calendar });
124                             }
125                         }
126                     }
127                 }
129                 if (updateMembers?.length) {
130                     const ownAddressIDs = (selectAddresses(state)?.value || []).map(({ ID }) => ID);
132                     for (const event of updateMembers) {
133                         if (getIsCalendarMemberEventManagerDelete(event)) {
134                             const [calendarIndex, memberIndex] = findMemberIndices(event.ID, newCalendarsWithMembers);
135                             if (calendarIndex !== -1 && memberIndex !== -1) {
136                                 const { CalendarID, AddressID } =
137                                     newCalendarsWithMembers[calendarIndex].Members[memberIndex]!;
138                                 if (ownAddressIDs.includes(AddressID)) {
139                                     // the user is the member removed -> remove the calendar
140                                     newCalendarsWithMembers.splice(calendarIndex, 1);
141                                     calendarEventModelManager.reset([CalendarID]);
142                                 } else {
143                                     // otherwise a member of one of an owned calendar got removed -> remove the member
144                                     newCalendarsWithMembers[calendarIndex].Members.splice(memberIndex, 1);
145                                 }
146                             }
147                         } else {
148                             const [calendarIndex, memberIndex] = findMemberIndices(
149                                 event.ID,
150                                 newCalendarsWithMembers,
151                                 event.Member.CalendarID
152                             );
153                             // If the targeted calendar cannot be found, ignore this update. It will be dealt with when the calendar update happens.
154                             if (calendarIndex === -1) {
155                                 continue;
156                             }
157                             if (getIsCalendarMemberEventManagerCreate(event)) {
158                                 if (memberIndex !== -1) {
159                                     continue;
160                                 }
161                                 newCalendarsWithMembers[calendarIndex].Members.push(event.Member);
162                             } else if (getIsCalendarMemberEventManagerUpdate(event)) {
163                                 if (memberIndex === -1) {
164                                     continue;
165                                 }
166                                 newCalendarsWithMembers[calendarIndex].Members.splice(memberIndex, 1, event.Member);
167                             }
168                         }
169                     }
170                 }
171             });
173             if (newCalendarsWithMembers !== oldCalendarsWithMembers) {
174                 listenerApi.dispatch(calendarsActions.updateCalendars(newCalendarsWithMembers));
175             }
176         },
177     });