@craftercms/studio-ui
Version:
Services, components, models & utils to build CrafterCMS authoring extensions.
303 lines (301 loc) • 10.8 kB
JavaScript
/*
* 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 {
clearClipboard,
completeDetailedItem,
fetchDetailedItem,
fetchDetailedItemComplete,
fetchDetailedItems,
fetchDetailedItemsComplete,
fetchQuickCreateList,
fetchQuickCreateListComplete,
fetchQuickCreateListFailed,
fetchSandboxItem,
fetchSandboxItemComplete,
fetchSandboxItems,
fetchSandboxItemsComplete,
reloadDetailedItem,
restoreClipboard,
setClipboard,
updateItemsByPath
} from '../actions/content';
import {
pathNavigatorBulkFetchPathComplete,
pathNavigatorConditionallySetPathComplete,
pathNavigatorFetchParentItemsComplete,
pathNavigatorFetchPathComplete
} from '../actions/pathNavigator';
import { parseSandBoxItemToDetailedItem } from '../../utils/content';
import { createLookupTable, reversePluckProps } from '../../utils/object';
import { changeSiteComplete } from '../actions/sites';
import {
pathNavigatorTreeBulkFetchPathChildrenComplete,
pathNavigatorTreeBulkRestoreComplete,
pathNavigatorTreeFetchPathChildrenComplete,
pathNavigatorTreeFetchPathPageComplete,
pathNavigatorTreeRestoreComplete
} from '../actions/pathNavigatorTree';
import { STATE_LOCKED_MASK } from '../../utils/constants';
import { deleteContentEvent, deleteContentEvents, lockContentEvent, moveContentEvent } from '../actions/system';
const initialState = {
quickCreate: {
error: null,
isFetching: false,
items: null
},
itemsByPath: {},
clipboard: null,
itemsBeingFetchedByPath: {}
};
const updateItemByPath = (state, { payload }) => {
const { parent, children } = payload;
const nextByPath = {
...state.itemsByPath,
...createLookupTable(parseSandBoxItemToDetailedItem(children, state.itemsByPath), 'path')
};
if (children.levelDescriptor) {
nextByPath[children.levelDescriptor.path] = parseSandBoxItemToDetailedItem(
children.levelDescriptor,
state.itemsByPath[children.levelDescriptor.path]
);
}
if (parent) {
nextByPath[parent.path] = parent;
}
return {
...state,
itemsByPath: nextByPath
};
};
const updateItemsByPaths = (state, { payload: { paths } }) => {
let nextByPath = state.itemsByPath;
paths.forEach((path) => {
nextByPath = {
...nextByPath,
...updateItemByPath({ ...state, itemsByPath: nextByPath }, { payload: path }).itemsByPath
};
});
return {
...state,
itemsByPath: nextByPath
};
};
const updateItemsBeingFetchedByPath = (state, { payload: { path } }) => {
state.itemsBeingFetchedByPath[path] = true;
};
const updateItemsBeingFetchedByPaths = (state, { payload: { paths } }) => {
paths.forEach((path) => {
state.itemsBeingFetchedByPath[path] = true;
});
};
const updateItemsFromRestoredTree = (state, payload) => {
const { children, items } = payload;
let nextByPath = {};
Object.values(children).forEach((children) => {
Object.assign(nextByPath, createLookupTable(parseSandBoxItemToDetailedItem(children, state.itemsByPath), 'path'));
if (children.levelDescriptor) {
nextByPath[children.levelDescriptor.path] = parseSandBoxItemToDetailedItem(
children.levelDescriptor,
state.itemsByPath[children.levelDescriptor.path]
);
}
});
items.forEach((item) => {
nextByPath[item.path] = item;
});
return { ...state, itemsByPath: { ...state.itemsByPath, ...nextByPath } };
};
const reducer = createReducer(initialState, (builder) => {
builder
.addCase(fetchQuickCreateList, (state) => ({
...state,
quickCreate: {
...state.quickCreate,
isFetching: true
}
}))
.addCase(fetchQuickCreateListComplete, (state, { payload }) => ({
...state,
quickCreate: {
...state.quickCreate,
items: payload,
isFetching: false
}
}))
.addCase(fetchQuickCreateListFailed, (state, error) => ({
...state,
quickCreate: {
...state.quickCreate,
isFetching: false,
error: error.payload.response
}
}))
.addCase(fetchDetailedItem, updateItemsBeingFetchedByPath)
.addCase(reloadDetailedItem, updateItemsBeingFetchedByPath)
.addCase(completeDetailedItem, updateItemsBeingFetchedByPath)
.addCase(fetchSandboxItem, updateItemsBeingFetchedByPath)
.addCase(fetchSandboxItems, updateItemsBeingFetchedByPaths)
.addCase(fetchSandboxItemsComplete, (state, { payload: { items } }) => {
items.forEach((item) => {
const path = item.path;
state.itemsByPath[path] = parseSandBoxItemToDetailedItem(item, state.itemsByPath[item.path]);
delete state.itemsBeingFetchedByPath[path];
});
})
.addCase(fetchDetailedItemComplete, (state, { payload }) => ({
...state,
itemsByPath: {
...state.itemsByPath,
[payload.path]: payload
},
itemsBeingFetchedByPath: {
...reversePluckProps(state.itemsBeingFetchedByPath, payload.path)
}
}))
.addCase(fetchDetailedItems, updateItemsBeingFetchedByPaths)
.addCase(fetchDetailedItemsComplete, (state, { payload: { items } }) => {
items.forEach((item) => {
const path = item.path;
state.itemsByPath[path] = item;
delete state.itemsBeingFetchedByPath[path];
});
})
.addCase(fetchSandboxItemComplete, (state, { payload: { item } }) => {
const path = item.path;
state.itemsByPath[path] = parseSandBoxItemToDetailedItem(item, state.itemsByPath[item.path]);
state.itemsBeingFetchedByPath[path] = false;
})
.addCase(restoreClipboard, (state, { payload }) => ({
...state,
clipboard: payload
}))
.addCase(setClipboard, (state, { payload }) => ({
...state,
clipboard: payload
}))
.addCase(clearClipboard, (state) => ({
...state,
clipboard: null
}))
.addCase(pathNavigatorConditionallySetPathComplete, updateItemByPath)
.addCase(pathNavigatorFetchPathComplete, updateItemByPath)
.addCase(pathNavigatorBulkFetchPathComplete, updateItemsByPaths)
.addCase(pathNavigatorFetchParentItemsComplete, (state, { payload: { items, children } }) => {
return {
...state,
itemsByPath: {
...state.itemsByPath,
...createLookupTable(parseSandBoxItemToDetailedItem(children, state.itemsByPath), 'path'),
...(children.levelDescriptor && {
[children.levelDescriptor.path]: parseSandBoxItemToDetailedItem(
children.levelDescriptor,
state.itemsByPath[children.levelDescriptor.path]
)
}),
...createLookupTable(
items.reduce((items, item) => {
if (state.itemsByPath[item.path]?.live) {
item.live = state.itemsByPath[item.path].live;
item.staging = state.itemsByPath[item.path].staging;
}
return items;
}, items),
'path'
)
}
};
})
.addCase(pathNavigatorTreeFetchPathChildrenComplete, updateItemByPath)
.addCase(pathNavigatorTreeBulkFetchPathChildrenComplete, updateItemsByPaths)
.addCase(pathNavigatorTreeFetchPathPageComplete, updateItemByPath)
.addCase(pathNavigatorTreeRestoreComplete, (state, action) => {
const { payload } = action;
return updateItemsFromRestoredTree(state, payload);
})
.addCase(pathNavigatorTreeBulkRestoreComplete, (state, { payload: { trees } }) => {
let nextByPath = state.itemsByPath;
trees.forEach((tree) => {
nextByPath = {
...nextByPath,
...updateItemsFromRestoredTree({ ...state, itemsByPath: nextByPath }, tree).itemsByPath
};
});
return {
...state,
itemsByPath: nextByPath
};
})
.addCase(updateItemsByPath, (state, { payload }) => {
return updateItemByPath(state, { payload: { parent: null, children: payload.items } });
})
.addCase(changeSiteComplete, () => initialState)
.addCase(lockContentEvent, (state, { payload }) => {
const { targetPath: path, user, locked } = payload;
if (
!state.itemsByPath[path] ||
(locked && state.itemsByPath[path].stateMap.locked) ||
(!locked && !state.itemsByPath[path].stateMap.locked)
) {
return state;
}
const updatedState = {
...state,
itemsByPath: {
...state.itemsByPath,
[path]: {
...state.itemsByPath[path],
lockOwner: locked ? user : null,
state: locked
? state.itemsByPath[path].state + STATE_LOCKED_MASK
: state.itemsByPath[path].state - STATE_LOCKED_MASK,
stateMap: { ...state.itemsByPath[path].stateMap, locked }
}
}
};
return updatedState;
})
.addCase(deleteContentEvent, (state, { payload: { targetPath } }) => {
delete state.itemsByPath[targetPath];
delete state.itemsBeingFetchedByPath[targetPath];
})
.addCase(deleteContentEvents, (state, { payload: { targetPaths } }) => {
targetPaths.forEach((targetPath) => {
delete state.itemsByPath[targetPath];
delete state.itemsBeingFetchedByPath[targetPath];
});
})
.addCase(moveContentEvent, (state, { payload: { sourcePath } }) => {
delete state.itemsByPath[sourcePath];
delete state.itemsBeingFetchedByPath[sourcePath];
});
});
export default reducer;