Tidy up of compiler warnings, also make sure that the unique naming works better.

This commit is contained in:
Colin Dawson 2026-02-14 14:59:34 +00:00
parent 6f49add5f7
commit 291a391faf
3 changed files with 60 additions and 31 deletions

View File

@ -28,33 +28,63 @@ export const TaskCoreEditor: React.FC<TaskCoreEditorProps> = ({
const [fieldErrors, setFieldErrors] = useState<Record<string, string>>({}); const [fieldErrors, setFieldErrors] = useState<Record<string, string>>({});
const prevErrorsRef = useRef<Record<string, string>>({}); const prevErrorsRef = useRef<Record<string, string>>({});
const formatNewTaskName = ( // Generate a unique default name
tasks: TaskDefinition<Record<string, unknown>>[], const nameExists = (tasks: TaskDefinition[], candidate: string): boolean => {
) => { const target = candidate.trim().toLowerCase();
return tasks.some(
(t) => (t.config.name as string)?.trim().toLowerCase() === target,
);
};
const formatNewTaskName = useCallback(
(tasks: TaskDefinition[]) => {
const displayName = allowedTasks.find( const displayName = allowedTasks.find(
(t) => t.taskType === task.type, (t) => t.taskType === task.type,
)?.displayName; )?.displayName;
return `${tTaskType(displayName!)} ${tasks.length + 1}`; if (!displayName) return "New Task";
};
const base = `${tTaskType(displayName)} `;
let index = 1;
while (nameExists(tasks, `${base}${index}`)) {
index++;
}
return `${base}${index}`;
},
[allowedTasks, task.type, tTaskType],
);
const runValidation = useCallback(() => { const runValidation = useCallback(() => {
const errors: Record<string, string> = {}; const errors: Record<string, string> = {};
//If the task doesn't have a name (can happen when adding a new task), generate a default one. // If the task doesn't have a name, generate a default one via onChange
if (task.config.name === undefined) { if (!task.config.name) {
task.config.name = formatNewTaskName(allTasks); const newName = formatNewTaskName(allTasks);
onChange({
...task,
config: {
...task.config,
name: newName,
},
});
// Stop here — next render will validate again
return { errors: {}, isValid: true };
} }
if (!task.config.name || (task.config.name as string).trim() === "") { // Name required
if (!(task.config.name as string).trim()) {
errors["name"] = "Name cannot be empty"; errors["name"] = "Name cannot be empty";
} }
if (task.config.name) { // Name must be unique
// Name must be unique across all tasks
const duplicate = allTasks.find( const duplicate = allTasks.find(
(t) => (t) =>
(t.config.guid as string) !== (task.config.guid as string) && // exclude self t.config.guid !== task.config.guid &&
(t.config.name as string).trim().toLowerCase() === (t.config.name as string).trim().toLowerCase() ===
(task.config.name as string).trim().toLowerCase(), (task.config.name as string).trim().toLowerCase(),
); );
@ -62,12 +92,12 @@ export const TaskCoreEditor: React.FC<TaskCoreEditorProps> = ({
if (duplicate) { if (duplicate) {
errors["name"] = "Name must be unique."; errors["name"] = "Name must be unique.";
} }
}
// Description max length
const descriptionMaxLength = 5000; const descriptionMaxLength = 5000;
if ( if (
task.config.description && (task.config.description && (task.config.description as string).length) ??
(task.config.description as string).length >= descriptionMaxLength 0 > descriptionMaxLength
) { ) {
errors["description"] = errors["description"] =
`Description can be up to ${descriptionMaxLength} characters long.`; `Description can be up to ${descriptionMaxLength} characters long.`;
@ -75,9 +105,9 @@ export const TaskCoreEditor: React.FC<TaskCoreEditorProps> = ({
const isValid = Object.keys(errors).length === 0; const isValid = Object.keys(errors).length === 0;
return { errors, isValid }; return { errors, isValid };
}, [allTasks, task.config.description, task.config.guid, task.config.name]); }, [task, allTasks, formatNewTaskName, onChange]);
//Validate when task changes. // Validate when task changes (new task selected / created)
useEffect(() => { useEffect(() => {
const { errors, isValid } = runValidation(); const { errors, isValid } = runValidation();
@ -85,7 +115,7 @@ export const TaskCoreEditor: React.FC<TaskCoreEditorProps> = ({
prevErrorsRef.current = errors; prevErrorsRef.current = errors;
onValidate({ isValid, errors }); onValidate({ isValid, errors });
}, [onValidate, runValidation, task.config.guid]); }, [task.config.guid, runValidation, onValidate]);
// Validate when fields change // Validate when fields change
useEffect(() => { useEffect(() => {
@ -101,7 +131,7 @@ export const TaskCoreEditor: React.FC<TaskCoreEditorProps> = ({
onValidate({ isValid, errors }); onValidate({ isValid, errors });
prevErrorsRef.current = errors; prevErrorsRef.current = errors;
} }
}, [task.config.description, task.config.name, onValidate, runValidation]); }, [task.config.name, task.config.description, runValidation, onValidate]);
return ( return (
<div> <div>

View File

@ -4,8 +4,6 @@ import {
TaskMetadata, TaskMetadata,
} from "../services/WorkflowTemplateService"; } from "../services/WorkflowTemplateService";
import AddTaskButton from "./AddTaskButton"; import AddTaskButton from "./AddTaskButton";
import { Namespaces } from "../../../../i18n/i18n";
import { useTranslation } from "react-i18next";
import { SelectableList } from "../../../../components/common/SelectableList"; import { SelectableList } from "../../../../components/common/SelectableList";
import { faExclamationCircle } from "@fortawesome/free-solid-svg-icons"; import { faExclamationCircle } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";

View File

@ -22,6 +22,7 @@ export const TaskEditor: React.FC<TaskEditorProps> = ({
onChange, onChange,
onValidate, onValidate,
}) => { }) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [validationMap, setValidationMap] = useState< const [validationMap, setValidationMap] = useState<
Record<string, TaskValidationResult> Record<string, TaskValidationResult>
>({}); >({});