diff --git a/apps/class-solid/src/components/Analysis.tsx b/apps/class-solid/src/components/Analysis.tsx index 130b8e8..18c3d64 100644 --- a/apps/class-solid/src/components/Analysis.tsx +++ b/apps/class-solid/src/components/Analysis.tsx @@ -27,20 +27,18 @@ import { createUniqueId, } from "solid-js"; import { createStore } from "solid-js/store"; +import type { + Analysis, + ProfilesAnalysis, + SkewTAnalysis, + TimeseriesAnalysis, +} from "~/lib/analysis_type"; import type { Observation } from "~/lib/experiment_config"; import { observationsForProfile, observationsForSounding, } from "~/lib/profiles"; -import { - type Analysis, - type ProfilesAnalysis, - type SkewTAnalysis, - type TimeseriesAnalysis, - deleteAnalysis, - experiments, - updateAnalysis, -} from "~/lib/store"; +import { deleteAnalysis, experiments, updateAnalysis } from "~/lib/store"; import { MdiCamera, MdiDelete, MdiImageFilterCenterFocus } from "./icons"; import { AxisBottom, AxisLeft, getNiceAxisLimits } from "./plots/Axes"; import { Chart, ChartContainer, type ChartData } from "./plots/ChartContainer"; diff --git a/apps/class-solid/src/lib/analysis_type.ts b/apps/class-solid/src/lib/analysis_type.ts new file mode 100644 index 0000000..7495dbc --- /dev/null +++ b/apps/class-solid/src/lib/analysis_type.ts @@ -0,0 +1,87 @@ +import { ajv } from "@classmodel/class/validate"; +import { type DefinedError, type JSONSchemaType, ValidationError } from "ajv"; + +export interface BaseAnalysis { + id: string; + description: string; + type: string; + name: string; +} + +export type TimeseriesAnalysis = BaseAnalysis & { + xVariable: string; + yVariable: string; +}; + +export type ProfilesAnalysis = BaseAnalysis & { + variable: string; + time: number; +}; + +export type SkewTAnalysis = BaseAnalysis & { + time: number; +}; + +export type Analysis = TimeseriesAnalysis | ProfilesAnalysis | SkewTAnalysis; +export const analysisNames = [ + "Vertical profiles", + "Timeseries", + "Thermodynamic diagram", +]; + +export function parseAnalysis(raw: unknown): Analysis { + const schema = { + oneOf: [ + { + type: "object", + required: [ + "id", + "description", + "type", + "name", + "xVariable", + "yVariable", + ], + properties: { + id: { type: "string" }, + description: { type: "string" }, + type: { const: "timeseries" }, + name: { type: "string" }, + xVariable: { type: "string" }, + yVariable: { type: "string" }, + }, + additionalProperties: false, + }, + { + type: "object", + required: ["id", "description", "type", "name", "variable", "time"], + properties: { + id: { type: "string" }, + description: { type: "string" }, + type: { const: "profiles" }, + name: { type: "string" }, + variable: { type: "string" }, + time: { type: "number" }, + }, + additionalProperties: false, + }, + { + type: "object", + required: ["id", "description", "type", "name", "time"], + properties: { + id: { type: "string" }, + description: { type: "string" }, + type: { const: "skewT" }, + name: { type: "string" }, + time: { type: "number" }, + }, + additionalProperties: false, + }, + ], + } as unknown as JSONSchemaType; + const validate = ajv.compile(schema); + if (!validate(raw)) { + throw new ValidationError(validate.errors as DefinedError[]); + } + return raw; +} diff --git a/apps/class-solid/src/lib/encode.ts b/apps/class-solid/src/lib/encode.ts index b871a0d..c1082be 100644 --- a/apps/class-solid/src/lib/encode.ts +++ b/apps/class-solid/src/lib/encode.ts @@ -1,12 +1,14 @@ import { pruneConfig } from "@classmodel/class/config_utils"; import { unwrap } from "solid-js/store"; +import { parseAnalysis } from "./analysis_type"; +import type { Analysis } from "./analysis_type"; import { type ExperimentConfig, type PartialExperimentConfig, parseExperimentConfig, } from "./experiment_config"; import { findPresetByName } from "./presets"; -import type { Analysis, Experiment } from "./store"; +import type { Experiment } from "./store"; export function decodeAppState(encoded: string): [Experiment[], Analysis[]] { const decoded = decodeURI(encoded); @@ -28,8 +30,14 @@ export function decodeAppState(encoded: string): [Experiment[], Analysis[]] { } else { console.error("No experiments found in ", encoded); } - const analyses: Analysis[] = []; + if (typeof parsed === "object" && Array.isArray(parsed.analyses)) { + for (const analysisRaw of parsed.analyses) { + const analysis = parseAnalysis(analysisRaw); + analyses.push(analysis); + } + } + return [experiments, analyses]; } @@ -38,8 +46,10 @@ export function encodeAppState( analyses: Analysis[], ) { const rawExperiments = unwrap(experiments); + const rawAnalyses = unwrap(analyses); const minimizedState = { experiments: rawExperiments.map((exp) => toPartial(exp.config)), + analyses: rawAnalyses, }; return encodeURI(JSON.stringify(minimizedState, undefined, 0)); } diff --git a/apps/class-solid/src/lib/profiles.ts b/apps/class-solid/src/lib/profiles.ts index a0c9d17..7cd969e 100644 --- a/apps/class-solid/src/lib/profiles.ts +++ b/apps/class-solid/src/lib/profiles.ts @@ -108,9 +108,15 @@ export function observationsForProfile(obs: Observation, variable = "theta") { return { y: h, x: u }; case "v": return { y: h, x: v }; + case "rh": + return { y: h, x: rh }; + case "T": + return { y: h, x: T }; + case "p": + return { y: h, x: p }; default: console.warn( - "Unknown variable '${variable}' for observation profile.", + `Unknown variable '${variable}' for observation profile.`, ); return { y: Number.NaN, x: Number.NaN }; } diff --git a/apps/class-solid/src/lib/store.ts b/apps/class-solid/src/lib/store.ts index 190bc42..d68bc4a 100644 --- a/apps/class-solid/src/lib/store.ts +++ b/apps/class-solid/src/lib/store.ts @@ -8,6 +8,12 @@ import { mergeConfigurations, pruneConfig, } from "@classmodel/class/config_utils"; +import type { + Analysis, + ProfilesAnalysis, + SkewTAnalysis, + TimeseriesAnalysis, +} from "./analysis_type"; import { decodeAppState } from "./encode"; import { parseExperimentConfig } from "./experiment_config"; import type { ExperimentConfig } from "./experiment_config"; @@ -233,41 +239,11 @@ export function swapPermutationAndReferenceConfiguration( export async function loadStateFromString(rawState: string): Promise { const [loadedExperiments, loadedAnalyses] = decodeAppState(rawState); + setAnalyses(loadedAnalyses); setExperiments(loadedExperiments); await Promise.all(loadedExperiments.map((_, i) => runExperiment(i))); } -export interface Analysis { - id: string; - description: string; - type: string; - name: string; -} - -export type TimeseriesAnalysis = Analysis & { - xVariable: string; - yVariable: string; -}; - -export type ProfilesAnalysis = Analysis & { - variable: string; - time: number; -}; - -export type SkewTAnalysis = Analysis & { - time: number; -}; - -export type AnalysisType = - | TimeseriesAnalysis - | ProfilesAnalysis - | SkewTAnalysis; -export const analysisNames = [ - "Vertical profiles", - "Timeseries", - "Thermodynamic diagram", -]; - export function addAnalysis(name: string) { let newAnalysis: Analysis; diff --git a/apps/class-solid/src/routes/index.tsx b/apps/class-solid/src/routes/index.tsx index 7a2fc9c..7f87983 100644 --- a/apps/class-solid/src/routes/index.tsx +++ b/apps/class-solid/src/routes/index.tsx @@ -16,7 +16,8 @@ import { Flex } from "~/components/ui/flex"; import { Toaster } from "~/components/ui/toast"; import { onPageLoad } from "~/lib/state"; -import { addAnalysis, analysisNames, experiments } from "~/lib/store"; +import { analysisNames } from "~/lib/analysis_type"; +import { addAnalysis, experiments } from "~/lib/store"; import { analyses } from "~/lib/store"; export default function Home() {