UNPKG

@craftercms/studio-ui

Version:

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

464 lines (462 loc) 16 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 { ofType } from 'redux-observable'; import { ignoreElements, map, mergeMap, tap, throttleTime, withLatestFrom } from 'rxjs/operators'; import { catchAjaxError } from '../../utils/ajax'; import { checkPathExistence, fetchChildrenByPath, fetchItemsByPath, fetchItemWithChildrenByPath } from '../../services/content'; import { getIndividualPaths, getParentPath, getRootPath, withIndex } from '../../utils/path'; import { forkJoin } from 'rxjs'; import { pathNavigatorBackgroundRefresh, pathNavigatorChangeLimit, pathNavigatorChangePage, pathNavigatorConditionallySetPath, pathNavigatorConditionallySetPathComplete, pathNavigatorConditionallySetPathFailed, pathNavigatorFetchParentItems, pathNavigatorFetchParentItemsComplete, pathNavigatorFetchPath, pathNavigatorFetchPathComplete, pathNavigatorFetchPathFailed, pathNavigatorInit, pathNavigatorRefresh, pathNavigatorSetCollapsed, pathNavigatorSetCurrentPath, pathNavigatorSetKeyword, pathNavRootPathMissing } from '../actions/pathNavigator'; import { setStoredPathNavigator } from '../../utils/state'; import { showErrorDialog } from '../reducers/dialogs/error'; import { contentEvent, deleteContentEvent, moveContentEvent, pluginInstalled, publishEvent, workflowEvent } from '../actions/system'; export default [ // region pathNavigatorInit (action$, state$) => action$.pipe( ofType(pathNavigatorInit.type), withLatestFrom(state$), mergeMap( ([ { payload: { id, excludes, rootPath } }, state ]) => checkPathExistence(state.sites.active, rootPath).pipe( map((exists) => exists ? pathNavigatorFetchParentItems({ id, path: state.pathNavigator[id].currentPath, offset: state.pathNavigator[id].offset, keyword: state.pathNavigator[id].keyword, excludes, limit: state.pathNavigator[id].limit }) : pathNavRootPathMissing({ id }) ) ) ) ), // endregion // region pathNavigatorRefresh (action$, state$) => action$.pipe( ofType(pathNavigatorRefresh.type, pathNavigatorBackgroundRefresh.type), withLatestFrom(state$), mergeMap( ([ { type, payload: { id } }, state ]) => fetchItemWithChildrenByPath(state.sites.active, state.pathNavigator[id].currentPath, { keyword: state.pathNavigator[id].keyword, limit: state.pathNavigator[id].limit, offset: state.pathNavigator[id].offset, excludes: state.pathNavigator[id].excludes }).pipe( map(({ item, children }) => pathNavigatorFetchPathComplete({ id, parent: item, children })), catchAjaxError((error) => { if (error.status === 404 && state.pathNavigator[id].rootPath !== state.pathNavigator[id].currentPath) { return pathNavigatorConditionallySetPath({ id, path: state.pathNavigator[id].rootPath }); } else { return pathNavigatorFetchPathFailed({ error, id }); } }) ) ) ), // endregion // region pathNavigatorFetchPath (action$, state$) => action$.pipe( ofType(pathNavigatorFetchPath.type), withLatestFrom(state$), mergeMap( ([ { type, payload: { id, path } }, state ]) => fetchItemWithChildrenByPath(state.sites.active, path, { excludes: state.pathNavigator[id].excludes, limit: state.pathNavigator[id].limit }).pipe( map(({ item, children }) => pathNavigatorFetchPathComplete({ id, parent: item, children })), catchAjaxError( (error) => pathNavigatorFetchPathFailed({ id, error }), (error) => { var _a; return showErrorDialog({ error: (_a = error.response) !== null && _a !== void 0 ? _a : error }); } ) ) ) ), // endregion // region pathNavigatorConditionallySetPath (action$, state$) => action$.pipe( ofType(pathNavigatorConditionallySetPath.type), withLatestFrom(state$), mergeMap( ([ { payload: { id, path } }, state ]) => fetchItemWithChildrenByPath(state.sites.active, path, { excludes: state.pathNavigator[id].excludes, limit: state.pathNavigator[id].limit }).pipe( map(({ item, children }) => pathNavigatorConditionallySetPathComplete({ id, path, parent: item, children }) ), catchAjaxError( (error) => pathNavigatorConditionallySetPathFailed({ id, error }), (error) => { var _a; return showErrorDialog({ error: (_a = error.response) !== null && _a !== void 0 ? _a : error }); } ) ) ) ), // endregion // region pathNavigatorSetCurrentPath (action$, state$) => action$.pipe( ofType(pathNavigatorSetCurrentPath.type), withLatestFrom(state$), mergeMap( ([ { payload: { id, path } }, state ]) => fetchItemWithChildrenByPath(state.sites.active, path).pipe( map(({ item, children }) => pathNavigatorFetchPathComplete({ id, parent: item, children })), catchAjaxError((error) => pathNavigatorFetchPathFailed({ error, id })) ) ) ), // endregion // region pathNavigatorSetKeyword (action$, state$) => action$.pipe( ofType(pathNavigatorSetKeyword.type), withLatestFrom(state$), mergeMap( ([ { type, payload: { id, keyword } }, state ]) => fetchChildrenByPath(state.sites.active, state.pathNavigator[id].currentPath, { keyword, limit: state.pathNavigator[id].limit }).pipe( map((children) => pathNavigatorFetchPathComplete({ id, parent: state.content.itemsByPath[state.pathNavigator[id].currentPath], children }) ), catchAjaxError((error) => pathNavigatorFetchPathFailed({ error, id })) ) ) ), // endregion // region pathNavigatorChangePage (action$, state$) => action$.pipe( ofType(pathNavigatorChangePage.type, pathNavigatorChangeLimit.type), withLatestFrom(state$), mergeMap( ([ { type, payload: { id, offset } }, state ]) => fetchChildrenByPath( state.sites.active, state.pathNavigator[id].currentPath, Object.assign( Object.assign( { limit: state.pathNavigator[id].limit }, Boolean(state.pathNavigator[id].keyword) && { keyword: state.pathNavigator[id].keyword } ), { offset } ) ).pipe( map((children) => pathNavigatorFetchPathComplete({ id, children })), catchAjaxError((error) => pathNavigatorFetchPathFailed({ error, id })) ) ) ), // endregion // region pathNavigatorFetchParentItems (action$, state$) => action$.pipe( ofType(pathNavigatorFetchParentItems.type), withLatestFrom(state$), mergeMap( ([ { type, payload: { id, path, excludes, limit, offset, keyword } }, state ]) => { const site = state.sites.active; const parentsPath = getIndividualPaths(path, state.pathNavigator[id].rootPath); if (parentsPath.length > 1) { return forkJoin([ fetchItemsByPath(site, parentsPath, { castAsDetailedItem: true }), fetchChildrenByPath(site, path, { excludes, limit, offset, keyword }) ]).pipe( map(([items, children]) => pathNavigatorFetchParentItemsComplete({ id, items, children })), catchAjaxError((error) => { if (error.status === 404) { return pathNavigatorConditionallySetPath({ id, path: getRootPath(path) }); } else { return pathNavigatorFetchPathFailed({ error, id }); } }) ); } else { return fetchItemWithChildrenByPath(site, path, { excludes, limit, offset, keyword }).pipe( map(({ item, children }) => pathNavigatorFetchPathComplete({ id, parent: item, children })), catchAjaxError((error) => pathNavigatorFetchPathFailed({ error, id })) ); } } ) ), // endregion // region pathNavigatorFetchPathComplete, pathNavigatorConditionallySetPathComplete, pathNavigatorSetCollapsed (action$, state$) => action$.pipe( ofType( pathNavigatorFetchPathComplete.type, pathNavigatorConditionallySetPathComplete.type, pathNavigatorSetCollapsed.type, pathNavigatorChangeLimit.type ), withLatestFrom(state$), tap( ([ { type, payload: { id, parent } }, state ]) => { if ( type !== pathNavigatorConditionallySetPathComplete.type || (parent === null || parent === void 0 ? void 0 : parent.childrenCount) > 0 ) { const uuid = state.sites.byId[state.sites.active].uuid; setStoredPathNavigator(uuid, state.user.username, id, { currentPath: state.pathNavigator[id].currentPath, collapsed: state.pathNavigator[id].collapsed, keyword: state.pathNavigator[id].keyword, offset: state.pathNavigator[id].offset, limit: state.pathNavigator[id].limit }); } } ), ignoreElements() ), // endregion // region contentEvent (action$, state$) => action$.pipe( ofType(contentEvent.type), withLatestFrom(state$), mergeMap(([action, state]) => { // Cases: // a. Item is the current path in the navigator: refresh navigator // b. Item is a direct child of the current path: refresh navigator // b. Item is a direct child of the current path: refresh navigator // c. Item is a child of an item on the current path: refresh item's child count const actions = []; const { payload: { targetPath } } = action; const parentPathOfTargetPath = getParentPath(targetPath); const parentOfTargetWithIndex = withIndex(parentPathOfTargetPath); Object.values(state.pathNavigator).forEach((navigator) => { if ( // Case (a) navigator.currentPath === targetPath || // Case (b) navigator.currentPath === parentPathOfTargetPath || navigator.currentPath === parentOfTargetWithIndex ) { actions.push(pathNavigatorBackgroundRefresh({ id: navigator.id })); } /* else if ( // Case (c) - Content epics load any item that's on the state already navigator.currentPath === getParentPath(parentPathOfTargetPath) ) { actions.push(fetchSandboxItem({ path: parentPathOfTargetPath })); } */ }); return actions; }) ), // endregion // region deleteContentEvent (action$, state$) => action$.pipe( ofType(deleteContentEvent.type), withLatestFrom(state$), mergeMap(([action, state]) => { const actions = []; const { payload: { targetPath } } = action; Object.values(state.pathNavigator).forEach((navigator) => { if (!navigator.isRootPathMissing && navigator.currentPath.startsWith(targetPath)) { actions.push(pathNavigatorSetCurrentPath({ id: navigator.id, path: navigator.rootPath })); } }); return actions; }) ), // endregion // region moveContentEvent (action$, state$) => action$.pipe( ofType(moveContentEvent.type), withLatestFrom(state$), mergeMap(([action, state]) => { const actions = []; const { payload: { targetPath, sourcePath } } = action; const parentOfTargetPath = getParentPath(targetPath); const parentOfSourcePath = getParentPath(sourcePath); Object.values(state.pathNavigator).forEach((navigator) => { if (navigator.isRootPathMissing && targetPath === navigator.rootPath) { actions.push(pathNavigatorRefresh({ id: navigator.id })); } else if (!navigator.isRootPathMissing && navigator.currentPath.startsWith(sourcePath)) { actions.push(pathNavigatorSetCurrentPath({ id: navigator.id, path: navigator.rootPath })); } else if (navigator.currentPath === parentOfTargetPath || navigator.currentPath === parentOfSourcePath) { actions.push(pathNavigatorBackgroundRefresh({ id: navigator.id })); } }); return actions; }) ), // endregion // region pluginInstalled (action$, state$) => action$.pipe( ofType(pluginInstalled.type), throttleTime(500), withLatestFrom(state$), mergeMap(([, state]) => { const actions = []; Object.values(state.pathNavigator).forEach((tree) => { if (['/templates', '/scripts', '/static-assets'].includes(getRootPath(tree.rootPath))) { actions.push(pathNavigatorBackgroundRefresh({ id: tree.id })); } }); return actions; }) ), // endregion // region publishEvent, workflowEvent (action$, state$) => action$.pipe( ofType(publishEvent.type, workflowEvent.type), throttleTime(500), withLatestFrom(state$), mergeMap(([, state]) => { const actions = []; Object.values(state.pathNavigator).forEach((tree) => { actions.push(pathNavigatorBackgroundRefresh({ id: tree.id })); }); return actions; }) ) // endregion ];