mirror of
				https://github.com/community-scripts/ProxmoxVE.git
				synced 2025-11-04 02:12:49 +00:00 
			
		
		
		
	JSON editor note fix (#3260)
This commit is contained in:
		@@ -1,11 +1,11 @@
 | 
			
		||||
import { Button } from "@/components/ui/button";
 | 
			
		||||
import { Input } from "@/components/ui/input";
 | 
			
		||||
import {
 | 
			
		||||
  Select,
 | 
			
		||||
  SelectContent,
 | 
			
		||||
  SelectItem,
 | 
			
		||||
  SelectTrigger,
 | 
			
		||||
  SelectValue,
 | 
			
		||||
    Select,
 | 
			
		||||
    SelectContent,
 | 
			
		||||
    SelectItem,
 | 
			
		||||
    SelectTrigger,
 | 
			
		||||
    SelectValue,
 | 
			
		||||
} from "@/components/ui/select";
 | 
			
		||||
import { AlertColors } from "@/config/siteConfig";
 | 
			
		||||
import { cn } from "@/lib/utils";
 | 
			
		||||
@@ -15,116 +15,136 @@ import { ScriptSchema, type Script } from "../_schemas/schemas";
 | 
			
		||||
import { memo, useCallback, useRef } from "react";
 | 
			
		||||
 | 
			
		||||
type NoteProps = {
 | 
			
		||||
  script: Script;
 | 
			
		||||
  setScript: (script: Script) => void;
 | 
			
		||||
  setIsValid: (isValid: boolean) => void;
 | 
			
		||||
  setZodErrors: (zodErrors: z.ZodError | null) => void;
 | 
			
		||||
    script: Script;
 | 
			
		||||
    setScript: (script: Script) => void;
 | 
			
		||||
    setIsValid: (isValid: boolean) => void;
 | 
			
		||||
    setZodErrors: (zodErrors: z.ZodError | null) => void;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function Note({
 | 
			
		||||
  script,
 | 
			
		||||
  setScript,
 | 
			
		||||
  setIsValid,
 | 
			
		||||
  setZodErrors,
 | 
			
		||||
    script,
 | 
			
		||||
    setScript,
 | 
			
		||||
    setIsValid,
 | 
			
		||||
    setZodErrors,
 | 
			
		||||
}: NoteProps) {
 | 
			
		||||
  const inputRefs = useRef<(HTMLInputElement | null)[]>([]);
 | 
			
		||||
    const inputRefs = useRef<(HTMLInputElement | null)[]>([]);
 | 
			
		||||
 | 
			
		||||
  const addNote = useCallback(() => {
 | 
			
		||||
    setScript({
 | 
			
		||||
      ...script,
 | 
			
		||||
      notes: [...script.notes, { text: "", type: "" }],
 | 
			
		||||
    });
 | 
			
		||||
  }, [script, setScript]);
 | 
			
		||||
    const addNote = useCallback(() => {
 | 
			
		||||
        setScript({
 | 
			
		||||
            ...script,
 | 
			
		||||
            notes: [...script.notes, { text: "", type: "" }],
 | 
			
		||||
        });
 | 
			
		||||
    }, [script, setScript]);
 | 
			
		||||
 | 
			
		||||
  const updateNote = useCallback((
 | 
			
		||||
    index: number,
 | 
			
		||||
    key: keyof Script["notes"][number],
 | 
			
		||||
    value: string,
 | 
			
		||||
  ) => {
 | 
			
		||||
    const updated: Script = {
 | 
			
		||||
      ...script,
 | 
			
		||||
      notes: script.notes.map((note, i) =>
 | 
			
		||||
        i === index ? { ...note, [key]: value } : note,
 | 
			
		||||
      ),
 | 
			
		||||
    };
 | 
			
		||||
    const result = ScriptSchema.safeParse(updated);
 | 
			
		||||
    setIsValid(result.success);
 | 
			
		||||
    setZodErrors(result.success ? null : result.error);
 | 
			
		||||
    setScript(updated);
 | 
			
		||||
    // Restore focus after state update
 | 
			
		||||
    if (key === "text") {
 | 
			
		||||
      setTimeout(() => {
 | 
			
		||||
        inputRefs.current[index]?.focus();
 | 
			
		||||
      }, 0);
 | 
			
		||||
    }
 | 
			
		||||
  }, [script, setScript, setIsValid, setZodErrors]);
 | 
			
		||||
    const updateNote = useCallback((
 | 
			
		||||
        index: number,
 | 
			
		||||
        key: keyof Script["notes"][number],
 | 
			
		||||
        value: string,
 | 
			
		||||
    ) => {
 | 
			
		||||
        const updated: Script = {
 | 
			
		||||
            ...script,
 | 
			
		||||
            notes: script.notes.map((note, i) =>
 | 
			
		||||
                i === index ? { ...note, [key]: value } : note,
 | 
			
		||||
            ),
 | 
			
		||||
        };
 | 
			
		||||
        const result = ScriptSchema.safeParse(updated);
 | 
			
		||||
        setIsValid(result.success);
 | 
			
		||||
        setZodErrors(result.success ? null : result.error);
 | 
			
		||||
        setScript(updated);
 | 
			
		||||
        // Restore focus after state update
 | 
			
		||||
        if (key === "text") {
 | 
			
		||||
            setTimeout(() => {
 | 
			
		||||
                inputRefs.current[index]?.focus();
 | 
			
		||||
            }, 0);
 | 
			
		||||
        }
 | 
			
		||||
    }, [script, setScript, setIsValid, setZodErrors]);
 | 
			
		||||
 | 
			
		||||
  const removeNote = useCallback((index: number) => {
 | 
			
		||||
    setScript({
 | 
			
		||||
      ...script,
 | 
			
		||||
      notes: script.notes.filter((_, i) => i !== index),
 | 
			
		||||
    });
 | 
			
		||||
  }, [script, setScript]);
 | 
			
		||||
    const removeNote = useCallback((index: number) => {
 | 
			
		||||
        setScript({
 | 
			
		||||
            ...script,
 | 
			
		||||
            notes: script.notes.filter((_, i) => i !== index),
 | 
			
		||||
        });
 | 
			
		||||
    }, [script, setScript]);
 | 
			
		||||
 | 
			
		||||
  const NoteItem = memo(
 | 
			
		||||
    ({ note, index }: { note: Script["notes"][number]; index: number }) => (
 | 
			
		||||
      <div className="space-y-2 border p-4 rounded">
 | 
			
		||||
        <Input
 | 
			
		||||
          placeholder="Note Text"
 | 
			
		||||
          value={note.text}
 | 
			
		||||
          onChange={(e) => updateNote(index, "text", e.target.value)}
 | 
			
		||||
          ref={(el) => {
 | 
			
		||||
            inputRefs.current[index] = el;
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
        <Select
 | 
			
		||||
          value={note.type}
 | 
			
		||||
          onValueChange={(value) => updateNote(index, "type", value)}
 | 
			
		||||
        >
 | 
			
		||||
          <SelectTrigger className="flex-1">
 | 
			
		||||
            <SelectValue placeholder="Type" />
 | 
			
		||||
          </SelectTrigger>
 | 
			
		||||
          <SelectContent>
 | 
			
		||||
            {Object.keys(AlertColors).map((type) => (
 | 
			
		||||
              <SelectItem key={type} value={type}>
 | 
			
		||||
                <span className="flex items-center gap-2">
 | 
			
		||||
                  {type.charAt(0).toUpperCase() + type.slice(1)}{" "}
 | 
			
		||||
                  <div
 | 
			
		||||
                    className={cn(
 | 
			
		||||
                      "size-4 rounded-full border",
 | 
			
		||||
                      AlertColors[type as keyof typeof AlertColors],
 | 
			
		||||
                    )}
 | 
			
		||||
                  />
 | 
			
		||||
                </span>
 | 
			
		||||
              </SelectItem>
 | 
			
		||||
    return (
 | 
			
		||||
        <>
 | 
			
		||||
            <h3 className="text-xl font-semibold">Notes</h3>
 | 
			
		||||
            {script.notes.map((note, index) => (
 | 
			
		||||
                <NoteItem key={index} note={note} index={index} updateNote={updateNote} removeNote={removeNote} />
 | 
			
		||||
            ))}
 | 
			
		||||
          </SelectContent>
 | 
			
		||||
        </Select>
 | 
			
		||||
        <Button
 | 
			
		||||
          size="sm"
 | 
			
		||||
          variant="destructive"
 | 
			
		||||
          type="button"
 | 
			
		||||
          onClick={() => removeNote(index)}
 | 
			
		||||
        >
 | 
			
		||||
          <Trash2 className="mr-2 h-4 w-4" /> Remove Note
 | 
			
		||||
        </Button>
 | 
			
		||||
      </div>
 | 
			
		||||
    ),
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  NoteItem.displayName = 'NoteItem';
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
      <h3 className="text-xl font-semibold">Notes</h3>
 | 
			
		||||
      {script.notes.map((note, index) => (
 | 
			
		||||
        <NoteItem key={index} note={note} index={index} />
 | 
			
		||||
      ))}
 | 
			
		||||
      <Button type="button" size="sm" onClick={addNote}>
 | 
			
		||||
        <PlusCircle className="mr-2 h-4 w-4" /> Add Note
 | 
			
		||||
      </Button>
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
            <Button type="button" size="sm" onClick={addNote}>
 | 
			
		||||
                <PlusCircle className="mr-2 h-4 w-4" /> Add Note
 | 
			
		||||
            </Button>
 | 
			
		||||
        </>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default memo(Note);
 | 
			
		||||
const NoteItem = memo(
 | 
			
		||||
    ({
 | 
			
		||||
        note,
 | 
			
		||||
        index,
 | 
			
		||||
        updateNote,
 | 
			
		||||
        removeNote,
 | 
			
		||||
    }: {
 | 
			
		||||
        note: Script["notes"][number];
 | 
			
		||||
        index: number;
 | 
			
		||||
        updateNote: (index: number, key: keyof Script["notes"][number], value: string) => void;
 | 
			
		||||
        removeNote: (index: number) => void;
 | 
			
		||||
    }) => {
 | 
			
		||||
        const inputRef = useRef<HTMLInputElement | null>(null);
 | 
			
		||||
 | 
			
		||||
        const handleTextChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
 | 
			
		||||
            updateNote(index, "text", e.target.value);
 | 
			
		||||
            setTimeout(() => {
 | 
			
		||||
                inputRef.current?.focus();
 | 
			
		||||
            }, 0);
 | 
			
		||||
        }, [index, updateNote]);
 | 
			
		||||
 | 
			
		||||
        return (
 | 
			
		||||
            <div className="space-y-2 border p-4 rounded">
 | 
			
		||||
                <Input
 | 
			
		||||
                    placeholder="Note Text"
 | 
			
		||||
                    value={note.text}
 | 
			
		||||
                    onChange={handleTextChange}
 | 
			
		||||
                    ref={inputRef}
 | 
			
		||||
                />
 | 
			
		||||
                <Select
 | 
			
		||||
                    value={note.type}
 | 
			
		||||
                    onValueChange={(value) => updateNote(index, "type", value)}
 | 
			
		||||
                >
 | 
			
		||||
                    <SelectTrigger className="flex-1">
 | 
			
		||||
                        <SelectValue placeholder="Type" />
 | 
			
		||||
                    </SelectTrigger>
 | 
			
		||||
                    <SelectContent>
 | 
			
		||||
                        {Object.keys(AlertColors).map((type) => (
 | 
			
		||||
                            <SelectItem key={type} value={type}>
 | 
			
		||||
                                <span className="flex items-center gap-2">
 | 
			
		||||
                                    {type.charAt(0).toUpperCase() + type.slice(1)}{" "}
 | 
			
		||||
                                    <div
 | 
			
		||||
                                        className={cn(
 | 
			
		||||
                                            "size-4 rounded-full border",
 | 
			
		||||
                                            AlertColors[type as keyof typeof AlertColors],
 | 
			
		||||
                                        )}
 | 
			
		||||
                                    />
 | 
			
		||||
                                </span>
 | 
			
		||||
                            </SelectItem>
 | 
			
		||||
                        ))}
 | 
			
		||||
                    </SelectContent>
 | 
			
		||||
                </Select>
 | 
			
		||||
                <Button
 | 
			
		||||
                    size="sm"
 | 
			
		||||
                    variant="destructive"
 | 
			
		||||
                    type="button"
 | 
			
		||||
                    onClick={() => removeNote(index)}
 | 
			
		||||
                >
 | 
			
		||||
                    <Trash2 className="mr-2 h-4 w-4" /> Remove Note
 | 
			
		||||
                </Button>
 | 
			
		||||
            </div>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
NoteItem.displayName = 'NoteItem';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export default memo(Note);
 | 
			
		||||
		Reference in New Issue
	
	Block a user