UNPKG

@craftercms/studio-ui

Version:

Services, components, models & utils to build CrafterCMS authoring extensions.

268 lines (266 loc) 11.2 kB
/* * Copyright (C) 2007-2022 Crafter Software Corporation. All Rights Reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ /* * Copyright (C) 2007-2022 Crafter Software Corporation. All Rights Reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ import { createReducer } from '@reduxjs/toolkit'; import { pathNavigatorTreeCollapsePath, pathNavigatorTreeExpandPath, pathNavigatorTreeFetchPathChildren, pathNavigatorTreeFetchPathChildrenComplete, pathNavigatorTreeFetchPathPage, pathNavigatorTreeFetchPathPageComplete, pathNavigatorTreeInit, pathNavigatorTreeRestore, pathNavigatorTreeRestoreComplete, pathNavigatorTreeRootMissing, pathNavigatorTreeSetKeyword, pathNavigatorTreeToggleCollapsed, pathNavigatorTreeUpdate } from '../actions/pathNavigatorTree'; import { changeSite } from '../actions/sites'; import { fetchSiteUiConfig } from '../actions/configuration'; import { reversePluckProps } from '../../utils/object'; import { fetchSandboxItemComplete } from '../actions/content'; import { getIndividualPaths, getParentPath, withoutIndex } from '../../utils/path'; import { deleteContentEvent, moveContentEvent } from '../actions/system'; import { createPresenceTable } from '../../utils/array'; export function contentAndDeleteEventForEachApplicableTree(state, targetPath, callbackFn) { const parentPathOfTargetPath = getParentPath(targetPath); Object.values(state).forEach((tree) => { if (tree.rootPath === targetPath || targetPath in tree.totalByPath || parentPathOfTargetPath in tree.totalByPath) { callbackFn(tree, targetPath, parentPathOfTargetPath); } }); } const expandPath = (state, { payload: { id, path } }) => { const chunk = state[id]; if (path.startsWith(withoutIndex(chunk.rootPath)) && !chunk.expanded.includes(path)) { const paths = getIndividualPaths(path, chunk.rootPath); const expandedPathLookup = createPresenceTable(chunk.expanded); paths.forEach((path) => { !expandedPathLookup[path] && !expandedPathLookup[`${path}/index.xml`] && chunk.expanded.push(path); }); } }; export function deleteItemFromState(tree, targetPath) { let parentPath = getParentPath(targetPath); let totalByPath = tree.totalByPath; let childrenByParentPath = tree.childrenByParentPath; // Remove deleted item from the parent path's children childrenByParentPath[parentPath] = childrenByParentPath[parentPath].filter((childPath) => targetPath !== childPath); // Discount deleted item from parent path child count totalByPath[parentPath] = totalByPath[parentPath] - 1; // TODO: Remove. if (totalByPath[parentPath] < 0) { debugger; } // Remove item delete totalByPath[targetPath]; delete tree.keywordByPath[targetPath]; delete tree.offsetByPath[targetPath]; // Remove children of the item delete childrenByParentPath[targetPath]; // Remove item from expanded. Parent too if pertinent. tree.expanded = tree.expanded.filter( // If the parent is left without children, remove from expanded too. totalByPath[parentPath] === 0 ? (expandedPath) => expandedPath !== targetPath || expandedPath !== parentPath : (expandedPath) => expandedPath !== targetPath ); } const reducer = createReducer( {}, { // region pathNavigatorTreeInit [pathNavigatorTreeInit.type]: (state, action) => { const { payload: { id, rootPath, collapsed = true, limit, expanded = [], keywordByPath = {}, excludes = null, systemTypes = null } } = action; state[id] = { id, rootPath, collapsed, limit, expanded, childrenByParentPath: {}, offsetByPath: {}, keywordByPath, totalByPath: {}, excludes, error: null, isRootPathMissing: false, systemTypes }; }, // endregion [pathNavigatorTreeExpandPath.type]: expandPath, [pathNavigatorTreeCollapsePath.type]: (state, { payload: { id, path } }) => { state[id].expanded = state[id].expanded.filter((expanded) => !expanded.startsWith(path)); state[id].offsetByPath = Object.assign(Object.assign({}, state[id].offsetByPath), { [path]: 0 }); state[id].childrenByParentPath = Object.assign(Object.assign({}, state[id].childrenByParentPath), { [path]: state[id].childrenByParentPath[path].splice(0, state[id].limit) }); }, [pathNavigatorTreeToggleCollapsed.type]: (state, { payload: { id, collapsed } }) => { state[id].collapsed = collapsed; }, [pathNavigatorTreeSetKeyword.type]: (state, { payload: { id, path, keyword } }) => { state[id].keywordByPath[path] = keyword; }, [pathNavigatorTreeFetchPathChildren.type]: (state, action) => { const { expand = true } = action.payload; expand && expandPath(state, action); }, [pathNavigatorTreeFetchPathChildrenComplete.type]: (state, { payload: { id, parentPath, children, options } }) => { const chunk = state[id]; chunk.totalByPath[parentPath] = children.total; chunk.childrenByParentPath[parentPath] = []; children.forEach((item) => { chunk.childrenByParentPath[parentPath].push(item.path); chunk.totalByPath[item.path] = item.childrenCount; }); if (children.levelDescriptor) { chunk.childrenByParentPath[parentPath].push(children.levelDescriptor.path); chunk.totalByPath[children.levelDescriptor.path] = 0; } // If the expanded node has no children and is not filtered, it's a // leaf node and there's no point keeping it in `expanded` if (children.length === 0 && !(options === null || options === void 0 ? void 0 : options.keyword)) { chunk.expanded = chunk.expanded.filter((path) => path !== parentPath); } }, [pathNavigatorTreeFetchPathPage.type]: (state, { payload: { id, path } }) => { state[id].offsetByPath[path] = state[id].offsetByPath[path] ? state[id].offsetByPath[path] + state[id].limit : state[id].limit; }, [pathNavigatorTreeFetchPathPageComplete.type]: (state, { payload: { id, parentPath, children, options } }) => { const chunk = state[id]; chunk.totalByPath[parentPath] = children.total; if (children.levelDescriptor) { chunk.totalByPath[children.levelDescriptor.path] = 0; } children.forEach((item) => { chunk.childrenByParentPath[parentPath].push(item.path); chunk.totalByPath[item.path] = item.childrenCount; }); }, [pathNavigatorTreeUpdate.type]: (state, { payload }) => { return Object.assign(Object.assign({}, state), { [payload.id]: Object.assign(Object.assign({}, state[payload.id]), reversePluckProps(payload, 'id')) }); }, [pathNavigatorTreeRestore.type]: (state, { payload: { id } }) => { state[id].isRootPathMissing = false; }, // region pathNavigatorTreeRestoreComplete // Assumption: this reducer is a reset. Not suitable for partial updates. [pathNavigatorTreeRestoreComplete.type]: (state, { payload: { id, children, items } }) => { const chunk = state[id]; chunk.childrenByParentPath = {}; chunk.totalByPath = {}; const childrenByParentPath = chunk.childrenByParentPath; const totalByPath = chunk.totalByPath; const offsetByPath = chunk.offsetByPath; items.forEach((item) => { totalByPath[item.path] = item.childrenCount; }); Object.keys(children).forEach((parentPath) => { var _a; const childrenOfPath = children[parentPath]; if (childrenOfPath.length || childrenOfPath.levelDescriptor) { childrenByParentPath[parentPath] = []; if (childrenOfPath.levelDescriptor) { childrenByParentPath[parentPath].push(childrenOfPath.levelDescriptor.path); totalByPath[childrenOfPath.levelDescriptor.path] = 0; } childrenOfPath.forEach((child) => { childrenByParentPath[parentPath].push(child.path); totalByPath[child.path] = child.childrenCount; }); } // Should we account here for the level descriptor (LD)? if there's a LD, add 1 to the total? totalByPath[parentPath] = childrenOfPath.total; offsetByPath[parentPath] = (_a = offsetByPath[parentPath]) !== null && _a !== void 0 ? _a : 0; // If the expanded node is filtered or has children it means, it's not a leaf, // and we should keep it in 'expanded'. // if (chunk.keywordByPath[parentPath] || childrenByParentPath[parentPath].length) { // chunk.expanded.push(parentPath); // } }); }, // endregion [changeSite.type]: () => ({}), [fetchSiteUiConfig.type]: () => ({}), // region fetchSandboxItemComplete [fetchSandboxItemComplete.type]: (state, { payload: { item } }) => { const path = item.path; Object.values(state).forEach((tree) => { if (path in tree.totalByPath) { tree.totalByPath[path] = item.childrenCount; } }); }, // endregion [pathNavigatorTreeRootMissing.type]: (state, { payload: { id } }) => { state[id].isRootPathMissing = true; }, // region deleteContentEvent [deleteContentEvent.type]: (state, { payload: { targetPath } }) => { contentAndDeleteEventForEachApplicableTree(state, targetPath, (tree, targetPath, parentPathOfTargetPath) => { if (targetPath === tree.rootPath) { tree.isRootPathMissing = true; } else if (parentPathOfTargetPath in tree.totalByPath) { deleteItemFromState(tree, targetPath); } }); }, // endregion [moveContentEvent.type]: (state, { payload: { sourcePath } }) => { Object.values(state).forEach((tree) => { if (tree.rootPath === sourcePath) { tree.isRootPathMissing = true; } else if (sourcePath in tree.totalByPath) { deleteItemFromState(tree, sourcePath); } }); } } ); export default reducer;