explorer/fileNameFormGroup: add "fix it" button

This adds a button on some errors to automatically fix the error.
This commit is contained in:
David Lechner
2022-06-01 12:54:51 -05:00
parent 9cdd102477
commit c26a83f126
3 changed files with 91 additions and 6 deletions
@@ -2,32 +2,108 @@
// Copyright (c) 2022 The Pybricks Authors
import { Classes, FormGroup, InputGroup, Intent, Tag } from '@blueprintjs/core';
import React from 'react';
import type { AriaButtonProps } from '@react-types/button';
import React, { useCallback, useRef } from 'react';
import { useButton } from 'react-aria';
import { FileNameValidationResult } from '../../pybricksMicropython/lib';
import { I18nId, useI18n } from './i18n';
/**
* Trims trailing and leading whitespace and replaces additional whitespace
* with underscores.
* @param value The input string.
* @returns The fixed up string.
*/
function replaceSpaces(value: string): string {
return value.trim().replaceAll(/\s+/g, '_');
}
/**
* Removes the file extension from a string.
* @param value The input string.
* @returns The fixed up string.
*/
function removeFileExtension(value: string): string {
return value.replace(/\.\w+$/, '');
}
/**
* Trims trailing and leading whitespace and replaces groups of invalid
* characters with underscores.
* @param value The input string.
* @returns The fixed up string.
*/
function replaceInvalidCharacters(value: string): string {
return value.trim().replaceAll(/[^A-Za-z0-9-_]+/g, '_');
}
type FixItButtonProps = Pick<AriaButtonProps<'a'>, 'onPress'>;
const FixItButton: React.VoidFunctionComponent<FixItButtonProps> = (props) => {
const i18n = useI18n();
const ref = useRef<HTMLAnchorElement>(null);
const { buttonProps } = useButton(
{
...props,
elementType: 'a',
},
ref,
);
return <a {...buttonProps}>{i18n.translate(I18nId.HelpTextFixIt)}</a>;
};
type FileNameHelpTextProps = {
/** The file name in the input (without file extension). */
fileName: string;
/** The result of the file name validation. */
validation: FileNameValidationResult;
/** Called when the "fix it" link is clicked. */
onFix: (newName: string) => void;
};
/**
* Component that maps FileNameValidationResult to help message to display to user.
*/
const FileNameHelpText: React.VoidFunctionComponent<FileNameHelpTextProps> = ({
fileName,
validation,
onFix,
}) => {
const i18n = useI18n();
const handleHasSpaces = useCallback(() => {
onFix(replaceSpaces(fileName));
}, [fileName, onFix]);
const handleHasFileExtension = useCallback(() => {
onFix(removeFileExtension(fileName));
}, [fileName, onFix]);
const handleHasInvalidCharacters = useCallback(() => {
onFix(replaceInvalidCharacters(fileName));
}, [fileName, onFix]);
switch (validation) {
case FileNameValidationResult.IsOk:
return <>{i18n.translate(I18nId.HelpTextIsOk)}</>;
case FileNameValidationResult.IsEmpty:
return <>{i18n.translate(I18nId.HelpTextIsEmpty)}</>;
case FileNameValidationResult.HasSpaces:
return <>{i18n.translate(I18nId.HelpTextHasSpaces)}</>;
return (
<>
{i18n.translate(I18nId.HelpTextHasSpaces)}{' '}
<FixItButton onPress={handleHasSpaces} />
</>
);
case FileNameValidationResult.HasFileExtension:
return <>{i18n.translate(I18nId.HelpTextHasFileExtension)}</>;
return (
<>
{i18n.translate(I18nId.HelpTextHasFileExtension)}{' '}
<FixItButton onPress={handleHasFileExtension} />
</>
);
case FileNameValidationResult.HasInvalidFirstCharacter:
return (
<>
@@ -45,7 +121,8 @@ const FileNameHelpText: React.VoidFunctionComponent<FileNameHelpTextProps> = ({
numbers: <code className={Classes.CODE}>09</code>,
dash: <code className={Classes.CODE}>-</code>,
underscore: <code className={Classes.CODE}>_</code>,
})}
})}{' '}
<FixItButton onPress={handleHasInvalidCharacters} />
</>
);
case FileNameValidationResult.AlreadyExists:
@@ -87,7 +164,13 @@ const FileNameFormGroup: React.VoidFunctionComponent<FileNameFormGroupProps> = (
<FormGroup
label={i18n.translate(I18nId.Label)}
intent={fileNameIntent}
subLabel={<FileNameHelpText validation={validationResult} />}
subLabel={
<FileNameHelpText
fileName={fileName}
validation={validationResult}
onFix={onChange}
/>
}
>
<InputGroup
aria-label="File name"
+1
View File
@@ -18,4 +18,5 @@ export enum I18nId {
HelpTextHasInvalidFirstCharacter = 'helpText.hasInvalidFirstCharacter',
HelpTextHasInvalidCharacters = 'helpText.hasInvalidCharacters',
HelpTextAlreadyExists = 'helpText.alreadyExists',
HelpTextFixIt = 'helpText.fixIt',
}
@@ -7,6 +7,7 @@
"hasFileExtension": "The file extension will be added automatically.",
"hasInvalidFirstCharacter": "File name must start with a letter ({letters}) or underscore ({underscore}).",
"hasInvalidCharacters": "File name can only contain letters ({letters}), numbers ({numbers}), dashes ({dash}) and underscores ({underscore}).",
"alreadyExists": "A file with this name already exists. Try a different name."
"alreadyExists": "A file with this name already exists. Try a different name.",
"fixIt": "Fix it for me."
}
}