From bd571c5616555fe5426299eea3842bbf349c90a1 Mon Sep 17 00:00:00 2001 From: ascibisz Date: Wed, 5 Nov 2025 15:38:14 -0800 Subject: [PATCH 01/18] use result_path from firebase to display result in viewer upon selection --- src/App.tsx | 4 +++- src/components/RecipeForm/index.tsx | 2 +- src/constants/firebase.ts | 1 + src/state/store.ts | 13 +++++++++++++ src/types/index.ts | 2 ++ src/utils/firebase.ts | 2 ++ 6 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index b5c4e29b..f1745473 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,6 +6,7 @@ import { getFirebaseRecipe, jsonToString } from "./utils/recipeLoader"; import { getSubmitPackingUrl, JOB_STATUS } from "./constants/aws"; import { FIRESTORE_FIELDS } from "./constants/firebase"; import { SIMULARIUM_EMBED_URL } from "./constants/urls"; +import { useResultUrl, useSetResultUrl } from "./state/store"; import PackingInput from "./components/PackingInput"; import Viewer from "./components/Viewer"; import StatusBar from "./components/StatusBar"; @@ -18,9 +19,10 @@ function App() { const [jobId, setJobId] = useState(""); const [jobStatus, setJobStatus] = useState(""); const [jobLogs, setJobLogs] = useState(""); - const [resultUrl, setResultUrl] = useState(""); const [outputDir, setOutputDir] = useState(""); const [runTime, setRunTime] = useState(0); + const resultUrl = useResultUrl(); + const setResultUrl = useSetResultUrl(); let start = 0; diff --git a/src/components/RecipeForm/index.tsx b/src/components/RecipeForm/index.tsx index 8016cb59..d46996af 100644 --- a/src/components/RecipeForm/index.tsx +++ b/src/components/RecipeForm/index.tsx @@ -46,7 +46,7 @@ const RecipeForm = ({ onStartPacking }: RecipeFormProps) => { disabled={isPacking} style={{ width: "100%" }} > - Pack! + Re-run )} diff --git a/src/constants/firebase.ts b/src/constants/firebase.ts index ff95961e..7f5bff4f 100644 --- a/src/constants/firebase.ts +++ b/src/constants/firebase.ts @@ -38,6 +38,7 @@ export const FIRESTORE_FIELDS = { RECIPE: "recipe", CONFIG: "config", EDITABLE_FIELDS: "editable_fields", + RESULT_PATH: "result_path", } as const; export const RETENTION_POLICY = { diff --git a/src/state/store.ts b/src/state/store.ts index d7b388d1..cc7be6e3 100644 --- a/src/state/store.ts +++ b/src/state/store.ts @@ -4,6 +4,7 @@ import { get as lodashGet, set as lodashSet } from "lodash-es"; import { PackingInputs } from "../types"; import { getFirebaseRecipe, jsonToString } from "../utils/recipeLoader"; import { getPackingInputsDict } from "../utils/firebase"; +import { SIMULARIUM_EMBED_URL } from "../constants/urls"; export interface RecipeData { id: string; @@ -14,6 +15,7 @@ export interface RecipeData { export interface RecipeState { selectedRecipeId: string; + resultUrl: string; inputOptions: Record; recipes: Record; } @@ -43,6 +45,7 @@ type Actions = { recipeString: string ) => Promise ) => Promise; + setResultUrl: (url: string) => void; }; export type RecipeStore = RecipeState & UIState & Actions; @@ -51,6 +54,7 @@ const INITIAL_RECIPE_ID = "peroxisome_v_gradient_packing"; const initialState: RecipeState & UIState = { selectedRecipeId: INITIAL_RECIPE_ID, + resultUrl: "", inputOptions: {}, recipes: {}, isLoading: false, @@ -119,6 +123,7 @@ export const useRecipeStore = create()( set({ selectedRecipeId: recipeId, + resultUrl: SIMULARIUM_EMBED_URL + (sel.result_path ?? ""), }); if (sel.recipe && !get().recipes[sel.recipe]) { @@ -126,6 +131,12 @@ export const useRecipeStore = create()( } }, + + setResultUrl: (url: string) => { + set({ resultUrl: url }); + }, + + updateRecipeString: (recipeId, newString) => { set((s) => { const rec = s.recipes[recipeId]; @@ -230,6 +241,7 @@ export const useIsCurrentRecipeModified = () => useRecipeStore((s) => s.recipes[s.selectedRecipeId]?.isModified ?? false); export const useGetOriginalValue = () => useRecipeStore((s) => s.getOriginalValue); +export const useResultUrl = () => useRecipeStore((s) => s.resultUrl); // action selectors (stable identities) export const useLoadInputOptions = () => @@ -245,3 +257,4 @@ export const useRestoreRecipeDefault = () => export const useStartPacking = () => useRecipeStore((s) => s.startPacking); export const useGetCurrentValue = () => useRecipeStore((s) => s.getCurrentValue); +export const useSetResultUrl = () => useRecipeStore((s) => s.setResultUrl); diff --git a/src/types/index.ts b/src/types/index.ts index 73f47721..7a2848b0 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -5,6 +5,7 @@ export interface Document { recipe?: string; config?: string; editable_fields?: string[]; + result_path?: string; } export type FirestoreDoc = Document & { @@ -19,6 +20,7 @@ export type PackingInputs = { name?: string; config: string; recipe: string; + result_path?: string; editable_fields?: EditableField[]; }; diff --git a/src/utils/firebase.ts b/src/utils/firebase.ts index 78180d13..ec435ac8 100644 --- a/src/utils/firebase.ts +++ b/src/utils/firebase.ts @@ -175,12 +175,14 @@ const getPackingInputsDict = async (): Promise> => { const editableFields = await getEditableFieldsList( doc[FIRESTORE_FIELDS.EDITABLE_FIELDS] || [] ); + const result = doc[FIRESTORE_FIELDS.RESULT_PATH] || ""; if (config && recipe) { inputsDict[recipe] = { [FIRESTORE_FIELDS.NAME]: displayName, [FIRESTORE_FIELDS.CONFIG]: config, [FIRESTORE_FIELDS.RECIPE]: recipe, [FIRESTORE_FIELDS.EDITABLE_FIELDS]: editableFields, + [FIRESTORE_FIELDS.RESULT_PATH]: result, }; } } From fdba1c78d18967930b2e6d1555ae6256c157c070 Mon Sep 17 00:00:00 2001 From: meganrm Date: Wed, 5 Nov 2025 17:07:51 -0800 Subject: [PATCH 02/18] working but many updates --- src/App.tsx | 21 +++++++++++++----- src/components/Dropdown/index.tsx | 4 ++-- src/state/store.ts | 37 ++++++++++++++++++++++--------- src/types/index.ts | 12 ++++++++-- src/utils/firebase.ts | 8 +++---- 5 files changed, 59 insertions(+), 23 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index f1745473..0dfc4f18 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -5,8 +5,7 @@ import { getJobStatus, addRecipe } from "./utils/firebase"; import { getFirebaseRecipe, jsonToString } from "./utils/recipeLoader"; import { getSubmitPackingUrl, JOB_STATUS } from "./constants/aws"; import { FIRESTORE_FIELDS } from "./constants/firebase"; -import { SIMULARIUM_EMBED_URL } from "./constants/urls"; -import { useResultUrl, useSetResultUrl } from "./state/store"; +import { useResultUrl, useSetPackingResults } from "./state/store"; import PackingInput from "./components/PackingInput"; import Viewer from "./components/Viewer"; import StatusBar from "./components/StatusBar"; @@ -22,7 +21,7 @@ function App() { const [outputDir, setOutputDir] = useState(""); const [runTime, setRunTime] = useState(0); const resultUrl = useResultUrl(); - const setResultUrl = useSetResultUrl(); + const setPackingResults = useSetPackingResults(); let start = 0; @@ -34,7 +33,13 @@ function App() { setJobId(""); setJobStatus(""); setJobLogs(""); - setResultUrl(""); + setPackingResults({ + jobId: "", + jobLogs: "", + resultUrl: "", + runTime: 0, + outputDir: "", + }); setRunTime(0); }; @@ -138,7 +143,13 @@ function App() { const range = (Date.now() - start) / 1000; setRunTime(range); if (localJobStatus.status == JOB_STATUS.DONE) { - setResultUrl(SIMULARIUM_EMBED_URL + localJobStatus.result_path); + setPackingResults({ + jobId: id, + jobLogs: "", + resultUrl: localJobStatus.result_path, + runTime: range, + outputDir: localJobStatus.outputs_directory, + }); setOutputDir(localJobStatus.outputs_directory); } else if (localJobStatus.status == JOB_STATUS.FAILED) { setJobLogs(localJobStatus.error_message); diff --git a/src/components/Dropdown/index.tsx b/src/components/Dropdown/index.tsx index 98e90612..055fd959 100644 --- a/src/components/Dropdown/index.tsx +++ b/src/components/Dropdown/index.tsx @@ -1,11 +1,11 @@ import { Select } from "antd"; import { map } from "lodash-es"; -import { Dictionary, PackingInputs } from "../../types"; +import { Dictionary, RecipeManifest } from "../../types"; interface DropdownProps { placeholder: string; defaultValue?: string; - options: Dictionary; + options: Dictionary; onChange: (value: string) => void; } diff --git a/src/state/store.ts b/src/state/store.ts index cc7be6e3..7268605c 100644 --- a/src/state/store.ts +++ b/src/state/store.ts @@ -1,7 +1,7 @@ import { create } from "zustand"; import { subscribeWithSelector } from "zustand/middleware"; import { get as lodashGet, set as lodashSet } from "lodash-es"; -import { PackingInputs } from "../types"; +import { PackingResults, RecipeManifest } from "../types"; import { getFirebaseRecipe, jsonToString } from "../utils/recipeLoader"; import { getPackingInputsDict } from "../utils/firebase"; import { SIMULARIUM_EMBED_URL } from "../constants/urls"; @@ -16,8 +16,9 @@ export interface RecipeData { export interface RecipeState { selectedRecipeId: string; resultUrl: string; - inputOptions: Record; + inputOptions: Record; recipes: Record; + packingResults?: PackingResults; } export interface UIState { @@ -45,7 +46,7 @@ type Actions = { recipeString: string ) => Promise ) => Promise; - setResultUrl: (url: string) => void; + setPackingResults: (results: PackingResults) => void; }; export type RecipeStore = RecipeState & UIState & Actions; @@ -123,7 +124,6 @@ export const useRecipeStore = create()( set({ selectedRecipeId: recipeId, - resultUrl: SIMULARIUM_EMBED_URL + (sel.result_path ?? ""), }); if (sel.recipe && !get().recipes[sel.recipe]) { @@ -131,12 +131,10 @@ export const useRecipeStore = create()( } }, - - setResultUrl: (url: string) => { - set({ resultUrl: url }); + setPackingResults: (results: PackingResults) => { + set({ packingResults: results }); }, - updateRecipeString: (recipeId, newString) => { set((s) => { const rec = s.recipes[recipeId]; @@ -241,7 +239,25 @@ export const useIsCurrentRecipeModified = () => useRecipeStore((s) => s.recipes[s.selectedRecipeId]?.isModified ?? false); export const useGetOriginalValue = () => useRecipeStore((s) => s.getOriginalValue); -export const useResultUrl = () => useRecipeStore((s) => s.resultUrl); + +const useDefaultResultPath = () => + useRecipeStore( + (s) => s.inputOptions[s.selectedRecipeId]?.defaultResultPath || "" + ); + +export const useResultUrl = () => { + let path = ""; + const results = useRecipeStore.getState().packingResults; + const currentRecipeId = useRecipeStore.getState().selectedRecipeId; + const defaultResultPath = useDefaultResultPath(); + if (results) { + path = results.resultUrl; + } else if (currentRecipeId) { + path = defaultResultPath; + } + console.log("useResultUrl path:", `${SIMULARIUM_EMBED_URL}${path}`); + return `${SIMULARIUM_EMBED_URL}${path}`; +}; // action selectors (stable identities) export const useLoadInputOptions = () => @@ -257,4 +273,5 @@ export const useRestoreRecipeDefault = () => export const useStartPacking = () => useRecipeStore((s) => s.startPacking); export const useGetCurrentValue = () => useRecipeStore((s) => s.getCurrentValue); -export const useSetResultUrl = () => useRecipeStore((s) => s.setResultUrl); +export const useSetPackingResults = () => + useRecipeStore((s) => s.setPackingResults); diff --git a/src/types/index.ts b/src/types/index.ts index 7a2848b0..3782d021 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -16,11 +16,11 @@ export interface Dictionary { [Key: string]: T; } -export type PackingInputs = { +export type RecipeManifest = { name?: string; config: string; recipe: string; - result_path?: string; + defaultResultPath?: string; editable_fields?: EditableField[]; }; @@ -31,6 +31,14 @@ export type JobStatusObject = { result_path: string; }; +export type PackingResults = { + jobId: string; + jobLogs: string; + resultUrl: string; + runTime: number; + outputDir: string; +}; + export type EditableField = { id: string; name: string; diff --git a/src/utils/firebase.ts b/src/utils/firebase.ts index ec435ac8..04c5a879 100644 --- a/src/utils/firebase.ts +++ b/src/utils/firebase.ts @@ -21,7 +21,7 @@ import { } from "../constants/firebase"; import { FirestoreDoc, - PackingInputs, + RecipeManifest, Dictionary, EditableField, JobStatusObject, @@ -163,11 +163,11 @@ const getEditableFieldsList = async ( return docs; }; -const getPackingInputsDict = async (): Promise> => { +const getPackingInputsDict = async (): Promise> => { const docs = await getAllDocsFromCollection( FIRESTORE_COLLECTIONS.PACKING_INPUTS ); - const inputsDict: Dictionary = {}; + const inputsDict: Dictionary = {}; for (const doc of docs) { const displayName = doc[FIRESTORE_FIELDS.NAME]; const config = doc[FIRESTORE_FIELDS.CONFIG]; @@ -182,7 +182,7 @@ const getPackingInputsDict = async (): Promise> => { [FIRESTORE_FIELDS.CONFIG]: config, [FIRESTORE_FIELDS.RECIPE]: recipe, [FIRESTORE_FIELDS.EDITABLE_FIELDS]: editableFields, - [FIRESTORE_FIELDS.RESULT_PATH]: result, + defaultResultPath: result, }; } } From c05b6f96d058412868af7789d111a20cef9a0542 Mon Sep 17 00:00:00 2001 From: meganrm Date: Wed, 5 Nov 2025 17:14:46 -0800 Subject: [PATCH 03/18] reduce rerenders --- src/App.tsx | 5 ++--- src/components/Viewer/index.tsx | 17 +++++++++-------- src/state/store.ts | 5 ++--- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 0dfc4f18..f57ed305 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -5,7 +5,7 @@ import { getJobStatus, addRecipe } from "./utils/firebase"; import { getFirebaseRecipe, jsonToString } from "./utils/recipeLoader"; import { getSubmitPackingUrl, JOB_STATUS } from "./constants/aws"; import { FIRESTORE_FIELDS } from "./constants/firebase"; -import { useResultUrl, useSetPackingResults } from "./state/store"; +import { useSetPackingResults } from "./state/store"; import PackingInput from "./components/PackingInput"; import Viewer from "./components/Viewer"; import StatusBar from "./components/StatusBar"; @@ -20,7 +20,6 @@ function App() { const [jobLogs, setJobLogs] = useState(""); const [outputDir, setOutputDir] = useState(""); const [runTime, setRunTime] = useState(0); - const resultUrl = useResultUrl(); const setPackingResults = useSetPackingResults(); let start = 0; @@ -175,7 +174,7 @@ function App() { - +