@craftercms/studio-ui
Version:
Services, components, models & utils to build CrafterCMS authoring extensions.
247 lines (245 loc) • 10.2 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/>.
*/
import React, { useEffect, useMemo, useState } from 'react';
import { useDispatch } from 'react-redux';
import { useActiveSiteId } from '../../hooks/useActiveSiteId';
import { FormattedMessage, useIntl } from 'react-intl';
import { getParentPath, getRootPath, withoutIndex } from '../../utils/path';
import { createFolder, fetchSandboxItem, renameFolder } from '../../services/content';
import { batchActions } from '../../state/actions/misc';
import { updateCreateFolderDialog } from '../../state/actions/dialogs';
import { showErrorDialog } from '../../state/reducers/dialogs/error';
import { validateActionPolicy } from '../../services/sites';
import { translations } from './translations';
import DialogBody from '../DialogBody/DialogBody';
import SingleItemSelector from '../SingleItemSelector';
import TextField from '@mui/material/TextField';
import DialogFooter from '../DialogFooter/DialogFooter';
import SecondaryButton from '../SecondaryButton';
import PrimaryButton from '../PrimaryButton';
import ConfirmDialog from '../ConfirmDialog/ConfirmDialog';
import useItemsByPath from '../../hooks/useItemsByPath';
import { UNDEFINED } from '../../utils/constants';
import { isBlank } from '../../utils/string';
import { useEnhancedDialogContext } from '../EnhancedDialog';
import { fetchSandboxItemComplete } from '../../state/actions/content';
import { switchMap, tap } from 'rxjs';
import { filter } from 'rxjs/operators';
import { applyFolderNameRules } from '../../utils/content';
import { useFetchItem } from '../../hooks/useFetchItem';
export function CreateFolderContainer(props) {
const { onClose, onCreated, onRenamed, rename = false, value = '', allowBraces = false } = props;
const { isSubmitting, hasPendingChanges } = useEnhancedDialogContext();
const [name, setName] = useState(value);
const [confirm, setConfirm] = useState(null);
const dispatch = useDispatch();
const site = useActiveSiteId();
const { formatMessage } = useIntl();
const [openSelector, setOpenSelector] = useState(false);
const [selectedItem, setSelectedItem] = useState(null);
const path = useMemo(() => {
return selectedItem ? withoutIndex(selectedItem.path) : withoutIndex(props.path);
}, [props.path, selectedItem]);
// When folder name changes, path prop will still be the previous one, and useDetailedItem will try to re-fetch the
// non-existing item (old folder name path), so we will only re-fetch when the actual path prop of the component
// changes (useDetailedItemNoState).
const item = useFetchItem(path);
const itemLookupTable = useItemsByPath();
const newFolderPath = `${rename ? getParentPath(path) : path}/${name}`;
const folderExists = rename
? name !== value && itemLookupTable[newFolderPath] !== UNDEFINED
: itemLookupTable[newFolderPath] !== UNDEFINED;
const isValid = !isBlank(name) && !folderExists && (!rename || name !== value);
useEffect(() => {
if (item && rename === false) {
setSelectedItem(item);
}
}, [item, rename]);
const onRenameFolder = (site, path, name) => {
renameFolder(site, path, name).subscribe({
next() {
onRenamed === null || onRenamed === void 0 ? void 0 : onRenamed({ path, name, rename });
dispatch(updateCreateFolderDialog({ isSubmitting: false, hasPendingChanges: false }));
},
error(response) {
dispatch(showErrorDialog({ error: response }));
}
});
};
const onCreateFolder = (site, path, name) => {
fetchSandboxItem(site, `${path}/${name}`)
.pipe(
tap(
(item) =>
item &&
dispatch(
batchActions([fetchSandboxItemComplete({ item }), updateCreateFolderDialog({ isSubmitting: false })])
)
),
filter((item) => !item),
switchMap(() => createFolder(site, path, name))
)
.subscribe({
next() {
onCreated === null || onCreated === void 0 ? void 0 : onCreated({ path, name, rename });
dispatch(updateCreateFolderDialog({ isSubmitting: false, hasPendingChanges: false }));
},
error(response) {
dispatch(
batchActions([showErrorDialog({ error: response }), updateCreateFolderDialog({ isSubmitting: false })])
);
}
});
};
const onCreate = () => {
dispatch(updateCreateFolderDialog({ isSubmitting: true }));
if (name) {
const parentPath = rename ? getParentPath(path) : path;
validateActionPolicy(site, {
type: rename ? 'RENAME' : 'CREATE',
target: `${parentPath}/${name}`
}).subscribe(({ allowed, modifiedValue }) => {
if (allowed && modifiedValue) {
setConfirm({
body: formatMessage(translations.createPolicy, { name: modifiedValue.replace(`${path}/`, '') })
});
} else if (allowed) {
if (rename) {
onRenameFolder(site, path, name);
} else {
onCreateFolder(site, path, name);
}
} else {
setConfirm({
error: true,
body: formatMessage(translations.policyError)
});
dispatch(updateCreateFolderDialog({ isSubmitting: false }));
}
});
}
};
const onConfirm = () => {
if (rename) {
onRenameFolder(site, path, name);
} else {
onCreateFolder(site, path, name);
}
};
const onConfirmCancel = () => {
setConfirm(null);
dispatch(updateCreateFolderDialog({ isSubmitting: false }));
};
const onInputChanges = (newValue) => {
setName(newValue);
const newHasPendingChanges = rename ? newValue !== value : !isBlank(newValue);
hasPendingChanges !== newHasPendingChanges &&
dispatch(updateCreateFolderDialog({ hasPendingChanges: newHasPendingChanges }));
};
const itemSelectorFilterChildren = useMemo(() => (item) => item.availableActionsMap.createFolder, []);
const onCloseButtonClick = (e) => onClose(e, null);
return React.createElement(
React.Fragment,
null,
React.createElement(
DialogBody,
null,
selectedItem &&
React.createElement(SingleItemSelector, {
label: React.createElement(FormattedMessage, { id: 'words.location', defaultMessage: 'Location' }),
open: openSelector,
onClose: () => setOpenSelector(false),
onDropdownClick: () => setOpenSelector(!openSelector),
rootPath: getRootPath(path),
selectedItem: selectedItem,
canSelectFolders: true,
onItemClicked: (item) => {
setOpenSelector(false);
setSelectedItem(item);
},
filterChildren: itemSelectorFilterChildren
}),
React.createElement(
'form',
{
onSubmit: (e) => {
e.preventDefault();
if (isValid) {
onCreate();
}
}
},
React.createElement(TextField, {
fullWidth: true,
label: rename
? React.createElement(FormattedMessage, {
id: 'newFolder.rename',
defaultMessage: 'Provide a new folder name'
})
: React.createElement(FormattedMessage, { id: 'newFolder.folderName', defaultMessage: 'Folder Name' }),
value: name,
autoFocus: true,
required: true,
error: (!name && isSubmitting !== null) || folderExists,
placeholder: formatMessage(translations.placeholder),
helperText: folderExists
? React.createElement(FormattedMessage, {
id: 'newFolder.folderAlreadyExists',
defaultMessage: 'A folder with that name already exists'
})
: !name && isSubmitting !== null
? React.createElement(FormattedMessage, {
id: 'newFolder.required',
defaultMessage: 'Folder name is required.'
})
: React.createElement(FormattedMessage, {
id: 'newFolder.helperText',
defaultMessage: 'Consisting of: letters, numbers, dash (-) and underscore (_).'
}),
disabled: isSubmitting,
margin: 'normal',
InputLabelProps: {
shrink: true
},
onChange: (event) => onInputChanges(applyFolderNameRules(event.target.value, { allowBraces }))
})
)
),
React.createElement(
DialogFooter,
null,
React.createElement(
SecondaryButton,
{ onClick: onCloseButtonClick, disabled: isSubmitting },
React.createElement(FormattedMessage, { id: 'words.cancel', defaultMessage: 'Cancel' })
),
React.createElement(
PrimaryButton,
{ onClick: onCreate, disabled: isSubmitting || !isValid, loading: isSubmitting },
rename
? React.createElement(FormattedMessage, { id: 'words.rename', defaultMessage: 'Rename' })
: React.createElement(FormattedMessage, { id: 'words.create', defaultMessage: 'Create' })
)
),
React.createElement(ConfirmDialog, {
open: Boolean(confirm),
body: confirm === null || confirm === void 0 ? void 0 : confirm.body,
onOk: (confirm === null || confirm === void 0 ? void 0 : confirm.error) ? onConfirmCancel : onConfirm,
onCancel: (confirm === null || confirm === void 0 ? void 0 : confirm.error) ? null : onConfirmCancel
})
);
}
export default CreateFolderContainer;