Skip to content

Commit 40eaeab

Browse files
authored
Merge pull request #264 from ToposInstitute/diagram-analysis
Analyses for diagrams in models
2 parents c646bbc + 20de659 commit 40eaeab

File tree

19 files changed

+525
-300
lines changed

19 files changed

+525
-300
lines changed

packages/frontend/src/App.tsx

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,10 @@ import * as uuid from "uuid";
88
import { MultiProvider } from "@solid-primitives/context";
99
import { Navigate, type RouteDefinition, type RouteSectionProps, Router } from "@solidjs/router";
1010
import { FirebaseProvider } from "solid-firebase";
11-
import { Show, createResource, lazy, useContext } from "solid-js";
11+
import { Show, createResource, lazy } from "solid-js";
1212

13-
import type { JsonValue } from "catcolab-api";
14-
import { RepoContext, RpcContext, createRpcClient } from "./api";
15-
import { newModelDocument } from "./model/document";
13+
import { RepoContext, RpcContext, createRpcClient, useApi } from "./api";
14+
import { createModel } from "./model/document";
1615
import { HelpContainer, lazyMdx } from "./page/help_page";
1716
import { TheoryLibraryContext, stdTheories } from "./stdlib";
1817

@@ -46,21 +45,9 @@ const Root = (props: RouteSectionProps<unknown>) => {
4645
};
4746

4847
function CreateModel() {
49-
const rpc = useContext(RpcContext);
50-
invariant(rpc, "Missing context to create model");
48+
const api = useApi();
5149

52-
const init = newModelDocument();
53-
54-
const [ref] = createResource<string>(async () => {
55-
const result = await rpc.new_ref.mutate({
56-
content: init as JsonValue,
57-
permissions: {
58-
anyone: "Read",
59-
},
60-
});
61-
invariant(result.tag === "Ok", "Failed to create model");
62-
return result.content;
63-
});
50+
const [ref] = createResource<string>(() => createModel(api));
6451

6552
return <Show when={ref()}>{(ref) => <Navigate href={`/model/${ref()}`} />}</Show>;
6653
}
Lines changed: 135 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
import Resizable, { type ContextValue } from "@corvu/resizable";
22
import { useParams } from "@solidjs/router";
3-
import { Show, createEffect, createResource, createSignal, useContext } from "solid-js";
3+
import {
4+
Match,
5+
Show,
6+
Switch,
7+
createEffect,
8+
createResource,
9+
createSignal,
10+
useContext,
11+
} from "solid-js";
412
import { Dynamic } from "solid-js/web";
513
import invariant from "tiny-invariant";
614

