@craftercms/studio-ui
Version:
Services, components, models & utils to build CrafterCMS authoring extensions.
306 lines (304 loc) • 12.6 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 { useEnhancedDialogContext } from '../EnhancedDialog';
import React, { useEffect, useState } from 'react';
import { DialogBody } from '../DialogBody';
import TextField from '@mui/material/TextField';
import { FormattedMessage, useIntl } from 'react-intl';
import { useDispatch } from 'react-redux';
import { fetchRenameAssetDependants, updateRenameAssetDialog } from '../../state/actions/dialogs';
import { applyAssetNameRules, openItemEditor, isEditableAsset } from '../../utils/content';
import useActiveSiteId from '../../hooks/useActiveSiteId';
import { DependenciesList } from '../DependenciesDialog';
import useItemsByPath from '../../hooks/useItemsByPath';
import { getParentPath, getFileNameWithExtensionForItemType } from '../../utils/path';
import { UNDEFINED } from '../../utils/constants';
import { isBlank } from '../../utils/string';
import SecondaryButton from '../SecondaryButton';
import { DialogFooter } from '../DialogFooter';
import PrimaryButton from '../PrimaryButton';
import { validateActionPolicy } from '../../services/sites';
import { ConfirmDialog } from '../ConfirmDialog';
import { renameContent } from '../../services/content';
import { showErrorDialog } from '../../state/reducers/dialogs/error';
import { translations } from '../CreateFileDialog/translations';
import Typography from '@mui/material/Typography';
import FormControlLabel from '@mui/material/FormControlLabel';
import Checkbox from '@mui/material/Checkbox';
import useSpreadState from '../../hooks/useSpreadState';
import Menu from '@mui/material/Menu';
import MenuItem from '@mui/material/MenuItem';
import useSelection from '../../hooks/useSelection';
import Alert from '@mui/material/Alert';
import { LoadingState } from '../LoadingState';
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
import getStyles from './styles';
export function RenameAssetDialogContainer(props) {
const {
onClose,
onRenamed,
path,
value = '',
allowBraces = false,
type,
dependantItems,
fetchingDependantItems
} = props;
const { isSubmitting, hasPendingChanges } = useEnhancedDialogContext();
const [name, setName] = useState(value);
const dispatch = useDispatch();
const itemLookupTable = useItemsByPath();
const newAssetName = type !== 'asset' ? getFileNameWithExtensionForItemType(type, name) : name;
const newAssetPath = `${getParentPath(path)}/${newAssetName}`;
const assetExists = newAssetName !== value && itemLookupTable[newAssetPath] !== UNDEFINED;
const isValid = !isBlank(name) && !assetExists && name !== value;
const siteId = useActiveSiteId();
const [confirm, setConfirm] = useState(null);
const [confirmBrokenReferences, setConfirmBrokenReferences] = useState(false);
const { formatMessage } = useIntl();
const renameDisabled =
isSubmitting ||
!isValid ||
fetchingDependantItems ||
((dependantItems === null || dependantItems === void 0 ? void 0 : dependantItems.length) > 0 &&
!confirmBrokenReferences);
const authoringBase = useSelection((state) => state.env.authoringBase);
const [contextMenu, setContextMenu] = useSpreadState({
el: null,
dependency: null
});
const sx = getStyles();
useEffect(() => {
dispatch(fetchRenameAssetDependants());
}, [dispatch]);
const onInputChanges = (newValue) => {
setName(newValue);
const newHasPendingChanges = newValue !== value;
hasPendingChanges !== newHasPendingChanges &&
dispatch(updateRenameAssetDialog({ hasPendingChanges: newHasPendingChanges }));
};
const onRenameAsset = (siteId, path, name) => {
const fileName = type !== 'asset' ? getFileNameWithExtensionForItemType(type, name) : name;
renameContent(siteId, path, fileName).subscribe({
next() {
onRenamed === null || onRenamed === void 0 ? void 0 : onRenamed({ path, name });
dispatch(updateRenameAssetDialog({ isSubmitting: false, hasPendingChanges: false }));
},
error({ response }) {
dispatch(showErrorDialog({ error: response.response }));
dispatch(updateRenameAssetDialog({ isSubmitting: false }));
}
});
};
const onConfirmCancel = () => {
setConfirm(null);
dispatch(updateRenameAssetDialog({ isSubmitting: false }));
};
const onRename = () => {
dispatch(updateRenameAssetDialog({ isSubmitting: true }));
if (name) {
validateActionPolicy(siteId, {
type: 'RENAME',
target: newAssetPath
}).subscribe(({ allowed, modifiedValue }) => {
if (allowed && modifiedValue) {
setConfirm({
body: formatMessage(translations.createPolicy, { name: modifiedValue.replace(`${path}/`, '') })
});
} else if (allowed) {
onRenameAsset(siteId, path, name);
} else {
setConfirm({
error: true,
body: formatMessage(translations.policyError)
});
}
});
}
};
const handleContextMenuClick = (event, dependency) => {
setContextMenu({
el: event.currentTarget,
dependency
});
};
const handleContextMenuClose = () => {
setContextMenu({
el: null,
dependency: null
});
};
const handleEditorDisplay = (item) => {
openItemEditor(item, authoringBase, siteId, dispatch, fetchRenameAssetDependants());
};
return React.createElement(
React.Fragment,
null,
React.createElement(
DialogBody,
null,
fetchingDependantItems
? React.createElement(LoadingState, {
title: formatMessage(translations.fetchingDependentItems),
styles: { title: { marginTop: 0 } }
})
: dependantItems
? React.createElement(
React.Fragment,
null,
React.createElement(
'form',
{
onSubmit: (e) => {
e.preventDefault();
if (!renameDisabled) {
onRename();
}
}
},
React.createElement(TextField, {
fullWidth: true,
label: React.createElement(FormattedMessage, {
id: 'renameAsset.rename',
defaultMessage: 'Provide a new asset name'
}),
value: name,
autoFocus: true,
required: true,
error: assetExists,
helperText: assetExists
? React.createElement(FormattedMessage, {
id: 'renameAsset.assetAlreadyExists',
defaultMessage: 'An asset with that name already exists.'
})
: !name && isSubmitting
? React.createElement(FormattedMessage, {
id: 'renameAsset.assetNameRequired',
defaultMessage: 'Asset name is required.'
})
: React.createElement(FormattedMessage, {
id: 'renameAsset.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 }))
})
),
dependantItems.length > 0
? React.createElement(
React.Fragment,
null,
React.createElement(
Typography,
{ variant: 'subtitle2', sx: { mt: 1, mb: 1 } },
React.createElement(FormattedMessage, {
id: 'renameAsset.dependentItems',
defaultMessage: 'Dependent Items'
})
),
React.createElement(DependenciesList, {
dependencies: dependantItems,
compactView: false,
showTypes: 'all-deps',
handleContextMenuClick: handleContextMenuClick
}),
React.createElement(
Menu,
{
open: Boolean(contextMenu.el),
anchorEl: contextMenu.el,
keepMounted: true,
onClose: handleContextMenuClose
},
contextMenu.dependency &&
isEditableAsset(contextMenu.dependency.path) &&
React.createElement(
MenuItem,
{ onClick: () => handleEditorDisplay(contextMenu.dependency) },
React.createElement(FormattedMessage, { id: 'words.edit', defaultMessage: 'Edit' })
)
),
React.createElement(
Alert,
{ severity: 'warning', icon: false, sx: { mt: 2 } },
React.createElement(FormControlLabel, {
control: React.createElement(Checkbox, {
checked: confirmBrokenReferences,
onChange: () => setConfirmBrokenReferences(!confirmBrokenReferences),
inputProps: { 'aria-label': 'controlled' }
}),
label: React.createElement(FormattedMessage, {
id: 'renameAsset.confirmBrokenReferences',
defaultMessage: 'I understand that there will be broken references'
})
})
)
)
: React.createElement(
Typography,
{ variant: 'body1', sx: sx.emptyMessage },
React.createElement(InfoOutlinedIcon, { sx: sx.emptyMessageIcon }),
React.createElement(FormattedMessage, {
id: 'renameAsset.noDependentItems',
defaultMessage: 'No dependent items'
})
)
)
: React.createElement(React.Fragment, null)
),
React.createElement(
DialogFooter,
null,
React.createElement(
SecondaryButton,
{ onClick: (e) => onClose(e, null), disabled: isSubmitting },
React.createElement(FormattedMessage, { id: 'words.cancel', defaultMessage: 'Cancel' })
),
React.createElement(
PrimaryButton,
{ onClick: onRename, disabled: renameDisabled, loading: isSubmitting },
React.createElement(FormattedMessage, { id: 'words.rename', defaultMessage: 'Rename' })
)
),
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
: () => onRenameAsset(siteId, path, name),
onCancel: (confirm === null || confirm === void 0 ? void 0 : confirm.error) ? null : onConfirmCancel
})
);
}