Skip to content
Merged
8 changes: 6 additions & 2 deletions packages/frontend/src/analysis/analysis_editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Dynamic } from "solid-js/web";
import invariant from "tiny-invariant";

import { Nb } from "catcolab-document-methods";
import { type FocusHandle } from "catcolab-ui-components";
import { type CellConstructor, type FormalCellEditorProps, NotebookEditor } from "../notebook";
import type { AnalysisMeta, DiagramAnalysisMeta, ModelAnalysisMeta } from "../theory";
import { LiveAnalysisContext } from "./context";
Expand All @@ -16,7 +17,10 @@ import type { Analysis } from "./types";

/** Notebook editor for analyses of models of double theories.
*/
export function AnalysisNotebookEditor(props: { liveAnalysis: LiveAnalysisDoc }) {
export function AnalysisNotebookEditor(props: {
liveAnalysis: LiveAnalysisDoc;
focus: FocusHandle;
}) {
const liveDoc = () => props.liveAnalysis.liveDoc;

const cellConstructors = () => {
Expand All @@ -39,7 +43,7 @@ export function AnalysisNotebookEditor(props: { liveAnalysis: LiveAnalysisDoc })
changeNotebook={(f) => liveDoc().changeDoc((doc) => f(doc.notebook))}
formalCellEditor={AnalysisCellEditor}
cellConstructors={cellConstructors()}
noShortcuts={true}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exciting to finally be rid of this hack!

focus={props.focus}
/>
</LiveAnalysisContext.Provider>
);
Expand Down
9 changes: 7 additions & 2 deletions packages/frontend/src/components/document_picker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export function DocumentPicker(
"docType",
"placeholder",
"isActive",
"focus",
"filterCompletions",
]);

Expand All @@ -68,10 +69,13 @@ export function DocumentPicker(
);

const [editMode, setEditMode] = createSignal(false);
const enableEditMode = () => setEditMode(true);
const enableEditMode = () => {
props.focus?.setFocused(true);
setEditMode(true);
};
const disableEditMode = () => setEditMode(false);

createEffect(() => setEditMode(props.isActive ?? false));
createEffect(() => setEditMode(props.focus?.hasFocus() ?? props.isActive ?? false));

const DocLink = (linkProps: ComponentProps<"a">) => (
<Switch>
Expand Down Expand Up @@ -123,6 +127,7 @@ export function DocumentPicker(
<Show when={editMode()} fallback={<EditableDocLink />}>
<DocSearchInput
isActive={true}
focus={props.focus}
refId={props.refId}
setRefId={(refId) => {
props.setRefId(refId);
Expand Down
8 changes: 5 additions & 3 deletions packages/frontend/src/diagram/diagram_editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import invariant from "tiny-invariant";

import { Diagram, Nb } from "catcolab-document-methods";
import type { DiagramJudgment, DiagramMorDecl, DiagramObDecl } from "catcolab-document-types";
import { type FocusHandle } from "catcolab-ui-components";
import { LiveModelContext } from "../model";
import { type CellConstructor, type FormalCellEditorProps, NotebookEditor } from "../notebook";
import type { InstanceTypeMeta } from "../theory";
Expand All @@ -14,7 +15,7 @@ import { DiagramObjectCellEditor } from "./object_cell_editor";

/** Notebook editor for a diagram in a model.
*/
export function DiagramNotebookEditor(props: { liveDiagram: LiveDiagramDoc }) {
export function DiagramNotebookEditor(props: { liveDiagram: LiveDiagramDoc; focus: FocusHandle }) {
const liveDoc = () => props.liveDiagram.liveDoc;
const liveModel = () => props.liveDiagram.liveModel;

Expand All @@ -39,6 +40,7 @@ export function DiagramNotebookEditor(props: { liveDiagram: LiveDiagramDoc }) {
cellConstructors={cellConstructors()}
cellLabel={judgmentLabel}
duplicateCell={Diagram.duplicateDiagramJudgment}
focus={props.focus}
/>
</MultiProvider>
);
Expand All @@ -59,7 +61,7 @@ function DiagramCellEditor(props: FormalCellEditorProps<DiagramJudgment>) {
modifyDecl={(f) =>
props.changeContent((content) => f(content as DiagramObDecl))
}
isActive={props.isActive}
focus={props.focus}
actions={props.actions}
theory={theory()}
/>
Expand All @@ -72,7 +74,7 @@ function DiagramCellEditor(props: FormalCellEditorProps<DiagramJudgment>) {
modifyDecl={(f) =>
props.changeContent((content) => f(content as DiagramMorDecl))
}
isActive={props.isActive}
focus={props.focus}
actions={props.actions}
theory={theory()}
/>
Expand Down
53 changes: 18 additions & 35 deletions packages/frontend/src/diagram/morphism_cell_editor.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { createEffect, createSignal, useContext } from "solid-js";
import { useContext } from "solid-js";
import invariant from "tiny-invariant";
import { v7 } from "uuid";

import { type FocusHandle, useChildFocus } from "catcolab-ui-components";
import type { DiagramMorDecl } from "catlog-wasm";
import { BasicMorInput } from "../model/morphism_input";
import type { CellActions } from "../notebook";
Expand All @@ -16,21 +17,15 @@ import "./morphism_cell_editor.css";
export function DiagramMorphismCellEditor(props: {
decl: DiagramMorDecl;
modifyDecl: (f: (decl: DiagramMorDecl) => void) => void;
isActive: boolean;
focus: FocusHandle;
actions: CellActions;
theory: Theory;
}) {
const liveDiagram = useContext(LiveDiagramContext);
invariant(liveDiagram, "Live diagram should be provided as context");

const [activeInput, setActiveInput] = createSignal<DiagramMorphismCellInput>("mor");

// Reset to default on deactivation so re-entry lands on the morphism input.
createEffect(() => {
if (!props.isActive) {
setActiveInput("mor");
}
});
// oxlint-disable-next-line solid/reactivity -- Focus handles are stable for a mounted cell.
const focus = useChildFocus<DiagramMorphismCellInput>(props.focus, { default: "mor" });

const domType = () => props.theory.theory.src(props.decl.morType);
const codType = () => props.theory.theory.tgt(props.decl.morType);
Expand Down Expand Up @@ -61,15 +56,11 @@ export function DiagramMorphismCellEditor(props: {
obType={domType()}
generateId={v7}
isInvalid={domInvalid()}
isActive={props.isActive && activeInput() === "dom"}
deleteForward={() => setActiveInput("mor")}
exitBackward={() => setActiveInput("mor")}
exitForward={() => setActiveInput("cod")}
exitRight={() => setActiveInput("mor")}
hasFocused={() => {
setActiveInput("dom");
props.actions.hasFocused?.();
}}
focus={focus.childFocus("dom")}
deleteForward={() => focus.setActiveChild("mor")}
exitBackward={() => focus.setActiveChild("mor")}
exitForward={() => focus.setActiveChild("cod")}
exitRight={() => focus.setActiveChild("mor")}
/>
<div class={arrowStyles.arrowWithName}>
<div class={arrowStyles.arrowName}>
Expand All @@ -82,19 +73,15 @@ export function DiagramMorphismCellEditor(props: {
}}
morType={props.decl.morType}
placeholder={props.theory.modelMorTypeMeta(props.decl.morType)?.name}
isActive={props.isActive && activeInput() === "mor"}
focus={focus.childFocus("mor")}
deleteBackward={props.actions.deleteBackward}
deleteForward={props.actions.deleteForward}
exitBackward={props.actions.activateAbove}
exitForward={() => setActiveInput("dom")}
exitForward={() => focus.setActiveChild("dom")}
exitUp={props.actions.activateAbove}
exitDown={props.actions.activateBelow}
exitLeft={() => setActiveInput("dom")}
exitRight={() => setActiveInput("cod")}
hasFocused={() => {
setActiveInput("mor");
props.actions.hasFocused?.();
}}
exitLeft={() => focus.setActiveChild("dom")}
exitRight={() => focus.setActiveChild("cod")}
/>
</div>
<div class={[arrowStyles.arrowContainer, arrowStyles.default].join(" ")}>
Expand All @@ -112,15 +99,11 @@ export function DiagramMorphismCellEditor(props: {
obType={codType()}
generateId={v7}
isInvalid={codInvalid()}
isActive={props.isActive && activeInput() === "cod"}
deleteBackward={() => setActiveInput("mor")}
exitBackward={() => setActiveInput("dom")}
focus={focus.childFocus("cod")}
deleteBackward={() => focus.setActiveChild("mor")}
exitBackward={() => focus.setActiveChild("dom")}
exitForward={props.actions.activateBelow}
exitLeft={() => setActiveInput("mor")}
hasFocused={() => {
setActiveInput("cod");
props.actions.hasFocused?.();
}}
exitLeft={() => focus.setActiveChild("mor")}
/>
</div>
);
Expand Down
29 changes: 10 additions & 19 deletions packages/frontend/src/diagram/object_cell_editor.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { createSignal } from "solid-js";

import { NameInput } from "catcolab-ui-components";
import { NameInput, type FocusHandle, useChildFocus } from "catcolab-ui-components";
import type { DiagramObDecl } from "catlog-wasm";
import { ObInput } from "../model/object_input";
import type { CellActions } from "../notebook";
Expand All @@ -12,11 +10,12 @@ import "./object_cell_editor.css";
export function DiagramObjectCellEditor(props: {
decl: DiagramObDecl;
modifyDecl: (f: (decl: DiagramObDecl) => void) => void;
isActive: boolean;
focus: FocusHandle;
actions: CellActions;
theory: Theory;
}) {
const [activeInput, setActiveInput] = createSignal<DiagramObjectCellInput>("name");
// oxlint-disable-next-line solid/reactivity -- Focus handles are stable for a mounted cell.
const focus = useChildFocus<DiagramObjectCellInput>(props.focus, { default: "name" });

return (
<div class="formal-judgment diagram-object-decl">
Expand All @@ -32,13 +31,9 @@ export function DiagramObjectCellEditor(props: {
deleteForward={props.actions.deleteForward}
exitUp={props.actions.activateAbove}
exitDown={props.actions.activateBelow}
exitRight={() => setActiveInput("overOb")}
exitForward={() => setActiveInput("overOb")}
isActive={props.isActive && activeInput() === "name"}
hasFocused={() => {
setActiveInput("name");
props.actions.hasFocused?.();
}}
exitRight={() => focus.setActiveChild("overOb")}
exitForward={() => focus.setActiveChild("overOb")}
focus={focus.childFocus("name")}
/>
<span class="is-a" />
<ObInput
Expand All @@ -52,13 +47,9 @@ export function DiagramObjectCellEditor(props: {
placeholder={props.theory.modelObTypeMeta(props.decl.obType)?.name}
exitUp={props.actions.activateAbove}
exitDown={props.actions.activateBelow}
exitLeft={() => setActiveInput("name")}
exitBackward={() => setActiveInput("name")}
isActive={props.isActive && activeInput() === "overOb"}
hasFocused={() => {
setActiveInput("overOb");
props.actions.hasFocused?.();
}}
exitLeft={() => focus.setActiveChild("name")}
exitBackward={() => focus.setActiveChild("name")}
focus={focus.childFocus("overOb")}
/>
</div>
);
Expand Down
48 changes: 15 additions & 33 deletions packages/frontend/src/model/contribution_cell_editor.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createEffect, createMemo, createSignal, useContext, Switch, Match } from "solid-js";
import { createMemo, useContext, Switch, Match } from "solid-js";
import invariant from "tiny-invariant";

import { NameInput } from "catcolab-ui-components";
import { NameInput, useChildFocus } from "catcolab-ui-components";
import type { Ob } from "catlog-wasm";
import { LiveModelContext } from "./context";
import { ContributionMonomialEditor } from "./contribution_monomial_editor";
Expand Down Expand Up @@ -32,14 +32,8 @@ export default function ContributionCellEditor(
const liveModel = useContext(LiveModelContext);
invariant(liveModel, "Live model should be provided as context");

const [activeInput, setActiveInput] = createSignal<MorphismCellInput>("name");

// Reset to default on deactivation so re-entry lands on the name input.
createEffect(() => {
if (!props.isActive) {
setActiveInput("name");
}
});
// oxlint-disable-next-line solid/reactivity -- Focus handles are stable for a mounted cell.
const focus = useChildFocus<MorphismCellInput>(props.focus, { default: "name" });

const morTypeMeta = () => props.theory.modelMorTypeMeta(props.morphism.morType);

Expand Down Expand Up @@ -104,19 +98,15 @@ export default function ContributionCellEditor(
mor.name = name;
});
}}
isActive={props.isActive && activeInput() === "name"}
focus={focus.childFocus("name")}
deleteBackward={props.actions.deleteBackward}
deleteForward={props.actions.deleteForward}
exitBackward={props.actions.activateAbove}
exitForward={() => setActiveInput("cod")}
exitForward={() => focus.setActiveChild("cod")}
exitUp={props.actions.activateAbove}
exitDown={props.actions.activateBelow}
exitLeft={() => setActiveInput("cod")}
exitRight={() => setActiveInput("dom")}
hasFocused={() => {
setActiveInput("name");
props.actions.hasFocused?.();
}}
exitLeft={() => focus.setActiveChild("cod")}
exitRight={() => focus.setActiveChild("dom")}
/>
</div>
<div class={styles["morphism-decl-name-separator"]}>:</div>
Expand All @@ -138,15 +128,11 @@ export default function ContributionCellEditor(
obType={codType()}
applyOp={morTypeMeta()?.codomain?.apply}
isInvalid={errors().some((err) => err.tag === "Cod" || err.tag === "CodType")}
isActive={props.isActive && activeInput() === "cod"}
deleteForward={() => setActiveInput("name")}
focus={focus.childFocus("cod")}
deleteForward={() => focus.setActiveChild("name")}
exitBackward={props.actions.activateAbove}
exitForward={() => setActiveInput("dom")}
exitLeft={() => setActiveInput("name")}
hasFocused={() => {
setActiveInput("cod");
props.actions.hasFocused?.();
}}
exitForward={() => focus.setActiveChild("dom")}
exitLeft={() => focus.setActiveChild("name")}
/>
</div>
<div class={styles["morphism-decl-arrow-replacement"]}>
Expand All @@ -164,15 +150,11 @@ export default function ContributionCellEditor(
setOb={setDomOb}
obType={domType()}
isInvalid={errors().some((err) => err.tag === "Dom" || err.tag === "DomType")}
isActive={props.isActive && activeInput() === "dom"}
deleteBackward={() => setActiveInput("name")}
exitBackward={() => setActiveInput("name")}
focus={focus.childFocus("dom")}
deleteBackward={() => focus.setActiveChild("name")}
exitBackward={() => focus.setActiveChild("name")}
exitForward={props.actions.activateBelow}
exitRight={props.actions.activateBelow}
hasFocused={() => {
setActiveInput("dom");
props.actions.hasFocused?.();
}}
/>
</div>
</div>
Expand Down
6 changes: 3 additions & 3 deletions packages/frontend/src/model/contribution_monomial_editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,12 @@ export function ContributionMonomialEditor(props: ContributionMonomialEditorProp

return (
<Show
when={props.isActive || obList().some((ob) => ob === null)}
when={(props.focus?.hasFocus() ?? props.isActive) || obList().some((ob) => ob === null)}
fallback={
<div
class={`${styles.monomial} ${styles.collapsed}`}
onMouseDown={(evt) => {
props.focus?.setFocused(true);
props.hasFocused?.();
evt.preventDefault();
}}
Expand Down Expand Up @@ -91,14 +92,13 @@ export function ContributionMonomialEditor(props: ContributionMonomialEditorProp
obType={props.obType}
placeholder={props.placeholder}
isInvalid={props.isInvalid}
isActive={props.isActive}
focus={props.focus}
deleteBackward={props.deleteBackward}
deleteForward={props.deleteForward}
exitBackward={props.exitBackward}
exitForward={props.exitForward}
exitLeft={props.exitLeft}
exitRight={props.exitRight}
hasFocused={props.hasFocused}
insertKey={props.insertKey ?? ","}
startDelimiter={<div class={styles.delimiter}>{"["}</div>}
endDelimiter={<div class={styles.delimiter}>{"]"}</div>}
Expand Down
Loading
Loading