import { type DragEvent } from "react"; import { api } from "~/utils/api"; import { isEqual } from "lodash"; import { type Scenario } from "./types"; import { useExperiment, useHandledAsyncCallback } from "~/utils/hooks"; import { useState } from "react"; import { Box, Button, Flex, HStack, Icon, Spinner, Stack, Tooltip, VStack } from "@chakra-ui/react"; import { cellPadding } from "../constants"; import { BsX } from "react-icons/bs"; import { RiDraggable } from "react-icons/ri"; import AutoResizeTextArea from "../AutoResizeTextArea"; export default function ScenarioEditor({ scenario, hovered, }: { scenario: Scenario; hovered: boolean; }) { const savedValues = scenario.variableValues as Record; const utils = api.useContext(); const [isDragTarget, setIsDragTarget] = useState(false); const [variableInputHovered, setVariableInputHovered] = useState(false); const [values, setValues] = useState>(savedValues); const experiment = useExperiment(); const vars = api.templateVars.list.useQuery({ experimentId: experiment.data?.id ?? "" }); const variableLabels = vars.data?.map((v) => v.label) ?? []; const hasChanged = !isEqual(savedValues, values); const mutation = api.scenarios.replaceWithValues.useMutation(); const [onSave] = useHandledAsyncCallback(async () => { await mutation.mutateAsync({ id: scenario.id, values, }); await utils.scenarios.list.invalidate(); }, [mutation, values]); const hideMutation = api.scenarios.hide.useMutation(); const [onHide, hidingInProgress] = useHandledAsyncCallback(async () => { await hideMutation.mutateAsync({ id: scenario.id, }); await utils.scenarios.list.invalidate(); await utils.promptVariants.stats.invalidate(); }, [hideMutation, scenario.id]); const reorderMutation = api.scenarios.reorder.useMutation(); const [onReorder] = useHandledAsyncCallback( async (e: DragEvent) => { e.preventDefault(); setIsDragTarget(false); const draggedId = e.dataTransfer.getData("text/plain"); const droppedId = scenario.id; if (!draggedId || !droppedId || draggedId === droppedId) return; await reorderMutation.mutateAsync({ draggedId, droppedId, }); await utils.scenarios.list.invalidate(); }, [reorderMutation, scenario.id], ); return ( { e.dataTransfer.setData("text/plain", scenario.id); e.currentTarget.style.opacity = "0.4"; }} onDragEnd={(e) => { e.currentTarget.style.opacity = "1"; }} onDragOver={(e) => { e.preventDefault(); setIsDragTarget(true); }} onDragLeave={() => { setIsDragTarget(false); }} onDrop={onReorder} backgroundColor={isDragTarget ? "gray.100" : "transparent"} > {/* for some reason the tooltip can't position itself properly relative to the icon without the wrapping box */} {variableLabels.length === 0 ? ( {vars.data ? "No scenario variables configured" : "Loading..."} ) : ( {variableLabels.map((key) => { const value = values[key] ?? ""; const layoutDirection = value.length > 20 ? "column" : "row"; return ( {key} { setValues((prev) => ({ ...prev, [key]: e.target.value })); }} maxH="32" overflowY="auto" onKeyDown={(e) => { if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) { e.preventDefault(); e.currentTarget.blur(); onSave(); } }} resize="none" overflow="hidden" flex={layoutDirection === "row" ? 1 : undefined} borderColor={hasChanged ? "blue.300" : "transparent"} _hover={{ borderColor: "gray.300" }} _focus={{ borderColor: "blue.500", outline: "none", bg: "white" }} onMouseEnter={() => setVariableInputHovered(true)} onMouseLeave={() => setVariableInputHovered(false)} /> ); })} {hasChanged && ( )} )} ); }