UNPKG

@magicbell/react-headless

Version:
381 lines 19.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = require("tslib"); const immer_1 = require("immer"); const ramda_1 = require("ramda"); const zustand_1 = require("zustand"); const realtime_js_1 = require("../../lib/realtime.js"); const buildStore_js_1 = tslib_1.__importDefault(require("./helpers/buildStore.js")); const setStoreProps_js_1 = tslib_1.__importDefault(require("./helpers/setStoreProps.js")); const strategies_js_1 = require("./helpers/strategies.js"); const NotificationRepository_js_1 = tslib_1.__importDefault(require("./NotificationRepository.js")); function unix() { return Math.floor(Date.now() / 1000); } function addToStore(store, notification) { store.total += 1; const idx = store.notifications.findIndex((n) => n.sentAt < notification.sentAt); if (idx === -1) { store.notifications.push(notification); } else { store.notifications.splice(idx, 0, notification); } } /** * Collection of notifications store. It contains all stores of notifications * and exposes methods to interact with them. * * @private Use the `useNotifications` hook instead. */ const useNotificationStoresCollection = (0, zustand_1.create)((set, get) => ({ stores: {}, _repository: new NotificationRepository_js_1.default(), setStore: (storeId, defaultQueryParams = {}, otherProps = {}) => { set((0, immer_1.produce)((draft) => { draft.stores[storeId] = (0, buildStore_js_1.default)({ ...otherProps, context: defaultQueryParams }); })); }, fetchStore: async (storeId, queryParams = {}, options = {}) => { const { stores, _repository } = get(); const store = stores[storeId]; if (store) { const response = await _repository.findBy({ ...store.context, ...queryParams }); if (!response) return; set((0, immer_1.produce)((draft) => { draft.stores[storeId] = (0, setStoreProps_js_1.default)(store, { ...response, lastFetchedAt: new Date() }, options); })); } else { // Provided for development support. // eslint-disable-next-line no-console console.error(`Store not found. Define a store with the ${storeId} ID`); } }, fetchAllStores: async (queryParams = {}, options = {}) => { const { stores, fetchStore } = get(); const storeIds = Object.keys(stores); const fetchers = storeIds.map((storeId) => fetchStore(storeId, queryParams, options)); await Promise.all(fetchers); }, markNotificationAsSeen: (notification) => { const { stores } = get(); const notificationId = notification.id; (0, realtime_js_1.emitEvent)('notifications.seen', notification, 'local'); set((0, immer_1.produce)((draft) => { for (const storeId in stores) { const { notifications, unseenCount } = stores[storeId]; const index = (0, ramda_1.findIndex)((0, ramda_1.propEq)('id', notificationId), notifications); if (index > -1) { const notification = notifications[index]; if (!notification.seenAt) { draft.stores[storeId].unseenCount = Math.max(0, unseenCount - 1); draft.stores[storeId].notifications[index] = (0, ramda_1.mergeRight)(notifications[index], { seenAt: unix(), }); } } } })); }, markNotificationAsRead: (notification) => { const { stores, _repository } = get(); const { id: notificationId } = notification; const promise = _repository.markAsRead(notificationId); (0, realtime_js_1.emitEvent)('notifications.read', notification, 'local'); set((0, immer_1.produce)((draft) => { const now = unix(); const attrs = { readAt: now, seenAt: now }; for (const storeId in stores) { const { total, notifications, context, unreadCount, unseenCount } = stores[storeId]; const index = (0, ramda_1.findIndex)((0, ramda_1.propEq)('id', notificationId), notifications); if (index > -1) { // Decrease the counters if the notification wasn't already read|seen, and clamp to zero as it might happen // that markAsSeen has set the counter to zero, while the notification.seenAt is still undefined if (!notification.readAt) draft.stores[storeId].unreadCount = Math.max(0, unreadCount - 1); if (!notification.seenAt) draft.stores[storeId].unseenCount = Math.max(0, unseenCount - 1); const readNotification = (0, ramda_1.mergeRight)(notifications[index], attrs); if ((0, strategies_js_1.objMatchesContext)(readNotification, context).result) { // Update the store draft.stores[storeId].notifications[index] = readNotification; } else { // Remove notification from the store draft.stores[storeId].total = Math.max(0, total - 1); draft.stores[storeId].notifications.splice(index, 1); } } else { const readNotification = (0, ramda_1.mergeRight)(notification, attrs); if ((0, strategies_js_1.objMatchesContext)(readNotification, context).result) { addToStore(draft.stores[storeId], readNotification); } } } })); return promise; }, markNotificationAsUnread: (notification) => { const { stores, _repository } = get(); const { id: notificationId } = notification; const promise = _repository.markAsUnread(notificationId); (0, realtime_js_1.emitEvent)('notifications.unread', notification, 'local'); set((0, immer_1.produce)((draft) => { const attrs = { readAt: null }; for (const storeId in stores) { const { notifications, context } = stores[storeId]; const index = (0, ramda_1.findIndex)((0, ramda_1.propEq)('id', notificationId), notifications); if (index > -1) { const unreadNotification = (0, ramda_1.mergeRight)(notifications[index], attrs); if ((0, strategies_js_1.objMatchesContext)(unreadNotification, context).result) { // Update the store if (notification.readAt) draft.stores[storeId].unreadCount += 1; draft.stores[storeId].notifications[index] = unreadNotification; } else { // Remove notification from the store draft.stores[storeId].total = Math.max(0, draft.stores[storeId].total - 1); draft.stores[storeId].notifications.splice(index, 1); } } else { const unreadNotification = (0, ramda_1.mergeRight)(notification, attrs); if ((0, strategies_js_1.objMatchesContext)(unreadNotification, context).result) { addToStore(draft.stores[storeId], unreadNotification); if (notification.readAt) draft.stores[storeId].unreadCount += 1; } } } })); return promise; }, archiveNotification: (notification, options = {}) => { const { stores, _repository } = get(); const { id: notificationId } = notification; let promise = Promise.resolve(true); // Do not persist the state if this op is a consequence of a remote event. if (options.persist !== false) { promise = _repository.archive(notificationId); (0, realtime_js_1.emitEvent)('notifications.archived', notification, 'local'); } set((0, immer_1.produce)((draft) => { const now = unix(); const attrs = { archivedAt: now }; for (const storeId in stores) { const store = stores[storeId]; const index = (0, ramda_1.findIndex)((0, ramda_1.propEq)('id', notificationId), store.notifications); if (index > -1) { const newNotification = { ...store.notifications[index], ...attrs }; if ((0, strategies_js_1.objMatchesContext)(newNotification, store.context).result) { // Update the store draft.stores[storeId].notifications[index] = newNotification; } else { // Decrease the counters if the notification was included in it, and clamp to zero if (!notification.readAt) draft.stores[storeId].unreadCount = Math.max(0, store.unreadCount - 1); if (!notification.seenAt) draft.stores[storeId].unseenCount = Math.max(0, store.unseenCount - 1); // Remove notification from the store draft.stores[storeId].total = Math.max(0, store.total - 1); draft.stores[storeId].notifications.splice(index, 1); } } else { const newNotification = { ...notification, ...attrs }; if ((0, strategies_js_1.objMatchesContext)(newNotification, store.context).result) { // Add the notification to the store if (!notification.readAt) draft.stores[storeId].unreadCount += 1; if (!notification.seenAt) draft.stores[storeId].unseenCount += 1; addToStore(draft.stores[storeId], newNotification); } } } })); return promise; }, unarchiveNotification: (notification, options = {}) => { const { stores, _repository } = get(); const { id: notificationId } = notification; let promise = Promise.resolve(true); // Do not persist the state if this op is a consequence of a remote event. if (options.persist !== false) { promise = _repository.unarchive(notificationId); (0, realtime_js_1.emitEvent)('notifications.unarchived', notification, 'local'); } set((0, immer_1.produce)((draft) => { const attrs = { archivedAt: null }; for (const storeId in stores) { const store = stores[storeId]; const index = (0, ramda_1.findIndex)((0, ramda_1.propEq)('id', notificationId), store.notifications); if (index > -1) { const newNotification = { ...store.notifications[index], ...attrs }; if ((0, strategies_js_1.objMatchesContext)(newNotification, store.context).result) { draft.stores[storeId].notifications[index] = newNotification; } else { // Remove notification from the store if (!notification.readAt) draft.stores[storeId].unreadCount = Math.max(0, store.unreadCount - 1); if (!notification.seenAt) draft.stores[storeId].unseenCount = Math.max(0, store.unseenCount - 1); draft.stores[storeId].total = Math.max(0, store.total - 1); draft.stores[storeId].notifications.splice(index, 1); } } else { const newNotification = { ...notification, ...attrs }; if ((0, strategies_js_1.objMatchesContext)(newNotification, store.context).result) { // Add the notification to the store if (!notification.readAt) draft.stores[storeId].unreadCount += 1; if (!notification.seenAt) draft.stores[storeId].unseenCount += 1; addToStore(draft.stores[storeId], newNotification); } } } })); return promise; }, deleteNotification: (notification, options = {}) => { const { stores, _repository } = get(); const notificationId = notification.id; let promise = Promise.resolve(true); // Do not persist the state is this op is a consequence of a remote event. // Neither emit a local event. if (options.persist !== false) { promise = _repository.delete(notificationId); (0, realtime_js_1.emitEvent)('notifications.delete', notification, 'local'); } set((0, immer_1.produce)((draft) => { for (const storeId in stores) { const { notifications, total, unseenCount, unreadCount } = stores[storeId]; const index = (0, ramda_1.findIndex)((0, ramda_1.propEq)('id', notificationId), notifications); if (index > -1) { const notification = notifications[index]; if (!notification.seenAt) draft.stores[storeId].unseenCount = Math.max(0, unseenCount - 1); if (!notification.readAt) draft.stores[storeId].unreadCount = Math.max(0, unreadCount - 1); draft.stores[storeId].total = Math.max(0, total - 1); draft.stores[storeId].notifications.splice(index, 1); } } })); return promise; }, markAllAsSeen: (options = { persist: true, updateModels: true }) => { const { stores, _repository } = get(); let promise = Promise.resolve(true); // Do not persist the state if this op is a consequence of a remote event. // Neither emit a local event. if (options.persist !== false) { const params = options.storeId ? stores[options.storeId]?.context : {}; promise = _repository.markAllAsSeen(params); (0, realtime_js_1.emitEvent)('notifications.seen.all', null, 'local'); } set((0, immer_1.produce)((draft) => { const changedNotifications = new Map(); const now = unix(); for (const storeId in stores) { const { context } = stores[storeId]; draft.stores[storeId].unseenCount = 0; if (options.updateModels === false) continue; // don't iterate over notifications in stores that only hold seen notifications if (context.seen !== true) { for (const notification of draft.stores[storeId].notifications) { // don't change notifications that are already seen if (notification.seenAt) continue; notification.seenAt = now; changedNotifications.set(notification.id, notification); } } // stores that don't include seen notifications, can be flushed if (context.seen === false) { draft.stores[storeId].notifications = []; draft.stores[storeId].total = 0; } } // do a second pass to update the notifications in the stores that don't hold seen notifications for (const storeId in stores) { const { context } = stores[storeId]; // skip stores that already hold seen notifications if (context.seen !== true) continue; const notifications = draft.stores[storeId].notifications; for (const notification of changedNotifications.values()) { if ((0, strategies_js_1.objMatchesContext)(notification, context).result && !notifications.find((n) => n.id === notification.id)) { addToStore(draft.stores[storeId], notification); } } } })); return promise; }, markAllAsRead: (options = { persist: true, updateModels: true }) => { const { stores, _repository } = get(); let promise = Promise.resolve(true); // Do not persist the state if this op is a consequence of a remote event. // Neither emit a local event. if (options.persist !== false) { const params = options.storeId ? stores[options.storeId]?.context : {}; promise = _repository.markAllAsRead(params); (0, realtime_js_1.emitEvent)('notifications.read.all', null, 'local'); } set((0, immer_1.produce)((draft) => { const changedNotifications = new Map(); const now = unix(); for (const storeId in stores) { const { context } = stores[storeId]; draft.stores[storeId].unreadCount = 0; draft.stores[storeId].unseenCount = 0; if (options.updateModels === false) continue; // don't iterate over notifications in stores that only hold read notifications if (context.read !== true) { for (const notification of draft.stores[storeId].notifications) { // don't change notifications that are already read if (notification.readAt) continue; notification.readAt = now; notification.seenAt = now; changedNotifications.set(notification.id, notification); } } // stores that don't include read notifications, can be flushed if (context.read === false) { draft.stores[storeId].notifications = []; draft.stores[storeId].total = 0; } } // do a second run to add changed notifications to stores that didn't hold unread notifications for (const storeId in stores) { const { context } = stores[storeId]; // skip stores that already contain the notification if (context.read !== true) continue; const notifications = draft.stores[storeId].notifications; for (const notification of changedNotifications.values()) { if ((0, strategies_js_1.objMatchesContext)(notification, context).result && !notifications.find((n) => n.id === notification.id)) { addToStore(draft.stores[storeId], notification); } } } })); return promise; }, })); exports.default = useNotificationStoresCollection; //# sourceMappingURL=useNotificationStoresCollection.js.map