mirror of
https://github.com/pybricks/pybricks-code.git
synced 2026-09-12 01:23:52 +00:00
explorer/fileNameFormGroup: add "fix it" button
This adds a button on some errors to automatically fix the error.
This commit is contained in:
@@ -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}>0…9</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"
|
||||
|
||||
@@ -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."
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user