hiding and reordering scenarios

This commit is contained in:
Kyle Corbitt
2023-06-26 11:05:49 -07:00
parent eea9d8191e
commit 15087f6bcd
5 changed files with 219 additions and 65 deletions

View File

@@ -1,3 +1,4 @@
import { type DragEvent } from "react";
import { api } from "~/utils/api";
import { isEqual } from "lodash";
import { type Scenario } from "./types";
@@ -5,12 +6,21 @@ import { useExperiment, useHandledAsyncCallback } from "~/utils/hooks";
import { useState } from "react";
import ResizeTextarea from "react-textarea-autosize";
import { Box, Button, Flex, HStack, Stack, Textarea } from "@chakra-ui/react";
import { Box, Button, Flex, HStack, Icon, Stack, Textarea, Tooltip } from "@chakra-ui/react";
import { cellPadding } from "../constants";
import { BsX } from "react-icons/bs";
import { RiDraggable } from "react-icons/ri";
export default function ScenarioEditor({ scenario }: { scenario: Scenario }) {
export default function ScenarioEditor({
scenario,
hovered,
}: {
scenario: Scenario;
hovered: boolean;
}) {
const savedValues = scenario.variableValues as Record<string, string>;
const utils = api.useContext();
const [isDragTarget, setIsDragTarget] = useState(false);
const [values, setValues] = useState<Record<string, string>>(savedValues);
@@ -30,60 +40,133 @@ export default function ScenarioEditor({ scenario }: { scenario: Scenario }) {
await utils.scenarios.list.invalidate();
}, [mutation, values]);
const hideMutation = api.scenarios.hide.useMutation();
const [onHide] = useHandledAsyncCallback(async () => {
await hideMutation.mutateAsync({
id: scenario.id,
});
await utils.scenarios.list.invalidate();
}, [hideMutation, scenario.id]);
const reorderMutation = api.scenarios.reorder.useMutation();
const [onReorder] = useHandledAsyncCallback(
async (e: DragEvent<HTMLDivElement>) => {
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 (
<Stack px={cellPadding.x} py={cellPadding.y}>
{variableLabels.map((key) => {
const value = values[key] ?? "";
const layoutDirection = value.length > 20 ? "column" : "row";
return (
<Flex
key={key}
direction={layoutDirection}
alignItems={layoutDirection === "column" ? "flex-start" : "center"}
flexWrap="wrap"
>
<Box bgColor="blue.100" color="blue.600" px={2} fontSize="xs" fontWeight="bold">
{key}
</Box>
<Textarea
borderRadius={0}
px={2}
py={1}
placeholder="empty"
value={value}
onChange={(e) => {
setValues((prev) => ({ ...prev, [key]: e.target.value }));
}}
resize="none"
overflow="hidden"
minRows={1}
minH="unset"
as={ResizeTextarea}
flex={layoutDirection === "row" ? 1 : undefined}
borderColor={hasChanged ? "blue.300" : "transparent"}
_hover={{ borderColor: "gray.300" }}
_focus={{ borderColor: "blue.500", outline: "none", bg: "white" }}
/>
</Flex>
);
})}
{hasChanged && (
<HStack justify="right">
<HStack
pr={cellPadding.x}
py={cellPadding.y}
height="100%"
draggable
onDragStart={(e) => {
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"}
>
<Stack alignSelf="flex-start" opacity={hovered ? 1 : 0}>
<Tooltip label="Hide scenario" hasArrow>
{/* for some reason the tooltip can't position itself properly relative to the icon without the wrapping box */}
<Button
size="sm"
borderRadius={0}
onMouseDown={() => {
setValues(savedValues);
variant="unstyled"
color="gray.400"
height="unset"
width="unset"
minW="unset"
onClick={onHide}
_hover={{
color: "gray.800",
cursor: "pointer",
}}
colorScheme="gray"
>
Reset
<Icon as={BsX} boxSize={6} />
</Button>
<Button size="sm" borderRadius={0} onMouseDown={onSave} colorScheme="blue">
Save
</Button>
</HStack>
)}
</Stack>
</Tooltip>
<Icon
as={RiDraggable}
boxSize={6}
color="gray.400"
_hover={{ color: "gray.800", cursor: "pointer" }}
/>
</Stack>
<Stack>
{variableLabels.map((key) => {
const value = values[key] ?? "";
const layoutDirection = value.length > 20 ? "column" : "row";
return (
<Flex
key={key}
direction={layoutDirection}
alignItems={layoutDirection === "column" ? "flex-start" : "center"}
flexWrap="wrap"
>
<Box bgColor="blue.100" color="blue.600" px={2} fontSize="xs" fontWeight="bold">
{key}
</Box>
<Textarea
borderRadius={0}
px={2}
py={1}
placeholder="empty"
value={value}
onChange={(e) => {
setValues((prev) => ({ ...prev, [key]: e.target.value }));
}}
resize="none"
overflow="hidden"
minRows={1}
minH="unset"
as={ResizeTextarea}
flex={layoutDirection === "row" ? 1 : undefined}
borderColor={hasChanged ? "blue.300" : "transparent"}
_hover={{ borderColor: "gray.300" }}
_focus={{ borderColor: "blue.500", outline: "none", bg: "white" }}
/>
</Flex>
);
})}
{hasChanged && (
<HStack justify="right">
<Button
size="sm"
borderRadius={0}
onMouseDown={() => {
setValues(savedValues);
}}
colorScheme="gray"
>
Reset
</Button>
<Button size="sm" borderRadius={0} onMouseDown={onSave} colorScheme="blue">
Save
</Button>
</HStack>
)}
</Stack>
</HStack>
);
}