1 import { useEffect, useMemo, useState } from 'react';
3 import useEventManager from '@proton/components/hooks/useEventManager';
4 import { useLoading } from '@proton/hooks';
5 import { getSubscriptionParameters } from '@proton/shared/lib/api/calendars';
6 import { getIsSubscribedCalendar, getVisualCalendars } from '@proton/shared/lib/calendar/calendar';
7 import { getIsCalendarSubscriptionEventManagerUpdate } from '@proton/shared/lib/calendar/subscribe/helpers';
10 getIsCalendarEventManagerCreate,
11 getIsCalendarEventManagerDelete,
12 getIsCalendarEventManagerUpdate,
13 getIsCalendarMemberEventManagerCreate,
14 getIsCalendarMemberEventManagerDelete,
15 getIsCalendarMemberEventManagerUpdate,
16 } from '@proton/shared/lib/eventManager/calendar/helpers';
21 CalendarSubscriptionResponse,
22 CalendarWithOwnMembers,
25 } from '@proton/shared/lib/interfaces/calendar';
28 CalendarMemberEventManager,
29 CalendarSubscriptionEventManager,
30 } from '@proton/shared/lib/interfaces/calendar/EventManager';
31 import addItem from '@proton/utils/addItem';
32 import removeItem from '@proton/utils/removeIndex';
33 import updateItem from '@proton/utils/updateItem';
35 import { useCalendarModelEventManager } from '../containers/eventManager/calendar/CalendarModelEventManagerProvider';
36 import useApi from './useApi';
38 const useSubscribedCalendars = (calendars: VisualCalendar[], loadingCalendars = false) => {
39 const [subscribedCalendars, setSubscribedCalendars] = useState<SubscribedCalendar[]>([]);
40 const [loading, withLoading] = useLoading(true);
43 const calendarIDs = useMemo(() => calendars.map(({ ID }) => ID), [calendars]);
45 const { subscribe: coreSubscribe } = useEventManager();
46 const { subscribe: calendarSubscribe } = useCalendarModelEventManager();
48 const handleAddCalendar = async (calendar: CalendarWithOwnMembers) => {
49 if (!getIsSubscribedCalendar(calendar)) {
52 const { CalendarSubscription } = await api<CalendarSubscriptionResponse>(
53 getSubscriptionParameters(calendar.ID)
56 setSubscribedCalendars((prevState = []) =>
61 SubscriptionParameters: CalendarSubscription,
67 const handleDeleteCalendar = (calendarID: string) => {
68 setSubscribedCalendars((subscribedCalendars) => {
69 const index = subscribedCalendars?.findIndex((calendar) => calendar.ID === calendarID);
71 return removeItem(subscribedCalendars, index);
75 const handleUpdateCalendar = (calendar: Calendar) => {
76 setSubscribedCalendars((subscribedCalendars) => {
77 const { ID } = calendar;
78 const index = subscribedCalendars?.findIndex((calendar) => calendar.ID === ID);
80 return updateItem(subscribedCalendars, index, {
81 ...subscribedCalendars[index],
87 const handleDeleteMember = (memberID: string) => {
88 setSubscribedCalendars((subscribedCalendars) => {
89 const [calendarIndex, memberIndex] = findMemberIndices(memberID, subscribedCalendars);
90 if (calendarIndex === -1 || memberIndex === -1) {
91 return subscribedCalendars;
93 const oldCalendar = subscribedCalendars[calendarIndex];
95 return updateItem(subscribedCalendars, calendarIndex, {
97 Members: removeItem(oldCalendar.Members, memberIndex),
102 const handleCreateMember = (member: CalendarMember) => {
103 setSubscribedCalendars((subscribedCalendars) => {
104 const { ID: memberID, CalendarID } = member;
105 const [calendarIndex, memberIndex] = findMemberIndices(memberID, subscribedCalendars, CalendarID);
106 if (calendarIndex === -1 || memberIndex !== -1) {
107 return subscribedCalendars;
109 const oldCalendar = subscribedCalendars[calendarIndex];
111 return updateItem(subscribedCalendars, calendarIndex, {
113 Members: addItem(oldCalendar.Members, member),
118 const handleUpdateMember = (member: CalendarMember) => {
119 setSubscribedCalendars((subscribedCalendars) => {
120 const { ID: memberID, CalendarID } = member;
121 const [calendarIndex, memberIndex] = findMemberIndices(memberID, subscribedCalendars, CalendarID);
122 if (calendarIndex === -1 || memberIndex === -1) {
123 return subscribedCalendars;
125 const oldCalendar = subscribedCalendars[calendarIndex];
127 return getVisualCalendars(
128 updateItem(subscribedCalendars, calendarIndex, {
130 Members: updateItem(oldCalendar.Members, memberIndex, member),
136 const handleUpdateSubscription = (calendarID: string, subscription: CalendarSubscription) => {
137 setSubscribedCalendars((subscribedCalendars) => {
138 const index = subscribedCalendars?.findIndex((calendar) => calendar.ID === calendarID);
139 const oldCalendar = subscribedCalendars[index];
140 return updateItem(subscribedCalendars, index, {
142 SubscriptionParameters: {
143 ...oldCalendar.SubscriptionParameters,
151 if (loadingCalendars) {
155 return coreSubscribe(
157 Calendars: CalendarEvents = [],
158 CalendarMembers: CalendarMembersEvents = [],
160 Calendars?: CalendarEventManager[];
161 CalendarMembers?: CalendarMemberEventManager[];
163 CalendarEvents.forEach((event) => {
164 if (getIsCalendarEventManagerDelete(event)) {
165 handleDeleteCalendar(event.ID);
168 if (getIsCalendarEventManagerCreate(event)) {
169 // TODO: The code below is prone to race conditions. Namely if a new event manager update
170 // comes before this promise is resolved.
171 void handleAddCalendar(event.Calendar);
174 if (getIsCalendarEventManagerUpdate(event)) {
175 // TODO: The code below is prone to race conditions. Namely if a new event manager update
176 // comes before this promise is resolved.
177 void handleUpdateCalendar(event.Calendar);
181 CalendarMembersEvents.forEach((event) => {
182 if (getIsCalendarMemberEventManagerDelete(event)) {
183 handleDeleteMember(event.ID);
184 } else if (getIsCalendarMemberEventManagerCreate(event)) {
185 handleCreateMember(event.Member);
186 } else if (getIsCalendarMemberEventManagerUpdate(event)) {
187 handleUpdateMember(event.Member);
192 }, [loadingCalendars]);
195 if (loadingCalendars) {
199 return calendarSubscribe(
202 CalendarSubscriptions: CalendarSubscriptionEvents = [],
204 CalendarSubscriptions?: CalendarSubscriptionEventManager[];
206 CalendarSubscriptionEvents.forEach((calendarSubscriptionChange) => {
207 if (getIsCalendarSubscriptionEventManagerUpdate(calendarSubscriptionChange)) {
208 const { ID, CalendarSubscription } = calendarSubscriptionChange;
209 handleUpdateSubscription(ID, CalendarSubscription);
214 }, [calendarIDs, loadingCalendars]);
217 const run = async () => {
218 const newSubscribedCalendars = await Promise.all(
219 calendars.map(async (calendar) => {
220 const { CalendarSubscription } = await api<CalendarSubscriptionResponse>(
221 getSubscriptionParameters(calendar.ID)
226 SubscriptionParameters: CalendarSubscription,
231 void setSubscribedCalendars(newSubscribedCalendars);
234 if (loadingCalendars) {
238 void withLoading(run());
239 }, [loadingCalendars]);
241 // Refresh subscribed calendars when the list of calendars changes (example: visibility change)
243 if (loadingCalendars || loading) {
247 setSubscribedCalendars((prevState) => {
248 return prevState.map((calendar) => {
249 const visualCalendar = calendars.find(({ ID }) => ID === calendar.ID);
250 if (!visualCalendar) {
259 }, [calendars, loadingCalendars, loading]);
261 return { subscribedCalendars, loading };
264 export default useSubscribedCalendars;