Skip to content

Commit 3ccedbd

Browse files
authored
Merge pull request #72 from classmodel/61-persist-app-state
Make session share button + persist to local storage
2 parents fac6496 + aac745a commit 3ccedbd

23 files changed

+1472
-381
lines changed

apps/class-solid/src/components/Analysis.tsx

Lines changed: 63 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
import { For, Match, Switch, createMemo, createUniqueId } from "solid-js";
1+
import { For, Match, Show, Switch, createMemo, createUniqueId } from "solid-js";
22
import { getVerticalProfiles } from "~/lib/profiles";
3-
import { analyses, experiments, setAnalyses } from "~/lib/store";
4-
import type { Experiment } from "~/lib/store";
3+
import { type Analysis, deleteAnalysis, experiments } from "~/lib/store";
54
import LinePlot from "./LinePlot";
65
import { MdiCog, MdiContentCopy, MdiDelete, MdiDownload } from "./icons";
76
import { Button } from "./ui/button";
@@ -24,54 +23,30 @@ const colors = [
2423

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

27-
export interface Analysis {
28-
name: string;
29-
description: string;
30-
id: string;
31-
experiments: Experiment[] | undefined;
32-
type: string;
33-
}
34-
35-
export function addAnalysis(type = "default") {
36-
const name = {
37-
default: "Final height",
38-
timeseries: "Timeseries",
39-
profiles: "Vertical profiles",
40-
}[type];
41-
42-
setAnalyses(analyses.length, {
43-
name: name,
44-
id: createUniqueId(),
45-
experiments: experiments,
46-
type: type,
47-
});
48-
}
49-
50-
function deleteAnalysis(analysis: Analysis) {
51-
setAnalyses(analyses.filter((ana) => ana.id !== analysis.id));
52-
}
53-
5426
/** Very rudimentary plot showing time series of each experiment globally available
5527
* It only works if the time axes are equal
5628
*/
5729
export function TimeSeriesPlot() {
5830
const chartData = createMemo(() => {
5931
return experiments
60-
.filter((e) => e.reference.output)
32+
.filter((e) => e.running === false) // Skip running experiments
6133
.flatMap((e, i) => {
62-
const permutationRuns = e.permutations.map((perm, j) => {
63-
return {
64-
label: `${e.name}/${perm.name}`,
65-
y: perm.output === undefined ? [] : perm.output.h,
66-
x: perm.output === undefined ? [] : perm.output.t,
67-
color: colors[(j + 1) % 10],
68-
linestyle: linestyles[i % 5],
69-
};
70-
});
34+
const experimentOutput = e.reference.output;
35+
const permutationRuns = e.permutations
36+
.filter((perm) => perm.output !== undefined)
37+
.map((perm, j) => {
38+
return {
39+
label: `${e.name}/${perm.name}`,
40+
y: perm.output?.h ?? [],
41+
x: perm.output?.t ?? [],
42+
color: colors[(j + 1) % 10],
43+
linestyle: linestyles[i % 5],
44+
};
45+
});
7146
return [
7247
{
73-
y: e.reference.output === undefined ? [] : e.reference.output.h,
74-
x: e.reference.output === undefined ? [] : e.reference.output.t,
48+
y: experimentOutput?.h ?? [],
49+
x: experimentOutput?.t ?? [],
7550
label: e.name,
7651
color: colors[0],
7752
linestyle: linestyles[i],
@@ -94,33 +69,40 @@ export function VerticalProfilePlot() {
9469
const variable = "theta";
9570
const time = -1;
9671
const profileData = createMemo(() => {
97-
return experiments.flatMap((e, i) => {
98-
const permutations = e.permutations.map((p, j) => {
99-
// TODO get additional config info from reference
100-
// permutations probably usually don't have gammaq/gammatetha set?
101-
return {
102-
color: colors[(j + 1) % 10],
103-
linestyle: linestyles[i % 5],
104-
label: `${e.name}/${p.name}`,
105-
...getVerticalProfiles(p.output, p.config, variable, time),
106-
};
107-
});
72+
return experiments
73+
.filter((e) => e.running === false) // Skip running experiments
74+
.flatMap((e, i) => {
75+
const permutations = e.permutations.map((p, j) => {
76+
// TODO get additional config info from reference
77+
// permutations probably usually don't have gammaq/gammatetha set?
78+
return {
79+
color: colors[(j + 1) % 10],
80+
linestyle: linestyles[i % 5],
81+
label: `${e.name}/${p.name}`,
82+
...getVerticalProfiles(p.output, p.config, variable, time),
83+
};
84+
});
10885

109-
return [
110-
{
111-
label: e.name,
112-
color: colors[0],
113-
linestyle: linestyles[i],
114-
...getVerticalProfiles(
115-
e.reference.output,
116-
e.reference.config,
117-
variable,
118-
time,
119-
),
120-
},
121-
...permutations,
122-
];
123-
});
86+
return [
87+
{
88+
label: e.name,
89+
color: colors[0],
90+
linestyle: linestyles[i],
91+
...getVerticalProfiles(
92+
e.reference.output ?? {
93+
t: [],
94+
h: [],
95+
theta: [],
96+
dtheta: [],
97+
},
98+
e.reference.config,
99+
variable,
100+
time,
101+
),
102+
},
103+
...permutations,
104+
];
105+
});
124106
});
125107
return (
126108
<LinePlot
@@ -137,26 +119,31 @@ function FinalHeights() {
137119
<ul>
138120
<For each={experiments}>
139121
{(experiment) => {
140-
const h = () =>
141-
experiment.reference.output?.h[
142-
experiment.reference.output.h.length - 1
143-
] || 0;
122+
const h = () => {
123+
const experimentOutput = experiment.reference.output;
124+
return experimentOutput?.h[experimentOutput?.h.length - 1] || 0;
125+
};
144126
return (
145-
<>
127+
<Show when={!experiment.running}>
146128
<li class="mb-2" title={experiment.name}>
147129
{experiment.name}: {h().toFixed()} m
148130
</li>
149131
<For each={experiment.permutations}>
150132
{(perm) => {
151-
const h = () => perm.output?.h[perm.output.h.length - 1] || 0;
133+
const h = () => {
134+
const permOutput = perm.output;
135+
return permOutput?.h?.length
136+
? permOutput.h[permOutput.h.length - 1]
137+
: 0;
138+
};
152139
return (
153140
<li title={`${experiment.name}/${perm.name}`}>
154141
{experiment.name}/{perm.name}: {h().toFixed()} m
155142
</li>
156143
);
157144
}}
158145
</For>
159-
</>
146+
</Show>
160147
);
161148
}}
162149
</For>
@@ -196,7 +183,7 @@ export function AnalysisCard(analysis: Analysis) {
196183
</CardHeader>
197184
<CardContent class="min-h-[450px]">
198185
<Switch fallback={<p>Unknown analysis type</p>}>
199-
<Match when={analysis.type === "default"}>
186+
<Match when={analysis.type === "finalheight"}>
200187
<FinalHeights />
201188
</Match>
202189
<Match when={analysis.type === "timeseries"}>

apps/class-solid/src/components/Experiment.tsx

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import {
1717
} from "~/lib/store";
1818
import { ExperimentConfigForm } from "./ExperimentConfigForm";
1919
import { PermutationsList } from "./PermutationsList";
20-
import { ShareButton } from "./ShareButton";
2120
import { MdiCog, MdiContentCopy, MdiDelete, MdiDownload } from "./icons";
2221
import {
2322
Card,
@@ -53,7 +52,7 @@ export function AddExperimentDialog(props: {
5352
description: "",
5453
reference: { config: {} },
5554
permutations: [],
56-
running: false,
55+
running: false as const,
5756
};
5857
};
5958

@@ -131,14 +130,15 @@ export function ExperimentSettingsDialog(props: {
131130
);
132131
}
133132

134-
function RunningIndicator() {
133+
function RunningIndicator(props: { progress: number | false }) {
135134
return (
136-
<div class="flex">
135+
<div class="flex" role="status" aria-live="polite">
137136
<svg
138137
class="-ml-1 mr-3 h-5 w-5 animate-spin"
139138
xmlns="http://www.w3.org/2000/svg"
140139
fill="none"
141140
viewBox="0 0 24 24"
141+
aria-hidden="true"
142142
>
143143
<title>Running</title>
144144
<circle
@@ -155,7 +155,9 @@ function RunningIndicator() {
155155
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"
156156
/>
157157
</svg>
158-
<span>Running ...</span>
158+
<span>
159+
Running {props.progress ? (props.progress * 100).toFixed() : 100}% ...
160+
</span>
159161
</div>
160162
);
161163
}
@@ -181,6 +183,9 @@ function DownloadExperimentArchive(props: { experiment: Experiment }) {
181183
const [url, setUrl] = createSignal<string>("");
182184
createEffect(async () => {
183185
const archive = await createArchive(props.experiment);
186+
if (!archive) {
187+
return;
188+
}
184189
const objectUrl = URL.createObjectURL(archive);
185190
setUrl(objectUrl);
186191
onCleanup(() => URL.revokeObjectURL(objectUrl));
@@ -243,7 +248,10 @@ export function ExperimentCard(props: {
243248
/>
244249
</CardContent>
245250
<CardFooter>
246-
<Show when={!experiment().running} fallback={<RunningIndicator />}>
251+
<Show
252+
when={!experiment().running}
253+
fallback={<RunningIndicator progress={experiment().running} />}
254+
>
247255
<DownloadExperiment experiment={experiment()} />
248256
<ExperimentSettingsDialog
249257
experiment={experiment()}
@@ -271,7 +279,6 @@ export function ExperimentCard(props: {
271279
>
272280
<MdiDelete />
273281
</Button>
274-
<ShareButton experiment={experiment} />
275282
</Show>
276283
</CardFooter>
277284
</Card>

apps/class-solid/src/components/Nav.tsx

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
import { useLocation } from "@solidjs/router";
2+
import { saveToLocalStorage } from "~/lib/state";
3+
import { ShareButton } from "./ShareButton";
4+
import { MdiContentSave } from "./icons";
25

36
export default function Nav() {
47
const location = useLocation();
@@ -8,13 +11,27 @@ export default function Nav() {
811
: "border-transparent hover:border-sky-600";
912
return (
1013
<nav class="bg-sky-800">
11-
<ul class="container flex items-center p-3 text-gray-200">
12-
<li class={`border-b-2 ${active("/")} mx-1.5 sm:mx-6`}>
14+
<ul class="container flex items-center gap-4 p-3 text-gray-200">
15+
<li class={`border-b-2 ${active("/")}l`}>
1316
<a href="/">CLASS</a>
1417
</li>
15-
<li class={`border-b-2 ${active("/about")} mx-1.5 sm:mx-6`}>
18+
<li class=" w-full" />
19+
<li class={`border-b-2 ${active("/about")}`}>
1620
<a href="/about">About</a>
1721
</li>
22+
<li>
23+
<button
24+
type="button"
25+
class="flex items-center gap-2 border-transparent border-b-2 hover:border-sky-600"
26+
onClick={() => saveToLocalStorage()}
27+
title="Save application state, so when visiting the page again, the state can be restored"
28+
>
29+
Save <MdiContentSave />
30+
</button>
31+
</li>
32+
<li>
33+
<ShareButton />
34+
</li>
1835
</ul>
1936
</nav>
2037
);

0 commit comments

Comments
 (0)