diff --git a/src/components/OutputsTable/OutputCell.tsx b/src/components/OutputsTable/OutputCell.tsx index f7567c8..f55a0f5 100644 --- a/src/components/OutputsTable/OutputCell.tsx +++ b/src/components/OutputsTable/OutputCell.tsx @@ -39,7 +39,7 @@ export default function OutputCell({ { enabled: disabledReason === null } ); - if (!vars) return; + if (!vars) return null; if (disabledReason) return ( diff --git a/src/pages/experiments/[id].tsx b/src/pages/experiments/[id].tsx index 3be0b6b..877b531 100644 --- a/src/pages/experiments/[id].tsx +++ b/src/pages/experiments/[id].tsx @@ -1,16 +1,67 @@ -import { Box, Center } from "@chakra-ui/react"; +import { + Box, + Breadcrumb, + BreadcrumbItem, + BreadcrumbLink, + Button, + Center, + Flex, + HStack, + Icon, + Input, + Tooltip, +} from "@chakra-ui/react"; import { useRouter } from "next/router"; +import { useState, useRef, useEffect } from "react"; +import { BsTrash } from "react-icons/bs"; +import { RiFlaskLine } from "react-icons/ri"; import OutputsTable from "~/components/OutputsTable"; import AppShell from "~/components/nav/AppShell"; import { api } from "~/utils/api"; +import { useExperiment, useHandledAsyncCallback } from "~/utils/hooks"; + +const DeleteButton = (props: { experimentId: string }) => { + const mutation = api.experiments.delete.useMutation(); + const utils = api.useContext(); + const router = useRouter(); + + const [onClick] = useHandledAsyncCallback(async () => { + const nextExperiment = await mutation.mutateAsync({ id: props.experimentId }); + await utils.experiments.list.invalidate(); + + if (nextExperiment) { + await router.push({ pathname: "/experiments/[id]", query: { id: nextExperiment } }); + } + }, [mutation, props.experimentId]); + + return ( + + ); +}; export default function Experiment() { const router = useRouter(); + const experiment = useExperiment(); + const utils = api.useContext(); - const experiment = api.experiments.get.useQuery( - { id: router.query.id as string }, - { enabled: !!router.query.id } - ); + const [label, setLabel] = useState(experiment.data?.label || ""); + useEffect(() => { + setLabel(experiment.data?.label || ""); + }, [experiment.data?.label]); + + const updateMutation = api.experiments.update.useMutation(); + const [onSaveLabel] = useHandledAsyncCallback(async () => { + if (label && label !== experiment.data?.label && experiment.data?.id) { + await updateMutation.mutateAsync({ + id: experiment.data.id, + updates: { label: label }, + }); + await Promise.all([utils.experiments.list.invalidate(), utils.experiments.get.invalidate()]); + } + }, [updateMutation, experiment.data?.id, experiment.data?.label, label]); if (!experiment.isLoading && !experiment.data) { return ( @@ -25,6 +76,34 @@ export default function Experiment() { return ( + + + + + + Experiments + + + + + setLabel(e.target.value)} + onBlur={onSaveLabel} + borderWidth={1} + borderColor="transparent" + fontSize={16} + px={0} + minW={400} + flex={1} + _hover={{ borderColor: "gray.300" }} + _focus={{ borderColor: "blue.500", outline: "none" }} + /> + + + + diff --git a/src/server/api/routers/experiments.router.ts b/src/server/api/routers/experiments.router.ts index 173719d..d6ab173 100644 --- a/src/server/api/routers/experiments.router.ts +++ b/src/server/api/routers/experiments.router.ts @@ -69,4 +69,34 @@ export const experimentsRouter = createTRPCRouter({ return exp; }), + + update: publicProcedure + .input(z.object({ id: z.string(), updates: z.object({ label: z.string() }) })) + .mutation(async ({ input }) => { + return await prisma.experiment.update({ + where: { + id: input.id, + }, + data: { + label: input.updates.label, + }, + }); + }), + + delete: publicProcedure.input(z.object({ id: z.string() })).mutation(async ({ input }) => { + await prisma.experiment.delete({ + where: { + id: input.id, + }, + }); + + // Return the ID of the newest existing experiment so the client can redirect to it + const newestExperiment = await prisma.experiment.findFirst({ + orderBy: { + sortIndex: "desc", + }, + }); + + return newestExperiment?.id; + }), });