UNPKG

@replyke/core

Version:

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

234 lines 11.5 kB
import { createSlice, createSelector } from "@reduxjs/toolkit"; // Initial state const initialState = { collectionsById: {}, subcollectionsMap: {}, currentCollectionId: null, collectionHistory: [], loading: false, currentProjectId: undefined, entitiesByCollectionId: {}, }; // Create the slice export const collectionsSlice = createSlice({ name: "collections", initialState, reducers: { // Set the current project context setProjectContext: (state, action) => { state.currentProjectId = action.payload; }, // Set loading state setLoading: (state, action) => { state.loading = action.payload; }, // Navigation actions openCollection: (state, action) => { const collection = action.payload; // Store the collection if not already stored if (!state.collectionsById[collection.id]) { state.collectionsById[collection.id] = collection; } // Push current collection ID to history stack before opening new one if (state.currentCollectionId) { state.collectionHistory.push(state.currentCollectionId); } // Set new current collection ID state.currentCollectionId = collection.id; }, goBack: (state) => { if (state.collectionHistory.length === 0) return; const previousCollectionId = state.collectionHistory.pop(); if (!previousCollectionId) return; state.currentCollectionId = previousCollectionId; }, goToRoot: (state) => { if (state.collectionHistory.length === 0) return; const rootCollectionId = state.collectionHistory[0]; state.collectionHistory = []; state.currentCollectionId = rootCollectionId; }, // Set current collection (for initial root collection fetch) setCurrentCollection: (state, action) => { const collection = action.payload; if (collection) { state.collectionsById[collection.id] = collection; state.currentCollectionId = collection.id; } else { state.currentCollectionId = null; } }, // Set sub-collections and update mapping setSubCollections: (state, action) => { const { collections, parentCollectionId } = action.payload; // Store all collections in collectionsById collections.forEach(collection => { state.collectionsById[collection.id] = collection; }); // Update parent-child mapping state.subcollectionsMap[parentCollectionId] = collections.map(collection => collection.id); }, // Update current collection (for entity add/remove operations) updateCurrentCollection: (state, action) => { const updatedCollection = action.payload; // Update in collectionsById (single source of truth) state.collectionsById[updatedCollection.id] = updatedCollection; }, // Update a collection (now just updates in collectionsById) updateCollectionInSubCollections: (state, action) => { const updatedCollection = action.payload; // Update in collectionsById (single source of truth) state.collectionsById[updatedCollection.id] = updatedCollection; }, // Add new collection to sub-collections and navigate to it addNewCollectionAndNavigate: (state, action) => { const newCollection = action.payload; if (!state.currentCollectionId) return; // Store the new collection state.collectionsById[newCollection.id] = newCollection; // Push current collection ID to history state.collectionHistory.push(state.currentCollectionId); // Set new collection as current state.currentCollectionId = newCollection.id; // Update parent-child mapping if (newCollection.parentId) { if (!state.subcollectionsMap[newCollection.parentId]) { state.subcollectionsMap[newCollection.parentId] = []; } if (!state.subcollectionsMap[newCollection.parentId].includes(newCollection.id)) { state.subcollectionsMap[newCollection.parentId].push(newCollection.id); } } }, // Remove collection from sub-collections and storage removeCollectionFromSubCollections: (state, action) => { const collectionId = action.payload; // Remove from collectionsById delete state.collectionsById[collectionId]; // Remove from all parent-child mappings Object.keys(state.subcollectionsMap).forEach((parentId) => { state.subcollectionsMap[parentId] = state.subcollectionsMap[parentId].filter((id) => id !== collectionId); }); // Remove its own sub-collections mapping if it exists delete state.subcollectionsMap[collectionId]; }, // Handle collection deletion handleCollectionDeletion: (state, action) => { const { collectionId, parentId } = action.payload; // Remove from parent-child mapping if (parentId && state.subcollectionsMap[parentId]) { state.subcollectionsMap[parentId] = state.subcollectionsMap[parentId].filter((id) => id !== collectionId); } // Remove from collectionsById delete state.collectionsById[collectionId]; // Remove its own sub-collections mapping delete state.subcollectionsMap[collectionId]; // Remove its entity list delete state.entitiesByCollectionId[collectionId]; // If deleted collection is current collection, go back if (state.currentCollectionId === collectionId) { if (state.collectionHistory.length === 0) { state.currentCollectionId = null; return; } const previousCollectionId = state.collectionHistory.pop(); if (!previousCollectionId) { state.currentCollectionId = null; return; } state.currentCollectionId = previousCollectionId; } }, // Entity list management (shared state for optimistic add/remove) setCollectionEntities: (state, action) => { const { collectionId, entities } = action.payload; state.entitiesByCollectionId[collectionId] = entities; }, appendCollectionEntities: (state, action) => { const { collectionId, entities } = action.payload; if (!state.entitiesByCollectionId[collectionId]) { state.entitiesByCollectionId[collectionId] = []; } state.entitiesByCollectionId[collectionId].push(...entities); }, prependCollectionEntity: (state, action) => { const { collectionId, entity } = action.payload; if (!state.entitiesByCollectionId[collectionId]) { state.entitiesByCollectionId[collectionId] = []; } state.entitiesByCollectionId[collectionId].unshift(entity); }, removeCollectionEntity: (state, action) => { const { collectionId, entityId } = action.payload; if (state.entitiesByCollectionId[collectionId]) { state.entitiesByCollectionId[collectionId] = state.entitiesByCollectionId[collectionId].filter((e) => e.id !== entityId); } }, insertCollectionEntityAt: (state, action) => { const { collectionId, entity, index } = action.payload; if (!state.entitiesByCollectionId[collectionId]) { state.entitiesByCollectionId[collectionId] = []; } const clamped = Math.min(index, state.entitiesByCollectionId[collectionId].length); state.entitiesByCollectionId[collectionId].splice(clamped, 0, entity); }, // Reset all collections data resetCollections: (state) => { state.collectionsById = {}; state.subcollectionsMap = {}; state.currentCollectionId = null; state.collectionHistory = []; state.loading = false; state.entitiesByCollectionId = {}; }, // Handle errors by stopping loading handleError: (state) => { state.loading = false; }, }, }); // Export actions export const { setProjectContext, setLoading, openCollection, goBack, goToRoot, setCurrentCollection, setSubCollections, updateCurrentCollection, updateCollectionInSubCollections, addNewCollectionAndNavigate, removeCollectionFromSubCollections, handleCollectionDeletion, setCollectionEntities, appendCollectionEntities, prependCollectionEntity, removeCollectionEntity, insertCollectionEntityAt, resetCollections, handleError, } = collectionsSlice.actions; // Export reducer export default collectionsSlice.reducer; // Selectors - use namespaced state for dual-mode support export const selectCurrentCollection = (state) => { const { currentCollectionId, collectionsById } = state.replyke.collections; return currentCollectionId ? collectionsById[currentCollectionId] || null : null; }; export const selectSubCollections = createSelector([(state) => state.replyke.collections.currentCollectionId, (state) => state.replyke.collections.subcollectionsMap, (state) => state.replyke.collections.collectionsById], (currentCollectionId, subcollectionsMap, collectionsById) => { if (!currentCollectionId || !subcollectionsMap[currentCollectionId]) { return []; } return subcollectionsMap[currentCollectionId] .map(collectionId => collectionsById[collectionId]) .filter(Boolean); // Remove any undefined entries }); export const selectCollectionsLoading = (state) => state.replyke.collections.loading; export const selectCollectionHistory = createSelector([(state) => state.replyke.collections.collectionHistory, (state) => state.replyke.collections.collectionsById], (collectionHistory, collectionsById) => { return collectionHistory .map(collectionId => collectionsById[collectionId]) .filter(Boolean); // Remove any undefined entries }); // Selector for the sub-collections mapping export const selectSubCollectionsMap = (state) => state.replyke.collections.subcollectionsMap; // Selector for all collections export const selectCollectionsById = (state) => state.replyke.collections.collectionsById; export const selectCurrentProjectId = (state) => state.replyke.collections.currentProjectId; // Selector for current collection ID export const selectCurrentCollectionId = (state) => state.replyke.collections.currentCollectionId; const EMPTY_ENTITIES = []; // Selector for entities in a specific collection export const selectCollectionEntities = (collectionId) => (state) => collectionId ? (state.replyke.collections.entitiesByCollectionId[collectionId] ?? EMPTY_ENTITIES) : EMPTY_ENTITIES; //# sourceMappingURL=collectionsSlice.js.map