@craftercms/studio-ui
Version:
Services, components, models & utils to build CrafterCMS authoring extensions.
236 lines (234 loc) • 8.64 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 React, { useState } from 'react';
import { useDispatch } from 'react-redux';
import { useActiveSiteId } from '../../hooks/useActiveSiteId';
import { FormattedMessage, useIntl } from 'react-intl';
import { createFile, fetchSandboxItem } from '../../services/content';
import { showErrorDialog } from '../../state/reducers/dialogs/error';
import { validateActionPolicy } from '../../services/sites';
import DialogBody from '../DialogBody/DialogBody';
import TextField from '@mui/material/TextField';
import DialogFooter from '../DialogFooter/DialogFooter';
import SecondaryButton from '../SecondaryButton';
import PrimaryButton from '../PrimaryButton';
import ConfirmDialog from '../ConfirmDialog';
import { translations } from './translations';
import { updateCreateFileDialog, updateCreateFolderDialog } from '../../state/actions/dialogs';
import { batchActions } from '../../state/actions/misc';
import useEnhancedDialogContext from '../EnhancedDialog/useEnhancedDialogContext';
import useItemsByPath from '../../hooks/useItemsByPath';
import { UNDEFINED } from '../../utils/constants';
import { isBlank } from '../../utils/string';
import { applyAssetNameRules } from '../../utils/content';
import { getFileNameWithExtensionForItemType, pickExtensionForItemType } from '../../utils/path';
export function CreateFileDialogContainer(props) {
const { onClose, onCreated, type, path, allowBraces } = props;
const { isSubmitting, hasPendingChanges } = useEnhancedDialogContext();
const [name, setName] = useState('');
const [confirm, setConfirm] = useState(null);
const dispatch = useDispatch();
const site = useActiveSiteId();
const { formatMessage } = useIntl();
const itemLookup = useItemsByPath();
const computedFilePath = `${path}/${getFileNameWithExtensionForItemType(type, name)}`;
// When calling the validation API, we need to check if the item with the suggested name exists. This is an extra validation for the
// fileExists const.
const [itemExists, setItemExists] = useState(false);
const fileExists = itemExists || itemLookup[computedFilePath] !== UNDEFINED;
const isValid = !isBlank(name) && !fileExists;
const onError = (error) => {
dispatch(
batchActions([
showErrorDialog({ error }),
updateCreateFileDialog({
isSubmitting: false
})
])
);
};
const onCreateFile = (site, path, fileName) => {
createFile(site, path, fileName).subscribe({
next() {
onCreated?.({ path, fileName, mode: pickExtensionForItemType(type), openOnSuccess: true });
dispatch(
updateCreateFileDialog({
hasPendingChanges: false,
isSubmitting: false
})
);
},
error: onError
});
};
const onSubmit = () => {
dispatch(
updateCreateFileDialog({
isSubmitting: true
})
);
if (name) {
validateActionPolicy(site, {
type: 'CREATE',
target: `${path}/${name}`
}).subscribe({
next: ({ allowed, modifiedValue, message }) => {
if (allowed) {
const fileName = getFileNameWithExtensionForItemType(type, name);
const pathToCheckExists = modifiedValue ?? `${path}/${fileName}`;
setItemExists(false);
fetchSandboxItem(site, pathToCheckExists).subscribe({
next: (item) => {
if (item) {
setItemExists(true);
dispatch(updateCreateFileDialog({ isSubmitting: false }));
} else {
if (modifiedValue) {
setConfirm({ body: message });
} else {
onCreateFile(site, path, fileName);
}
}
},
error: onError
});
} else {
setConfirm({
error: true,
body: formatMessage(translations.policyError, { fileName: name, detail: message })
});
dispatch(
updateCreateFolderDialog({
isSubmitting: false
})
);
}
},
error: onError
});
}
};
const onConfirm = () => {
const fileName = getFileNameWithExtensionForItemType(type, name);
onCreateFile(site, path, fileName);
};
const onConfirmCancel = () => {
setConfirm(null);
dispatch(
updateCreateFileDialog({
isSubmitting: false
})
);
};
const onInputChanges = (value) => {
setName(value);
setItemExists(false);
const newHasPending = !isBlank(value);
hasPendingChanges !== newHasPending &&
dispatch(
updateCreateFileDialog({
hasPendingChanges: newHasPending
})
);
};
return React.createElement(
React.Fragment,
null,
React.createElement(
DialogBody,
null,
React.createElement(
'form',
{
onSubmit: (e) => {
e.preventDefault();
if (isValid) {
onSubmit();
}
}
},
React.createElement(TextField, {
label: React.createElement(FormattedMessage, {
id: 'createFileDialog.fileName',
defaultMessage: 'File Name'
}),
value: name,
fullWidth: true,
autoFocus: true,
required: true,
error: (!name && isSubmitting !== null) || fileExists,
placeholder: formatMessage(translations.placeholder),
helperText: fileExists
? React.createElement(FormattedMessage, {
id: 'createFileDialog.fileAlreadyExists',
defaultMessage: 'A file with that name already exists'
})
: !name && isSubmitting
? React.createElement(FormattedMessage, {
id: 'createFileDialog.fileNameRequired',
defaultMessage: 'File name is required.'
})
: React.createElement(FormattedMessage, {
id: 'createFileDialog.helperText',
defaultMessage: 'Consisting of letters, numbers, dot (.), dash (-) and underscore (_).'
}),
disabled: isSubmitting,
margin: 'normal',
InputLabelProps: {
shrink: true
},
onChange: (event) => onInputChanges(applyAssetNameRules(event.target.value, { allowBraces }))
})
)
),
React.createElement(
DialogFooter,
null,
React.createElement(
SecondaryButton,
{ onClick: (e) => onClose(e, null), disabled: isSubmitting },
React.createElement(FormattedMessage, { id: 'words.close', defaultMessage: 'Close' })
),
React.createElement(
PrimaryButton,
{ onClick: onSubmit, disabled: isSubmitting || !isValid, loading: isSubmitting },
React.createElement(FormattedMessage, { id: 'words.create', defaultMessage: 'Create' })
)
),
React.createElement(ConfirmDialog, {
open: Boolean(confirm),
body: confirm?.body,
onOk: confirm?.error ? onConfirmCancel : onConfirm,
onCancel: confirm?.error ? null : onConfirmCancel
})
);
}
export default CreateFileDialogContainer;