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;
+ }),
});