7-
import { RepoContext, RpcContext, getLiveDoc } from "../api";
15+
import { useApi } from "../api";
816
import { IconButton, ResizableHandle } from "../components";
9-
import { LiveModelContext, type ModelDocument, enlivenModelDocument } from "../model";
17+
import { DiagramPane } from "../diagram/diagram_editor";
1018
import { ModelPane } from "../model/model_editor";
1119
import {
1220
type CellConstructor,
@@ -16,9 +24,15 @@ import {
1624
} from "../notebook";
1725
import { BrandedToolbar, HelpButton } from "../page";
1826
import { TheoryLibraryContext } from "../stdlib";
19-
import type { ModelAnalysisMeta } from "../theory";
20-
import type { AnalysisDocument, LiveAnalysisDocument } from "./document";
21-
import type { ModelAnalysis } from "./types";
27+
import type { AnalysisMeta } from "../theory";
28+
import { LiveAnalysisContext } from "./context";
29+
import {
30+
type LiveAnalysisDocument,
31+
type LiveDiagramAnalysisDocument,
32+
type LiveModelAnalysisDocument,
33+
getLiveAnalysis,
34+
} from "./document";
35+
import type { Analysis } from "./types";
2236

2337
import PanelRight from "lucide-solid/icons/panel-right";
2438
import PanelRightClose from "lucide-solid/icons/panel-right-close";
@@ -28,87 +42,13 @@ export default function AnalysisPage() {
2842
const refId = params.ref;
2943
invariant(refId, "Must provide document ref as parameter to analysis page");
3044

31-
const rpc = useContext(RpcContext);
32-
const repo = useContext(RepoContext);
45+
const api = useApi();
3346
const theories = useContext(TheoryLibraryContext);
34-
invariant(rpc && repo && theories, "Missing context for analysis page");
35-
36-
const [liveAnalysis] = createResource<LiveAnalysisDocument>(async () => {
37-
const liveDoc = await getLiveDoc<AnalysisDocument>(rpc, repo, refId);
38-
const { doc } = liveDoc;
39-
invariant(doc.type === "analysis", () => `Expected analysis, got type: ${doc.type}`);
47+
invariant(theories, "Must provide theory library as context to analysis page");
4048

41-
const liveModelDoc = await getLiveDoc<ModelDocument>(rpc, repo, doc.modelRef.refId);
42-
const liveModel = enlivenModelDocument(doc.modelRef.refId, liveModelDoc, theories);
43-
44-
return { refId, liveDoc, liveModel };
45-
});
49+
const [liveAnalysis] = createResource(() => getLiveAnalysis(refId, api, theories));
4650

47-
return (
48-
<Show when={liveAnalysis()}>
49-
{(liveAnalysis) => <AnalysisDocumentEditor liveAnalysis={liveAnalysis()} />}
50-
</Show>
51-
);
52-
}
53-
54-
/** Notebook editor for analyses of models of double theories.
55-
*/
56-
export function AnalysisPane(props: {
57-
liveAnalysis: LiveAnalysisDocument;
58-
}) {
59-
const liveDoc = () => props.liveAnalysis.liveDoc;
60-
return (
61-
<LiveModelContext.Provider value={props.liveAnalysis.liveModel}>
62-
<NotebookEditor
63-
handle={liveDoc().docHandle}
64-
path={["notebook"]}
65-
notebook={liveDoc().doc.notebook}
66-
changeNotebook={(f) => liveDoc().changeDoc((doc) => f(doc.notebook))}
67-
formalCellEditor={ModelAnalysisCellEditor}
68-
cellConstructors={modelAnalysisCellConstructors(
69-
props.liveAnalysis.liveModel.theory()?.modelAnalyses ?? [],
70-
)}
71-
noShortcuts={true}
72-
/>
73-
</LiveModelContext.Provider>
74-
);
75-
}
76-
77-
function ModelAnalysisCellEditor(props: FormalCellEditorProps<ModelAnalysis>) {
78-
const liveModel = useContext(LiveModelContext);
79-
invariant(liveModel, "Live model should be provided as context for analysis");
80-
81-
return (
82-
<Show when={liveModel.theory()?.modelAnalysis(props.content.id)}>
83-
{(analysis) => (
84-
<Dynamic
85-
component={analysis().component}
86-
liveModel={liveModel}
87-
content={props.content.content}
88-
changeContent={(f: (c: unknown) => void) =>
89-
props.changeContent((content) => f(content.content))
90-
}
91-
/>
92-
)}
93-
</Show>
94-
);
95-
}
96-
97-
function modelAnalysisCellConstructors(
98-
analyses: ModelAnalysisMeta[],
99-
): CellConstructor<ModelAnalysis>[] {
100-
return analyses.map((analysis) => {
101-
const { id, name, description, initialContent } = analysis;
102-
return {
103-
name,
104-
description,
105-
construct: () =>
106-
newFormalCell({
107-
id,
108-
content: initialContent(),
109-
}),
110-
};
111-
});
51+
return <AnalysisDocumentEditor liveAnalysis={liveAnalysis()} />;
11252
}
11353

11454
/** Editor for a model of a double theory.
@@ -117,11 +57,8 @@ The editor includes a notebook for the model itself plus another pane for
11757
performing analysis of the model.
11858
*/
11959
export function AnalysisDocumentEditor(props: {
120-
liveAnalysis: LiveAnalysisDocument;
60+
liveAnalysis?: LiveAnalysisDocument;
12161
}) {
122-
const rpc = useContext(RpcContext);
123-
invariant(rpc, "Must provide RPC context");
124-
12562
const [resizableContext, setResizableContext] = createSignal<ContextValue>();
12663
const [isSidePanelOpen, setSidePanelOpen] = createSignal(true);
12764

@@ -170,7 +107,7 @@ export function AnalysisDocumentEditor(props: {
170107
</Show>
171108
</IconButton>
172109
</BrandedToolbar>
173-
<ModelPane liveModel={props.liveAnalysis.liveModel} />
110+
<AnalysisOfPane liveAnalysis={props.liveAnalysis} />
174111
</Resizable.Panel>
175112
<ResizableHandle hidden={!isSidePanelOpen()} />
176113
<Resizable.Panel
@@ -184,7 +121,11 @@ export function AnalysisDocumentEditor(props: {
184121
>
185122
<div class="notebook-container">
186123
<h2>Analysis</h2>
187-
<AnalysisPane liveAnalysis={props.liveAnalysis} />
124+
<Show when={props.liveAnalysis}>
125+
{(liveAnalysis) => (
126+
<AnalysisNotebookEditor liveAnalysis={liveAnalysis()} />
127+
)}
128+
</Show>
188129
</div>
189130
</Resizable.Panel>
190131
</>
@@ -193,3 +134,107 @@ export function AnalysisDocumentEditor(props: {
193134
</Resizable>
194135
);
195136
}
137+
138+
const AnalysisOfPane = (props: {
139+
liveAnalysis?: LiveAnalysisDocument;
140+
}) => (
141+
<Switch>
142+
<Match when={props.liveAnalysis?.analysisType === "model" && props.liveAnalysis.liveModel}>
143+
{(liveModel) => <ModelPane liveModel={liveModel()} />}
144+
</Match>
145+
<Match
146+
when={props.liveAnalysis?.analysisType === "diagram" && props.liveAnalysis.liveDiagram}
147+
>
148+
{(liveDiagram) => <DiagramPane liveDiagram={liveDiagram()} />}
149+
</Match>
150+
</Switch>
151+
);
152+
153+
/** Notebook editor for analyses of models of double theories.
154+
*/
155+
export function AnalysisNotebookEditor(props: {
156+
liveAnalysis: LiveAnalysisDocument;
157+
}) {
158+
const liveDoc = () => props.liveAnalysis.liveDoc;
159+
160+
const cellConstructors = () => {
161+
let meta = undefined;
162+
if (props.liveAnalysis.analysisType === "model") {
163+
meta = props.liveAnalysis.liveModel.theory()?.modelAnalyses;
164+
} else if (props.liveAnalysis.analysisType === "diagram") {
165+
meta = props.liveAnalysis.liveDiagram.liveModel.theory()?.diagramAnalyses;
166+
}
167+
return (meta ?? []).map(analysisCellConstructor);
168+
};
169+
170+
return (
171+
<LiveAnalysisContext.Provider value={props.liveAnalysis}>
172+
<NotebookEditor
173+
handle={liveDoc().docHandle}
174+
path={["notebook"]}
175+
notebook={liveDoc().doc.notebook}
176+
changeNotebook={(f) => liveDoc().changeDoc((doc) => f(doc.notebook))}
177+
formalCellEditor={AnalysisCellEditor}
178+
cellConstructors={cellConstructors()}
179+
noShortcuts={true}
180+
/>
181+
</LiveAnalysisContext.Provider>
182+
);
183+
}
184+
185+
function AnalysisCellEditor(props: FormalCellEditorProps<Analysis<unknown>>) {
186+
const liveAnalysis = useContext(LiveAnalysisContext);
187+
invariant(liveAnalysis, "Live analysis should be provided as context for cell editor");
188+
189+
return (
190+
<Switch>
191+
<Match
192+
when={
193+
liveAnalysis.analysisType === "model" &&
194+
liveAnalysis.liveModel.theory()?.modelAnalysis(props.content.id)
195+
}
196+
>
197+
{(analysis) => (
198+
<Dynamic
199+
component={analysis().component}
200+
liveModel={(liveAnalysis as LiveModelAnalysisDocument).liveModel}
201+
content={props.content.content}
202+
changeContent={(f: (c: unknown) => void) =>
203+
props.changeContent((content) => f(content.content))
204+
}
205+
/>
206+
)}
207+
</Match>
208+
<Match
209+
when={
210+
liveAnalysis.analysisType === "diagram" &&
211+
liveAnalysis.liveDiagram.liveModel.theory()?.diagramAnalysis(props.content.id)
212+
}
213+
>
214+
{(analysis) => (
215+
<Dynamic
216+
component={analysis().component}
217+
liveDiagram={(liveAnalysis as LiveDiagramAnalysisDocument).liveDiagram}
218+
content={props.content.content}
219+
changeContent={(f: (c: unknown) => void) =>
220+
props.changeContent((content) => f(content.content))
221+
}
222+
/>
223+
)}
224+
</Match>
225+
</Switch>
226+
);
227+
}
228+
229+
function analysisCellConstructor<T>(meta: AnalysisMeta<T>): CellConstructor<Analysis<T>> {
230+
const { id, name, description, initialContent } = meta;
231+
return {
232+
name,
233+
description,
234+
construct: () =>
235+
newFormalCell({
236+
id,
237+
content: initialContent(),
238+
}),
239+
};
240+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { createContext } from "solid-js";
2+
3+
import type { LiveAnalysisDocument } from "./document";
4+
5+
/** Context for a live analysis. */
6+
export const LiveAnalysisContext = createContext<LiveAnalysisDocument>();

0 commit comments

Comments
 (0)