UNPKG

@replyke/core

Version:

Replyke: Build interactive apps with social features like comments, votes, feeds, user lists, notifications, and more.

210 lines 8.41 kB
import { createSlice, createSelector } from "@reduxjs/toolkit"; // Default state for a new space list const createDefaultSpaceListState = () => ({ spaces: [], page: 1, loading: true, hasMore: true, error: null, lastFetched: null, // Default configuration limit: 20, // Default filters (user-controlled only) sortBy: "newest", searchSlug: null, searchName: null, searchDescription: null, searchAny: null, readingPermission: null, memberOf: false, parentSpaceId: undefined, // Will be set when fetchSpaces is called }); // Initial state const initialState = { lists: {}, }; // Create the slice export const spaceListsSlice = createSlice({ name: "spaceLists", initialState, reducers: { // Initialize or get existing list initializeList: (state, action) => { const { listId } = action.payload; if (!state.lists[listId]) { state.lists[listId] = createDefaultSpaceListState(); } }, // Update filters and sort (unified function) updateFilters: (state, action) => { const { listId, filters, options } = action.payload; // Ensure list exists if (!state.lists[listId]) { state.lists[listId] = createDefaultSpaceListState(); } const list = state.lists[listId]; // If resetUnspecified is true, reset to defaults first if (options?.resetUnspecified) { const defaultState = createDefaultSpaceListState(); // Reset all filter properties to defaults list.sortBy = defaultState.sortBy; list.searchSlug = defaultState.searchSlug; list.searchName = defaultState.searchName; list.searchDescription = defaultState.searchDescription; list.searchAny = defaultState.searchAny; list.readingPermission = defaultState.readingPermission; list.memberOf = defaultState.memberOf; list.parentSpaceId = defaultState.parentSpaceId; } // Update specified filters Object.keys(filters).forEach((key) => { if (filters[key] !== undefined) { list[key] = filters[key]; } }); // Update config if provided if (action.payload.config) { if (action.payload.config.limit !== undefined) { list.limit = action.payload.config.limit; } } // Reset pagination when filters change list.page = 1; list.hasMore = true; list.error = null; }, // Set loading state for space list setSpaceListLoading: (state, action) => { const { listId, loading } = action.payload; if (state.lists[listId]) { state.lists[listId].loading = loading; } }, // Set spaces for space list setSpaceListSpaces: (state, action) => { const { listId, spaces, append = false } = action.payload; if (!state.lists[listId]) { state.lists[listId] = createDefaultSpaceListState(); } const list = state.lists[listId]; if (append) { // Filter out duplicates when appending const existingIds = new Set(list.spaces.map((s) => s.id)); const newSpaces = spaces.filter((s) => !existingIds.has(s.id)); list.spaces = [...list.spaces, ...newSpaces]; } else { list.spaces = spaces; } list.loading = false; list.lastFetched = Date.now(); // Note: hasMore is set explicitly by the caller based on limit from hook props }, // Increment page for load more incrementPage: (state, action) => { const listId = action.payload; if (state.lists[listId]) { state.lists[listId].page += 1; } }, // Set hasMore for space list setSpaceListHasMore: (state, action) => { const { listId, hasMore } = action.payload; if (state.lists[listId]) { state.lists[listId].hasMore = hasMore; } }, // Set error for space list setSpaceListError: (state, action) => { const { listId, error } = action.payload; if (state.lists[listId]) { state.lists[listId].error = error; state.lists[listId].loading = false; } }, // Add space addSpace: (state, action) => { const { listId, space, insertPosition = "first" } = action.payload; if (!state.lists[listId]) return; const list = state.lists[listId]; if (insertPosition === "last") { list.spaces.push(space); } else { list.spaces.unshift(space); } }, // Remove space removeSpace: (state, action) => { const { listId, spaceId } = action.payload; if (!state.lists[listId]) return; const list = state.lists[listId]; list.spaces = list.spaces.filter((s) => s.id !== spaceId); }, // Update space updateSpace: (state, action) => { const { listId, spaceId, updates } = action.payload; if (!state.lists[listId]) return; const list = state.lists[listId]; const spaceIndex = list.spaces.findIndex((s) => s.id === spaceId); if (spaceIndex !== -1) { list.spaces[spaceIndex] = { ...list.spaces[spaceIndex], ...updates, }; } }, // Clean up unused lists (for memory management) cleanupList: (state, action) => { const listId = action.payload; delete state.lists[listId]; }, // Clean up old lists (older than TTL) cleanupOldLists: (state, action) => { const ttl = action.payload; // TTL in milliseconds const now = Date.now(); Object.keys(state.lists).forEach((listId) => { const list = state.lists[listId]; if (list.lastFetched && now - list.lastFetched > ttl) { delete state.lists[listId]; } }); }, }, }); // Export actions export const { initializeList, updateFilters, setSpaceListLoading, setSpaceListSpaces, incrementPage, setSpaceListHasMore, setSpaceListError, addSpace, removeSpace, updateSpace, cleanupList, cleanupOldLists, } = spaceListsSlice.actions; // Base selectors - use namespaced state for dual-mode support const selectSpaceListsState = (state) => state.replyke.spaceLists; const selectListId = (_, listId) => listId; // Memoized selectors using createSelector export const selectSpaceList = createSelector([selectSpaceListsState, selectListId], (spaceListsState, listId) => spaceListsState.lists[listId]); export const selectSpaceListSpaces = createSelector([selectSpaceList], (spaceList) => spaceList?.spaces || []); export const selectSpaceListLoading = createSelector([selectSpaceList], (spaceList) => spaceList?.loading || false); export const selectSpaceListHasMore = createSelector([selectSpaceList], (spaceList) => spaceList?.hasMore || false); export const selectSpaceListFilters = createSelector([selectSpaceList], (spaceList) => { if (!spaceList) return null; return { sortBy: spaceList.sortBy, searchSlug: spaceList.searchSlug, searchName: spaceList.searchName, searchDescription: spaceList.searchDescription, searchAny: spaceList.searchAny, readingPermission: spaceList.readingPermission, memberOf: spaceList.memberOf, parentSpaceId: spaceList.parentSpaceId, }; }); export const selectSpaceListConfig = createSelector([selectSpaceList], (spaceList) => { if (!spaceList) return null; return { limit: spaceList.limit, }; }); export default spaceListsSlice.reducer; //# sourceMappingURL=spaceListsSlice.js.map