From 0aa21ffd3a7110ccacc402c940ef048d627ae6e4 Mon Sep 17 00:00:00 2001 From: Stefan Verhoeven Date: Mon, 23 Sep 2024 14:40:15 +0200 Subject: [PATCH 1/2] Replace experiment.id with index --- .../class-solid/src/components/Experiment.tsx | 48 +++++--- .../src/components/PermutationsList.tsx | 40 +++++-- apps/class-solid/src/lib/store.ts | 103 +++++++----------- apps/class-solid/src/routes/index.tsx | 4 +- 4 files changed, 101 insertions(+), 94 deletions(-) diff --git a/apps/class-solid/src/components/Experiment.tsx b/apps/class-solid/src/components/Experiment.tsx index 545645b..3745ef0 100644 --- a/apps/class-solid/src/components/Experiment.tsx +++ b/apps/class-solid/src/components/Experiment.tsx @@ -40,10 +40,11 @@ import { DropdownMenuTrigger, } from "./ui/dropdown-menu"; -export function ExperimentSettingsDialog(experiment: Experiment) { - const [open, setOpen] = createSignal( - experiment.reference.output === undefined, - ); +export function ExperimentSettingsDialog(props: { + experiment: Experiment; + experimentIndex: number; +}) { + const [open, setOpen] = createSignal(false); return ( @@ -56,15 +57,15 @@ export function ExperimentSettingsDialog(experiment: Experiment) { { setOpen(false); const { title, description, ...strippedConfig } = newConfig; modifyExperiment( - experiment.id, + props.experimentIndex, strippedConfig, - title ?? experiment.name, - description ?? experiment.description, + title ?? props.experiment.name, + description ?? props.experiment.description, ); }} /> @@ -133,7 +134,7 @@ function DownloadExperimentArchive(props: { experiment: Experiment }) { onCleanup(() => URL.revokeObjectURL(objectUrl)); }); - const filename = `class-${props.experiment.id}.zip`; + const filename = `class-${props.experiment.name}.zip`; return ( Config + output @@ -162,31 +163,42 @@ function DownloadExperiment(props: { experiment: Experiment }) { ); } -export function ExperimentCard(experiment: Experiment) { +export function ExperimentCard(props: { + experiment: Experiment; + experimentIndex: number; +}) { + const experiment = () => props.experiment; + const experimentIndex = () => props.experimentIndex; return ( - {experiment.name} - {experiment.description} + {experiment().name} + {experiment().description} - + - }> - - + }> + + diff --git a/apps/class-solid/src/components/PermutationsList.tsx b/apps/class-solid/src/components/PermutationsList.tsx index c8a0734..6f4e706 100644 --- a/apps/class-solid/src/components/PermutationsList.tsx +++ b/apps/class-solid/src/components/PermutationsList.tsx @@ -55,7 +55,7 @@ function PermutationConfigForm(props: { }, }); - const handleSubmit: SubmitHandler = (values, event) => { + const handleSubmit: SubmitHandler = (values: ClassConfig) => { // Parse only for validation const data = classConfig.parse(values); // TODO if parse fails, show error @@ -80,7 +80,10 @@ function PermutationConfigForm(props: { ); } -function AddPermutationButton(props: { experiment: Experiment }) { +function AddPermutationButton(props: { + experiment: Experiment; + experimentIndex: number; +}) { const [open, setOpen] = createSignal(false); const permutationName = `${props.experiment.permutations.length + 1}`; return ( @@ -97,7 +100,7 @@ function AddPermutationButton(props: { experiment: Experiment }) { Permutation on reference configuration of experiment{" "} - {props.experiment.id} + {props.experiment.name} { const { title, description, ...strippedConfig } = config; setPermutationConfigInExperiment( - props.experiment.id, + props.experimentIndex, -1, strippedConfig, title ?? permutationName, @@ -127,6 +130,7 @@ function AddPermutationButton(props: { experiment: Experiment }) { function EditPermutationButton(props: { experiment: Experiment; + experimentIndex: number; permutationIndex: number; }) { const [open, setOpen] = createSignal(false); @@ -145,7 +149,7 @@ function EditPermutationButton(props: { Permutation on reference configuration of experiment{" "} - {props.experiment.id} + {props.experiment.name} { const { title, description, ...strippedConfig } = config; setPermutationConfigInExperiment( - props.experiment.id, + props.experimentIndex, props.permutationIndex, strippedConfig, title ?? permutationName, @@ -208,6 +212,7 @@ function PermutationDifferenceButton(props: { function PermutationInfo(props: { experiment: Experiment; + experimentIndex: number; permutationIndex: number; perm: Permutation; }) { @@ -220,6 +225,7 @@ function PermutationInfo(props: { /> @@ -234,7 +240,7 @@ function PermutationInfo(props: { deletePermutationFromExperiment( - props.experiment.id, + props.experimentIndex, props.permutationIndex, ) } @@ -243,7 +249,10 @@ function PermutationInfo(props: { { - duplicatePermutation(props.experiment.id, props.permutationIndex); + duplicatePermutation( + props.experimentIndex, + props.permutationIndex, + ); }} > Duplicate permutation @@ -251,7 +260,7 @@ function PermutationInfo(props: { { promotePermutationToExperiment( - props.experiment.id, + props.experimentIndex, props.permutationIndex, ); }} @@ -261,7 +270,7 @@ function PermutationInfo(props: { { swapPermutationAndReferenceConfiguration( - props.experiment.id, + props.experimentIndex, props.permutationIndex, ); }} @@ -274,18 +283,25 @@ function PermutationInfo(props: { ); } -export function PermutationsList(props: { experiment: Experiment }) { +export function PermutationsList(props: { + experimentIndex: number; + experiment: Experiment; +}) { return (
Permutations - +
    {(perm, permutationIndex) => (
  • ; output?: ClassOutput | undefined; @@ -28,13 +27,6 @@ export interface Experiment { running: boolean; } -let lastExperimentId = 0; - -function bumpLastExperimentId(): string { - lastExperimentId++; - return lastExperimentId.toString(); -} - export const [experiments, setExperiments] = createStore([]); export const [analyses, setAnalyses] = createStore([]); @@ -57,11 +49,11 @@ function mergeConfigurations(reference: any, permutation: any) { return merged; } -export async function runExperiment(id: string) { +export async function runExperiment(id: number) { const exp = findExperiment(id); setExperiments( - (e) => e.id === exp.id, + id, produce((e) => { e.running = true; }), @@ -74,7 +66,7 @@ export async function runExperiment(id: string) { const newOutput = await runClass(exp.reference.config); setExperiments( - (e) => e.id === exp.id, + id, produce((e) => { e.reference.output = newOutput; }), @@ -90,7 +82,7 @@ export async function runExperiment(id: string) { const newOutput = await runClass(combinedConfig); setExperiments( - (e) => e.id === exp.id, + id, produce((e) => { e.permutations[key].output = newOutput; }), @@ -98,17 +90,17 @@ export async function runExperiment(id: string) { } setExperiments( - (e) => e.id === exp.id, + id, produce((e) => { e.running = false; }), ); } -function findExperiment(id: string) { - const expProxy = experiments.find((exp) => exp.id === id); +function findExperiment(index: number) { + const expProxy = experiments[index]; if (!expProxy) { - throw new Error(`No experiment with id ${id}`); + throw new Error(`No experiment with index ${index}`); } const exp = unwrap(expProxy); return exp; @@ -118,11 +110,9 @@ export function addExperiment( config: Partial = {}, name?: string, ) { - const id = bumpLastExperimentId(); const newExperiment: Experiment = { - name: name ?? `My experiment ${id}`, + name: name ?? `My experiment ${experiments.length}`, description: "Standard experiment", - id, reference: { config, }, @@ -148,11 +138,9 @@ export type ExperimentConfigSchema = z.infer; export function uploadExperiment(rawData: unknown) { const upload = ExperimentConfigSchema.parse(rawData); - const id = bumpLastExperimentId(); const experiment: Experiment = { name: upload.name, // TODO check name is not already used description: upload.description, - id, reference: { config: upload.reference, }, @@ -162,11 +150,10 @@ export function uploadExperiment(rawData: unknown) { running: false, }; setExperiments(experiments.length, experiment); - // TODO dont trigger opening of form of reference configuration } -export function duplicateExperiment(id: string) { - const original = unwrap(experiments.find((e) => e.id === id)); +export function duplicateExperiment(id: number) { + const original = findExperiment(id); if (!original) { throw new Error("No experiment with id {id}"); } @@ -178,62 +165,57 @@ export function duplicateExperiment(id: string) { let key = 0; for (const perm of original.permutations) { setPermutationConfigInExperiment( - newExperiment.id, + experiments.length - 1, key++, perm.config, perm.name, ); } - runExperiment(newExperiment.id); + runExperiment(experiments.length - 1); } -export function deleteExperiment(id: string) { - setExperiments(experiments.filter((exp) => exp.id !== id)); +export function deleteExperiment(index: number) { + setExperiments(experiments.filter((_, i) => i !== index)); } export async function modifyExperiment( - id: string, + index: number, newConfig: Partial, name: string, description: string, ) { - setExperiments((exp, i) => exp.id === id, "reference", "config", newConfig); - setExperiments( - (exp, i) => exp.id === id, - (exp) => ({ - ...exp, - name, - description, - }), - ); - await runExperiment(id); + setExperiments(index, "reference", "config", newConfig); + setExperiments(index, (exp) => ({ + ...exp, + name, + description, + })); + await runExperiment(index); } export async function setPermutationConfigInExperiment( - experimentId: string, + experimentIndex: number, permutationIndex: number, config: Partial, name: string, ) { setExperiments( - (exp) => exp.id === experimentId, + experimentIndex, "permutations", permutationIndex === -1 - ? findExperiment(experimentId).permutations.length + ? findExperiment(experimentIndex).permutations.length : permutationIndex, { config, name }, ); - await runExperiment(experimentId); + await runExperiment(experimentIndex); } export async function deletePermutationFromExperiment( - experimentId: string, + experimentIndex: number, permutationIndex: number, ) { - setExperiments( - (exp) => exp.id === experimentId, - "permutations", - (perms) => perms.filter((_, i) => i !== permutationIndex), + setExperiments(experimentIndex, "permutations", (perms) => + perms.filter((_, i) => i !== permutationIndex), ); } @@ -246,10 +228,10 @@ export function findPermutation(exp: Experiment, permutationName: string) { } export function promotePermutationToExperiment( - experimentId: string, + experimentIndex: number, permutationIndex: number, ) { - const exp = findExperiment(experimentId); + const exp = findExperiment(experimentIndex); const perm = exp.permutations[permutationIndex]; const combinedConfig = mergeConfigurations(exp.reference.config, perm.config); @@ -258,13 +240,13 @@ export function promotePermutationToExperiment( } export function duplicatePermutation( - experimentId: string, + experimentIndex: number, permutationIndex: number, ) { - const exp = findExperiment(experimentId); + const exp = findExperiment(experimentIndex); const perm = exp.permutations[permutationIndex]; setPermutationConfigInExperiment( - experimentId, + experimentIndex, -1, perm.config, `Copy of ${perm.name}`, @@ -272,27 +254,22 @@ export function duplicatePermutation( } export function swapPermutationAndReferenceConfiguration( - experimentId: string, + experimentIndex: number, permutationIndex: number, ) { - const exp = findExperiment(experimentId); + const exp = findExperiment(experimentIndex); const refConfig = structuredClone(exp.reference.config); const perm = exp.permutations[permutationIndex]; const permConfig = structuredClone(perm.config); + setExperiments(experimentIndex, "reference", "config", permConfig); setExperiments( - (e) => e.id === experimentId, - "reference", - "config", - permConfig, - ); - setExperiments( - (e) => e.id === experimentId, + experimentIndex, "permutations", permutationIndex, "config", refConfig, ); // TODO should names also be swapped? - runExperiment(experimentId); + runExperiment(experimentIndex); } diff --git a/apps/class-solid/src/routes/index.tsx b/apps/class-solid/src/routes/index.tsx index 56f149e..12f2afa 100644 --- a/apps/class-solid/src/routes/index.tsx +++ b/apps/class-solid/src/routes/index.tsx @@ -47,7 +47,9 @@ export default function Home() { - {(experiment) => ExperimentCard(experiment)} + {(experiment, index) => ( + + )} From d05d1b4c7230d1a24048d8dfc07365117d414d91 Mon Sep 17 00:00:00 2001 From: Stefan Verhoeven Date: Mon, 23 Sep 2024 15:34:48 +0200 Subject: [PATCH 2/2] On click add experiment "from scratch", show experiment form in dialog, on form submit then add to store and run Tried to have dialog inside dropdown, but that did not work so made them siblings --- .../class-solid/src/components/Experiment.tsx | 51 +++++++++++++++++++ apps/class-solid/src/lib/store.ts | 7 +-- apps/class-solid/src/routes/index.tsx | 14 +++-- 3 files changed, 65 insertions(+), 7 deletions(-) diff --git a/apps/class-solid/src/components/Experiment.tsx b/apps/class-solid/src/components/Experiment.tsx index 3745ef0..a167fcb 100644 --- a/apps/class-solid/src/components/Experiment.tsx +++ b/apps/class-solid/src/components/Experiment.tsx @@ -10,6 +10,7 @@ import { Button, buttonVariants } from "~/components/ui/button"; import { createArchive, toConfigBlob } from "~/lib/download"; import { type Experiment, + addExperiment, deleteExperiment, duplicateExperiment, modifyExperiment, @@ -40,6 +41,56 @@ import { DropdownMenuTrigger, } from "./ui/dropdown-menu"; +export function AddExperimentDialog(props: { + nextIndex: number; + onClose: () => void; + open: boolean; +}) { + const initialExperiment = () => { + return { + name: `My experiment ${props.nextIndex}`, + description: "", + reference: { config: {} }, + permutations: [], + running: false, + }; + }; + + function setOpen(value: boolean) { + if (!value) { + props.onClose(); + } + } + + return ( + + + + Experiment + + { + props.onClose(); + const { title, description, ...strippedConfig } = newConfig; + addExperiment( + strippedConfig, + title ?? initialExperiment().name, + description ?? initialExperiment().description, + ); + }} + /> + + + + + + ); +} + export function ExperimentSettingsDialog(props: { experiment: Experiment; experimentIndex: number; diff --git a/apps/class-solid/src/lib/store.ts b/apps/class-solid/src/lib/store.ts index 29beac9..419058f 100644 --- a/apps/class-solid/src/lib/store.ts +++ b/apps/class-solid/src/lib/store.ts @@ -106,13 +106,14 @@ function findExperiment(index: number) { return exp; } -export function addExperiment( +export async function addExperiment( config: Partial = {}, name?: string, + description?: string, ) { const newExperiment: Experiment = { name: name ?? `My experiment ${experiments.length}`, - description: "Standard experiment", + description: description ?? "Standard experiment", reference: { config, }, @@ -120,7 +121,7 @@ export function addExperiment( running: false, }; setExperiments(experiments.length, newExperiment); - return newExperiment; + await runExperiment(experiments.length - 1); } const ExperimentConfigSchema = z.object({ diff --git a/apps/class-solid/src/routes/index.tsx b/apps/class-solid/src/routes/index.tsx index 12f2afa..1c646ad 100644 --- a/apps/class-solid/src/routes/index.tsx +++ b/apps/class-solid/src/routes/index.tsx @@ -1,7 +1,7 @@ -import { For, Show } from "solid-js"; +import { For, Show, createSignal } from "solid-js"; import { AnalysisCard, addAnalysis } from "~/components/Analysis"; -import { ExperimentCard } from "~/components/Experiment"; +import { AddExperimentDialog, ExperimentCard } from "~/components/Experiment"; import { UploadExperiment } from "~/components/UploadExperiment"; import { MdiPlusBox } from "~/components/icons"; import { @@ -14,10 +14,11 @@ import { } from "~/components/ui/dropdown-menu"; import { Flex } from "~/components/ui/flex"; -import { addExperiment, experiments } from "~/lib/store"; +import { experiments } from "~/lib/store"; import { analyses } from "~/lib/store"; export default function Home() { + const [openAddDialog, setOpenAddDialog] = createSignal(false); return (

    @@ -30,7 +31,7 @@ export default function Home() { Add experiment addExperiment()} + onClick={() => setOpenAddDialog(true)} class="cursor-pointer" > From scratch @@ -44,6 +45,11 @@ export default function Home() {

    + setOpenAddDialog(false)} + />