import { useEffect, type DragEvent } from "react"; import { api } from "~/utils/api"; import { isEqual } from "lodash-es"; import { type Scenario } from "./types"; import { useExperiment, useExperimentAccess, useHandledAsyncCallback } from "~/utils/hooks"; import { useState } from "react"; import { Box, Button, HStack, Icon, IconButton, Spinner, Stack, Tooltip, VStack, Text, } from "@chakra-ui/react"; import { cellPadding } from "../constants"; import { BsArrowsAngleExpand, BsX } from "react-icons/bs"; import { RiDraggable } from "react-icons/ri"; import { FloatingLabelInput } from "./FloatingLabelInput"; import { ScenarioEditorModal } from "./ScenarioEditorModal"; export default function ScenarioEditor({ scenario, ...props }: { scenario: Scenario; hovered: boolean; canHide: boolean; }) { const { canModify } = useExperimentAccess(); 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); useEffect(() => { if (savedValues) setValues(savedValues); }, [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], ); const [scenarioEditorModalOpen, setScenarioEditorModalOpen] = useState(false); 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"} > {canModify && props.canHide && ( {/* 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..."} ) : ( Scenario } onClick={() => setScenarioEditorModalOpen(true)} boxSize={6} borderRadius={4} p={1.5} minW={0} colorScheme="gray" color="gray.500" variant="ghost" /> {variableLabels.map((key) => { const value = values[key] ?? ""; return ( { setValues((prev) => ({ ...prev, [key]: e.target.value })); }} onKeyDown={(e) => { if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) { e.preventDefault(); e.currentTarget.blur(); onSave(); } }} onMouseEnter={() => setVariableInputHovered(true)} onMouseLeave={() => setVariableInputHovered(false)} /> ); })} {hasChanged && ( )} )} {scenarioEditorModalOpen && ( setScenarioEditorModalOpen(false)} /> )} ); }