@replyke/core
Version:
Replyke: Build interactive apps with social features like comments, votes, feeds, user lists, notifications, and more.
234 lines • 11.5 kB
JavaScript
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