Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make session share button + persist to local storage #72

Merged
merged 22 commits into from
Nov 12, 2024
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
460db0e
Make session share button + persist to local storage
sverhoeven Oct 28, 2024
f44783c
Merge remote-tracking branch 'origin/main' into 61-persist-app-state
sverhoeven Oct 29, 2024
d6802b5
Dont save state on leave, but manually save
sverhoeven Oct 29, 2024
b0ad687
Style save button + move to right
sverhoeven Nov 8, 2024
36ca2f2
Fix app tests
sverhoeven Nov 8, 2024
85be68d
Remove place holder
sverhoeven Nov 8, 2024
e8e9296
Show all analysis on completion of first experiment
sverhoeven Nov 8, 2024
c2ee658
When model run fails log the config
sverhoeven Nov 8, 2024
0dff547
Take output out of solid store + finer progress report
sverhoeven Nov 8, 2024
cfbf6ac
Add permutation sweep button
sverhoeven Nov 8, 2024
c9eaead
Add test for when state is too big, still need to implement
sverhoeven Nov 8, 2024
4616f40
Make share link less ugly
sverhoeven Nov 8, 2024
f49d7cf
More tests
sverhoeven Nov 11, 2024
b5c9bc6
Skip shareable link too long test
sverhoeven Nov 11, 2024
bc3f820
Dont update progress for performance reasons
sverhoeven Nov 11, 2024
3439d35
Dont share link when it is too big
sverhoeven Nov 11, 2024
2450904
Move TODOs to issues and implement some todos
sverhoeven Nov 11, 2024
130eeb9
Move start experiment buttons to own file + when no experiments then …
sverhoeven Nov 11, 2024
b3d7026
Replace drop down menu for start experiment with dialog
sverhoeven Nov 11, 2024
5c8f2ce
Use single button to create experiment in tests
sverhoeven Nov 11, 2024
00180dd
Drop analyis from share link
sverhoeven Nov 12, 2024
aac745a
Move outputs back into experiments store
sverhoeven Nov 12, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 72 additions & 70 deletions apps/class-solid/src/components/Analysis.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { For, Match, Switch, createMemo, createUniqueId } from "solid-js";
import { For, Match, Show, Switch, createMemo, createUniqueId } from "solid-js";
import { getVerticalProfiles } from "~/lib/profiles";
import { analyses, experiments, setAnalyses } from "~/lib/store";
import type { Experiment } from "~/lib/store";
import {
type Analysis,
deleteAnalysis,
experiments,
outputForExperiment,
outputForPermutation,
} from "~/lib/store";
import LinePlot from "./LinePlot";
import { MdiCog, MdiContentCopy, MdiDelete, MdiDownload } from "./icons";
import { Button } from "./ui/button";
Expand All @@ -24,54 +29,29 @@ const colors = [

const linestyles = ["none", "5,5", "10,10", "15,5,5,5", "20,10,5,5,5,10"];

export interface Analysis {
name: string;
description: string;
id: string;
experiments: Experiment[] | undefined;
type: string;
}

export function addAnalysis(type = "default") {
const name = {
default: "Final height",
timeseries: "Timeseries",
profiles: "Vertical profiles",
}[type];

setAnalyses(analyses.length, {
name: name,
id: createUniqueId(),
experiments: experiments,
type: type,
});
}

function deleteAnalysis(analysis: Analysis) {
setAnalyses(analyses.filter((ana) => ana.id !== analysis.id));
}

/** Very rudimentary plot showing time series of each experiment globally available
* It only works if the time axes are equal
*/
export function TimeSeriesPlot() {
const chartData = createMemo(() => {
return experiments
.filter((e) => e.reference.output)
.filter((e) => e.running === false) // Skip running experiments
.flatMap((e, i) => {
const experimentOutput = outputForExperiment(e);
const permutationRuns = e.permutations.map((perm, j) => {
const permOutput = outputForPermutation(experimentOutput, j);
return {
label: `${e.name}/${perm.name}`,
y: perm.output === undefined ? [] : perm.output.h,
x: perm.output === undefined ? [] : perm.output.t,
y: permOutput.h ?? [],
x: permOutput.t ?? [],
color: colors[(j + 1) % 10],
linestyle: linestyles[i % 5],
};
});
return [
{
y: e.reference.output === undefined ? [] : e.reference.output.h,
x: e.reference.output === undefined ? [] : e.reference.output.t,
y: experimentOutput?.reference.h ?? [],
x: experimentOutput?.reference.t ?? [],
label: e.name,
color: colors[0],
linestyle: linestyles[i],
Expand All @@ -94,33 +74,42 @@ export function VerticalProfilePlot() {
const variable = "theta";
const time = -1;
const profileData = createMemo(() => {
return experiments.flatMap((e, i) => {
const permutations = e.permutations.map((p, j) => {
// TODO get additional config info from reference
// permutations probably usually don't have gammaq/gammatetha set?
return {
color: colors[(j + 1) % 10],
linestyle: linestyles[i % 5],
label: `${e.name}/${p.name}`,
...getVerticalProfiles(p.output, p.config, variable, time),
};
});
return experiments
.filter((e) => e.running === false) // Skip running experiments
.flatMap((e, i) => {
const experimentOutput = outputForExperiment(e);
const permutations = e.permutations.map((p, j) => {
// TODO get additional config info from reference
// permutations probably usually don't have gammaq/gammatetha set?
const permOutput = outputForPermutation(experimentOutput, j);
return {
color: colors[(j + 1) % 10],
linestyle: linestyles[i % 5],
label: `${e.name}/${p.name}`,
...getVerticalProfiles(permOutput, p.config, variable, time),
};
});

return [
{
label: e.name,
color: colors[0],
linestyle: linestyles[i],
...getVerticalProfiles(
e.reference.output,
e.reference.config,
variable,
time,
),
},
...permutations,
];
});
return [
{
label: e.name,
color: colors[0],
linestyle: linestyles[i],
...getVerticalProfiles(
experimentOutput?.reference ?? {
t: [],
h: [],
theta: [],
dtheta: [],
},
e.reference.config,
variable,
time,
),
},
...permutations,
];
});
});
return (
<LinePlot
Expand All @@ -137,26 +126,39 @@ function FinalHeights() {
<ul>
<For each={experiments}>
{(experiment) => {
const h = () =>
experiment.reference.output?.h[
experiment.reference.output.h.length - 1
] || 0;
const h = () => {
const experimentOutput = outputForExperiment(experiment);
return (
experimentOutput?.reference.h[
experimentOutput.reference.h.length - 1
] || 0
);
};
return (
<>
<Show when={!experiment.running}>
<li class="mb-2" title={experiment.name}>
{experiment.name}: {h().toFixed()} m
</li>
<For each={experiment.permutations}>
{(perm) => {
const h = () => perm.output?.h[perm.output.h.length - 1] || 0;
{(perm, permIndex) => {
const h = () => {
const experimentOutput = outputForExperiment(experiment);
const permOutput = outputForPermutation(
experimentOutput,
permIndex(),
);
return permOutput.h?.length
? permOutput.h[permOutput.h.length - 1]
: 0;
};
return (
<li title={`${experiment.name}/${perm.name}`}>
{experiment.name}/{perm.name}: {h().toFixed()} m
</li>
);
}}
</For>
</>
</Show>
);
}}
</For>
Expand Down Expand Up @@ -196,7 +198,7 @@ export function AnalysisCard(analysis: Analysis) {
</CardHeader>
<CardContent class="min-h-[450px]">
<Switch fallback={<p>Unknown analysis type</p>}>
<Match when={analysis.type === "default"}>
<Match when={analysis.type === "finalheight"}>
<FinalHeights />
</Match>
<Match when={analysis.type === "timeseries"}>
Expand Down
21 changes: 14 additions & 7 deletions apps/class-solid/src/components/Experiment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import {
} from "~/lib/store";
import { ExperimentConfigForm } from "./ExperimentConfigForm";
import { PermutationsList } from "./PermutationsList";
import { ShareButton } from "./ShareButton";
import { MdiCog, MdiContentCopy, MdiDelete, MdiDownload } from "./icons";
import {
Card,
Expand Down Expand Up @@ -53,7 +52,7 @@ export function AddExperimentDialog(props: {
description: "",
reference: { config: {} },
permutations: [],
running: false,
running: false as const,
};
};

Expand Down Expand Up @@ -131,14 +130,15 @@ export function ExperimentSettingsDialog(props: {
);
}

function RunningIndicator() {
function RunningIndicator(props: { progress: number | false }) {
return (
<div class="flex">
<div class="flex" role="status" aria-live="polite">
<svg
class="-ml-1 mr-3 h-5 w-5 animate-spin"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
aria-hidden="true"
>
<title>Running</title>
<circle
Expand All @@ -155,7 +155,9 @@ function RunningIndicator() {
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
/>
</svg>
<span>Running ...</span>
<span>
Running {props.progress ? (props.progress * 100).toFixed() : 100}% ...
</span>
</div>
);
}
Expand All @@ -181,6 +183,9 @@ function DownloadExperimentArchive(props: { experiment: Experiment }) {
const [url, setUrl] = createSignal<string>("");
createEffect(async () => {
const archive = await createArchive(props.experiment);
if (!archive) {
return;
}
const objectUrl = URL.createObjectURL(archive);
setUrl(objectUrl);
onCleanup(() => URL.revokeObjectURL(objectUrl));
Expand Down Expand Up @@ -243,7 +248,10 @@ export function ExperimentCard(props: {
/>
</CardContent>
<CardFooter>
<Show when={!experiment().running} fallback={<RunningIndicator />}>
<Show
when={!experiment().running}
fallback={<RunningIndicator progress={experiment().running} />}
>
<DownloadExperiment experiment={experiment()} />
<ExperimentSettingsDialog
experiment={experiment()}
Expand Down Expand Up @@ -271,7 +279,6 @@ export function ExperimentCard(props: {
>
<MdiDelete />
</Button>
<ShareButton experiment={experiment} />
</Show>
</CardFooter>
</Card>
Expand Down
23 changes: 20 additions & 3 deletions apps/class-solid/src/components/Nav.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { useLocation } from "@solidjs/router";
import { saveToLocalStorage } from "~/lib/state";
import { ShareButton } from "./ShareButton";
import { MdiContentSave } from "./icons";

export default function Nav() {
const location = useLocation();
Expand All @@ -8,13 +11,27 @@ export default function Nav() {
: "border-transparent hover:border-sky-600";
return (
<nav class="bg-sky-800">
<ul class="container flex items-center p-3 text-gray-200">
<li class={`border-b-2 ${active("/")} mx-1.5 sm:mx-6`}>
<ul class="container flex items-center gap-4 p-3 text-gray-200">
<li class={`border-b-2 ${active("/")}l`}>
<a href="/">CLASS</a>
</li>
<li class={`border-b-2 ${active("/about")} mx-1.5 sm:mx-6`}>
<li class=" w-full" />
<li class={`border-b-2 ${active("/about")}`}>
<a href="/about">About</a>
</li>
<li>
<button
type="button"
class="flex items-center gap-2 border-transparent border-b-2 hover:border-sky-600"
onClick={() => saveToLocalStorage()}
title="Save application state, so when visiting the page again, the state can be restored"
>
Save <MdiContentSave />
</button>
</li>
<li>
<ShareButton />
</li>
</ul>
</nav>
);
Expand Down
Loading