UNPKG

@wordpress/core-data

Version:
450 lines (449 loc) 12.2 kB
// packages/core-data/src/reducer.js import fastDeepEqual from "fast-deep-equal/es6"; import { compose } from "@wordpress/compose"; import { combineReducers } from "@wordpress/data"; import { createUndoManager } from "@wordpress/undo-manager"; import { ifMatchingAction, replaceAction } from "./utils"; import { reducer as queriedDataReducer } from "./queried-data"; import { rootEntitiesConfig, DEFAULT_ENTITY_KEY } from "./entities"; function users(state = { byId: {}, queries: {} }, action) { switch (action.type) { case "RECEIVE_USER_QUERY": return { byId: { ...state.byId, // Key users by their ID. ...action.users.reduce( (newUsers, user) => ({ ...newUsers, [user.id]: user }), {} ) }, queries: { ...state.queries, [action.queryID]: action.users.map((user) => user.id) } }; } return state; } function currentUser(state = {}, action) { switch (action.type) { case "RECEIVE_CURRENT_USER": return action.currentUser; } return state; } function currentTheme(state = void 0, action) { switch (action.type) { case "RECEIVE_CURRENT_THEME": return action.currentTheme.stylesheet; } return state; } function currentGlobalStylesId(state = void 0, action) { switch (action.type) { case "RECEIVE_CURRENT_GLOBAL_STYLES_ID": return action.id; } return state; } function themeBaseGlobalStyles(state = {}, action) { switch (action.type) { case "RECEIVE_THEME_GLOBAL_STYLES": return { ...state, [action.stylesheet]: action.globalStyles }; } return state; } function themeGlobalStyleVariations(state = {}, action) { switch (action.type) { case "RECEIVE_THEME_GLOBAL_STYLE_VARIATIONS": return { ...state, [action.stylesheet]: action.variations }; } return state; } var withMultiEntityRecordEdits = (reducer) => (state, action) => { if (action.type === "UNDO" || action.type === "REDO") { const { record } = action; let newState = state; record.forEach(({ id: { kind, name, recordId }, changes }) => { newState = reducer(newState, { type: "EDIT_ENTITY_RECORD", kind, name, recordId, edits: Object.entries(changes).reduce( (acc, [key, value]) => { acc[key] = action.type === "UNDO" ? value.from : value.to; return acc; }, {} ) }); }); return newState; } return reducer(state, action); }; function entity(entityConfig) { return compose([ withMultiEntityRecordEdits, // Limit to matching action type so we don't attempt to replace action on // an unhandled action. ifMatchingAction( (action) => action.name && action.kind && action.name === entityConfig.name && action.kind === entityConfig.kind ), // Inject the entity config into the action. replaceAction((action) => { return { key: entityConfig.key || DEFAULT_ENTITY_KEY, ...action }; }) ])( combineReducers({ queriedData: queriedDataReducer, edits: (state = {}, action) => { switch (action.type) { case "RECEIVE_ITEMS": const context = action?.query?.context ?? "default"; if (context !== "default") { return state; } const nextState = { ...state }; for (const record of action.items) { const recordId = record?.[action.key]; const edits = nextState[recordId]; if (!edits) { continue; } const nextEdits2 = Object.keys(edits).reduce( (acc, key) => { if ( // Edits are the "raw" attribute values, but records may have // objects with more properties, so we use `get` here for the // comparison. !fastDeepEqual( edits[key], record[key]?.raw ?? record[key] ) && // Sometimes the server alters the sent value which means // we need to also remove the edits before the api request. (!action.persistedEdits || !fastDeepEqual( edits[key], action.persistedEdits[key] )) ) { acc[key] = edits[key]; } return acc; }, {} ); if (Object.keys(nextEdits2).length) { nextState[recordId] = nextEdits2; } else { delete nextState[recordId]; } } return nextState; case "EDIT_ENTITY_RECORD": const nextEdits = { ...state[action.recordId], ...action.edits }; Object.keys(nextEdits).forEach((key) => { if (nextEdits[key] === void 0) { delete nextEdits[key]; } }); return { ...state, [action.recordId]: nextEdits }; } return state; }, saving: (state = {}, action) => { switch (action.type) { case "SAVE_ENTITY_RECORD_START": case "SAVE_ENTITY_RECORD_FINISH": return { ...state, [action.recordId]: { pending: action.type === "SAVE_ENTITY_RECORD_START", error: action.error, isAutosave: action.isAutosave } }; } return state; }, deleting: (state = {}, action) => { switch (action.type) { case "DELETE_ENTITY_RECORD_START": case "DELETE_ENTITY_RECORD_FINISH": return { ...state, [action.recordId]: { pending: action.type === "DELETE_ENTITY_RECORD_START", error: action.error } }; } return state; }, revisions: (state = {}, action) => { if (action.type === "RECEIVE_ITEM_REVISIONS") { const recordKey = action.recordKey; delete action.recordKey; const newState = queriedDataReducer(state[recordKey], { ...action, type: "RECEIVE_ITEMS" }); return { ...state, [recordKey]: newState }; } if (action.type === "REMOVE_ITEMS") { return Object.fromEntries( Object.entries(state).filter( ([id]) => !action.itemIds.some((itemId) => { if (Number.isInteger(itemId)) { return itemId === +id; } return itemId === id; }) ) ); } return state; } }) ); } function entitiesConfig(state = rootEntitiesConfig, action) { switch (action.type) { case "ADD_ENTITIES": return [...state, ...action.entities]; } return state; } var entities = (state = {}, action) => { const newConfig = entitiesConfig(state.config, action); let entitiesDataReducer = state.reducer; if (!entitiesDataReducer || newConfig !== state.config) { const entitiesByKind = newConfig.reduce((acc, record) => { const { kind } = record; if (!acc[kind]) { acc[kind] = []; } acc[kind].push(record); return acc; }, {}); entitiesDataReducer = combineReducers( Object.fromEntries( Object.entries(entitiesByKind).map( ([kind, subEntities]) => { const kindReducer = combineReducers( Object.fromEntries( subEntities.map((entityConfig) => [ entityConfig.name, entity(entityConfig) ]) ) ); return [kind, kindReducer]; } ) ) ); } const newData = entitiesDataReducer(state.records, action); if (newData === state.records && newConfig === state.config && entitiesDataReducer === state.reducer) { return state; } return { reducer: entitiesDataReducer, records: newData, config: newConfig }; }; function undoManager(state = createUndoManager()) { return state; } function editsReference(state = {}, action) { switch (action.type) { case "EDIT_ENTITY_RECORD": case "UNDO": case "REDO": return {}; } return state; } function embedPreviews(state = {}, action) { switch (action.type) { case "RECEIVE_EMBED_PREVIEW": const { url, preview } = action; return { ...state, [url]: preview }; } return state; } function userPermissions(state = {}, action) { switch (action.type) { case "RECEIVE_USER_PERMISSION": return { ...state, [action.key]: action.isAllowed }; case "RECEIVE_USER_PERMISSIONS": return { ...state, ...action.permissions }; } return state; } function autosaves(state = {}, action) { switch (action.type) { case "RECEIVE_AUTOSAVES": const { postId, autosaves: autosavesData } = action; return { ...state, [postId]: autosavesData }; } return state; } function blockPatterns(state = [], action) { switch (action.type) { case "RECEIVE_BLOCK_PATTERNS": return action.patterns; } return state; } function blockPatternCategories(state = [], action) { switch (action.type) { case "RECEIVE_BLOCK_PATTERN_CATEGORIES": return action.categories; } return state; } function userPatternCategories(state = [], action) { switch (action.type) { case "RECEIVE_USER_PATTERN_CATEGORIES": return action.patternCategories; } return state; } function navigationFallbackId(state = null, action) { switch (action.type) { case "RECEIVE_NAVIGATION_FALLBACK_ID": return action.fallbackId; } return state; } function themeGlobalStyleRevisions(state = {}, action) { switch (action.type) { case "RECEIVE_THEME_GLOBAL_STYLE_REVISIONS": return { ...state, [action.currentId]: action.revisions }; } return state; } function defaultTemplates(state = {}, action) { switch (action.type) { case "RECEIVE_DEFAULT_TEMPLATE": return { ...state, [JSON.stringify(action.query)]: action.templateId }; } return state; } function registeredPostMeta(state = {}, action) { switch (action.type) { case "RECEIVE_REGISTERED_POST_META": return { ...state, [action.postType]: action.registeredPostMeta }; } return state; } function editorSettings(state = null, action) { switch (action.type) { case "RECEIVE_EDITOR_SETTINGS": return action.settings; } return state; } function editorAssets(state = null, action) { switch (action.type) { case "RECEIVE_EDITOR_ASSETS": return action.assets; } return state; } var reducer_default = combineReducers({ users, currentTheme, currentGlobalStylesId, currentUser, themeGlobalStyleVariations, themeBaseGlobalStyles, themeGlobalStyleRevisions, entities, editsReference, undoManager, embedPreviews, userPermissions, autosaves, blockPatterns, blockPatternCategories, userPatternCategories, navigationFallbackId, defaultTemplates, registeredPostMeta, editorSettings, editorAssets }); export { autosaves, blockPatternCategories, blockPatterns, currentGlobalStylesId, currentTheme, currentUser, reducer_default as default, defaultTemplates, editorAssets, editorSettings, editsReference, embedPreviews, entities, entitiesConfig, navigationFallbackId, registeredPostMeta, themeBaseGlobalStyles, themeGlobalStyleRevisions, themeGlobalStyleVariations, undoManager, userPatternCategories, userPermissions, users }; //# sourceMappingURL=reducer.js.map