Skip to content
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 76 additions & 35 deletions ui/app/src/components/CreateFileDialog/CreateFileDialogContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,15 @@ import { checkPathExistence, createFile } from '../../services/content';
import { validateActionPolicy } from '../../services/sites';
import DialogBody from '../DialogBody/DialogBody';
import TextField from '@mui/material/TextField';
import Box from '@mui/material/Box';
import FormControl from '@mui/material/FormControl';
import Select, { SelectChangeEvent } from '@mui/material/Select';
import MenuItem from '@mui/material/MenuItem';
import DialogFooter from '../DialogFooter/DialogFooter';
import SecondaryButton from '../SecondaryButton';
import PrimaryButton from '../PrimaryButton';
import ConfirmDialog from '../ConfirmDialog';
import { CreateFileContainerProps } from './utils';
import { CreateFileContainerProps, DEFAULT_TEMPLATE_EXTENSION, TEMPLATE_EXTENSIONS, TemplateExtension } from './utils';
import { translations } from './translations';
import useEnhancedDialogContext from '../EnhancedDialog/useEnhancedDialogContext';
import useItemsByPath from '../../hooks/useItemsByPath';
Expand All @@ -42,12 +46,15 @@ export function CreateFileDialogContainer(props: CreateFileContainerProps) {
const { onClose, onCreated, type, path, allowBraces } = props;
const { isSubmitting, hasPendingChanges } = useEnhancedDialogContext();
const [name, setName] = useState('');
const [extension, setExtension] = useState<TemplateExtension>(DEFAULT_TEMPLATE_EXTENSION);
const [confirm, setConfirm] = useState(null);
const dispatch = useDispatch();
const site = useActiveSiteId();
const { formatMessage } = useIntl();
const itemLookup = useItemsByPath();
const computedFilePath = `${path}/${getFileNameWithExtensionForItemType(type, name)}`;
const getFileName = (fileName: string) =>
getFileNameWithExtensionForItemType(type, fileName, type === 'template' ? extension : undefined);
const computedFilePath = `${path}/${getFileName(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);
Expand All @@ -64,7 +71,12 @@ export function CreateFileDialogContainer(props: CreateFileContainerProps) {
createFile(site, path, fileName).subscribe({
next() {
updateSubmittingOrHasPendingChanges({ hasPendingChanges: false, isSubmitting: false });
onCreated?.({ path, fileName, mode: pickExtensionForItemType(type), openOnSuccess: true });
onCreated?.({
path,
fileName,
mode: pickExtensionForItemType(type, fileName, type === 'template' ? extension : undefined),
openOnSuccess: true
});
},
error: onError
});
Expand All @@ -79,7 +91,7 @@ export function CreateFileDialogContainer(props: CreateFileContainerProps) {
}).subscribe({
next: ({ allowed, modifiedValue, message }) => {
if (allowed) {
const fileName = getFileNameWithExtensionForItemType(type, name);
const fileName = getFileName(name);
const pathToCheckExists = modifiedValue ?? `${path}/${fileName}`;
setItemExists(false);
checkPathExistence(site, pathToCheckExists).subscribe({
Expand Down Expand Up @@ -111,7 +123,7 @@ export function CreateFileDialogContainer(props: CreateFileContainerProps) {
};

const onConfirm = () => {
const fileName = getFileNameWithExtensionForItemType(type, name);
const fileName = getFileName(name);
onCreateFile(site, path, fileName);
};

Expand All @@ -127,6 +139,57 @@ export function CreateFileDialogContainer(props: CreateFileContainerProps) {
hasPendingChanges !== newHasPending && updateSubmittingOrHasPendingChanges({ hasPendingChanges: newHasPending });
};

const onExtensionChange = (event: SelectChangeEvent<TemplateExtension>) => {
setExtension(event.target.value as TemplateExtension);
setItemExists(false);
};

const fileNameField = (
<TextField
label={<FormattedMessage id="createFileDialog.fileName" defaultMessage="File Name" />}
value={name}
fullWidth={type !== 'template'}
autoFocus
required
error={(!name && Boolean(isSubmitting)) || fileExists}
placeholder={formatMessage(translations.placeholder)}
helperText={
fileExists ? (
<FormattedMessage
id="createFileDialog.fileAlreadyExists"
defaultMessage="A file with that name already exists"
/>
) : !name && isSubmitting ? (
<FormattedMessage id="createFileDialog.fileNameRequired" defaultMessage="File name is required." />
) : (
<FormattedMessage
id="createFileDialog.helperText"
defaultMessage="Consisting of letters, numbers, dot (.), dash (-) and underscore (_)."
/>
)
}
disabled={isSubmitting}
margin={type === 'template' ? 'none' : 'normal'}
sx={type === 'template' ? { flex: 1 } : undefined}
slotProps={{
inputLabel: { shrink: true }
}}
onChange={(event) => onInputChanges(applyAssetNameRules(event.target.value, { allowBraces }))}
/>
);

const extensionField = (
<FormControl variant="outlined" sx={{ minWidth: 110, flexShrink: 0 }} disabled={isSubmitting}>
<Select id="createFileDialogExtension" value={extension} onChange={onExtensionChange}>
{TEMPLATE_EXTENSIONS.map((templateExtension) => (
<MenuItem key={templateExtension} value={templateExtension}>
{`.${templateExtension}`}
</MenuItem>
))}
</Select>
</FormControl>
);

return (
<>
<DialogBody>
Expand All @@ -138,36 +201,14 @@ export function CreateFileDialogContainer(props: CreateFileContainerProps) {
}
}}
>
<TextField
label={<FormattedMessage id="createFileDialog.fileName" defaultMessage="File Name" />}
value={name}
fullWidth
autoFocus
required
error={(!name && Boolean(isSubmitting)) || fileExists}
placeholder={formatMessage(translations.placeholder)}
helperText={
fileExists ? (
<FormattedMessage
id="createFileDialog.fileAlreadyExists"
defaultMessage="A file with that name already exists"
/>
) : !name && isSubmitting ? (
<FormattedMessage id="createFileDialog.fileNameRequired" defaultMessage="File name is required." />
) : (
<FormattedMessage
id="createFileDialog.helperText"
defaultMessage="Consisting of letters, numbers, dot (.), dash (-) and underscore (_)."
/>
)
}
disabled={isSubmitting}
margin="normal"
slotProps={{
inputLabel: { shrink: true }
}}
onChange={(event) => onInputChanges(applyAssetNameRules(event.target.value, { allowBraces }))}
/>
{type === 'template' ? (
<Box sx={{ display: 'flex', alignItems: 'baseline', gap: 1, mt: 2, mb: 1 }}>
{fileNameField}
{extensionField}
</Box>
) : (
fileNameField
)}
</form>
</DialogBody>
<DialogFooter>
Expand Down
6 changes: 6 additions & 0 deletions ui/app/src/components/CreateFileDialog/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,9 @@ export interface CreateFileStateProps extends CreateFileBaseProps, EnhancedDialo
}

export interface CreateFileContainerProps extends CreateFileBaseProps, Pick<CreateFileProps, 'onCreated' | 'onClose'> {}

export const TEMPLATE_EXTENSIONS = ['ftl', 'ftlh', 'ftlx'];

export type TemplateExtension = (typeof TEMPLATE_EXTENSIONS)[number];
Comment thread
jvega190 marked this conversation as resolved.
Outdated

export const DEFAULT_TEMPLATE_EXTENSION: TemplateExtension = 'ftl';
12 changes: 8 additions & 4 deletions ui/app/src/utils/path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -349,16 +349,20 @@ export function processPathMacros(dependencies: {
return processedPath;
}

export const pickExtensionForItemType = (systemType: string, name?: string) => {
export const pickExtensionForItemType = (systemType: string, name?: string, extension?: string) => {
if (systemType === 'asset') {
return getFileExtension(name);
} else if (systemType === 'controller') {
return 'groovy';
} else if (extension) {
return extension.replace(/^\./, '');
} else {
return systemType === 'controller' ? `groovy` : `ftl`;
return 'ftl';
}
};

export const getFileNameWithExtensionForItemType = (type: string, name: string) =>
`${name}.${pickExtensionForItemType(type)}`
export const getFileNameWithExtensionForItemType = (type: string, name: string, extension?: string) =>
`${name}.${pickExtensionForItemType(type, name, extension)}`
.replace(/(\.groovy)(\.groovy)|(\.ftl)(\.ftl)/g, '$1$3')
.replace(/\.{2,}/g, '.');
Comment thread
jvega190 marked this conversation as resolved.
Outdated

Expand Down