diff --git a/src/components/Edit/index.tsx b/src/components/Edit/index.tsx index 175f36f6..d9bbaea5 100644 --- a/src/components/Edit/index.tsx +++ b/src/components/Edit/index.tsx @@ -48,8 +48,10 @@ import { } from "@/primer-api"; import { defaultTreeReactFlowProps, + inlineTreeReactFlowProps, ScrollToDef, } from "@/components/TreeReactFlow"; +import { Mode } from "../Toolbar"; // hardcoded values (for now) const initialLevel: Level = "Expert"; @@ -198,6 +200,8 @@ const AppNoError = ({ undoAvailable: boolean; redoAvailable: boolean; }): JSX.Element => { + const initialMode = "tree 1"; + const [mode, setMode] = useState(initialMode); const [level, setLevel] = useState(initialLevel); const toggleLevel = (): void => { switch (level) { @@ -265,6 +269,17 @@ const AppNoError = ({ .sort((a, b) => cmpName(a.name, b.name)) .map((d) => d.name.baseName); + const treeProps = (() => { + switch (mode) { + case "text": + return defaultTreeReactFlowProps; + case "tree 1": + return defaultTreeReactFlowProps; + case "tree 2": + return inlineTreeReactFlowProps; + } + })(); + return (
@@ -272,7 +287,7 @@ const AppNoError = ({ sel && setSelection(sel)} defs={p.module.defs} @@ -286,9 +301,7 @@ const AppNoError = ({
{ - console.log("Toggle mode"); - }} + onModeChange={setMode} level={level} onLevelChange={toggleLevel} undoAvailable={p.undoAvailable} @@ -307,7 +320,7 @@ const AppNoError = ({ }) .then(p.setProg); }} - initialMode="tree" + initialMode={mode} />
@@ -324,6 +337,7 @@ const AppNoError = ({ defs={defs} initialEvalDef={evalTarget} typeOrKind={p.selectionTypeOrKind} + extraTreeProps={treeProps} />
diff --git a/src/components/EvalFull/index.tsx b/src/components/EvalFull/index.tsx index 1bcd429a..a8431a7c 100644 --- a/src/components/EvalFull/index.tsx +++ b/src/components/EvalFull/index.tsx @@ -2,7 +2,10 @@ import { useState } from "react"; import { NodeChange, ReactFlowProvider, useReactFlow } from "reactflow"; import { EvalFullResp, GlobalName, Level } from "@/primer-api"; import { SelectMenu, TreeReactFlowOne } from "@/components"; -import { defaultTreeReactFlowProps } from "../TreeReactFlow"; +import { + TreeReactFlowOneProps, + defaultTreeReactFlowProps, +} from "../TreeReactFlow"; export type EvalFullProps = { moduleName: string[]; @@ -13,12 +16,14 @@ export type EvalFullProps = { level: Level; defs: string[]; initialEvalDef: string | undefined; + extraTreeProps: Partial; }; const Evaluated = (p: { defName: GlobalName; evaluated?: EvalFullResp; level: Level; + extraTreeProps: Partial; }) => { const padding = 1.0; const { fitView } = useReactFlow(); @@ -34,6 +39,7 @@ const Evaluated = (p: { zoomBarProps={{ padding }} onNodesChange={onNodesChange} fitViewOptions={{ padding }} + {...p.extraTreeProps} /> ); }; @@ -47,6 +53,7 @@ export const EvalFull = ({ moduleName, level, initialEvalDef, + extraTreeProps, }: EvalFullProps): JSX.Element => { const [evalDef, setEvalDef0] = useState(initialEvalDef ?? disableEval); const setEvalDef = (e: string) => { @@ -73,6 +80,7 @@ export const EvalFull = ({ defName={{ qualifiedModule: moduleName, baseName: evalDef }} {...(evalFull.result ? { evaluated: evalFull.result } : {})} level={level} + extraTreeProps={extraTreeProps} />
diff --git a/src/components/SelectionInfo/index.tsx b/src/components/SelectionInfo/index.tsx index 153e8247..c63fd953 100644 --- a/src/components/SelectionInfo/index.tsx +++ b/src/components/SelectionInfo/index.tsx @@ -1,14 +1,22 @@ import { NodeChange, ReactFlowProvider, useReactFlow } from "reactflow"; import { Level, TypeOrKind } from "@/primer-api"; import { TreeReactFlowOne } from "@/components"; -import { defaultTreeReactFlowProps } from "../TreeReactFlow"; +import { + TreeReactFlowOneProps, + defaultTreeReactFlowProps, +} from "../TreeReactFlow"; export type SelectionInfoProps = { typeOrKind: TypeOrKind | undefined; level: Level; + extraTreeProps: Partial; }; -const TypeOrKindTree = (p: { typeOrKind: TypeOrKind; level: Level }) => { +const TypeOrKindTree = (p: { + typeOrKind: TypeOrKind; + level: Level; + extraTreeProps: Partial; +}) => { const padding = 1.0; const { fitView } = useReactFlow(); const onNodesChange = (_: NodeChange[]) => { @@ -23,6 +31,7 @@ const TypeOrKindTree = (p: { typeOrKind: TypeOrKind; level: Level }) => { zoomBarProps={{ padding }} onNodesChange={onNodesChange} fitViewOptions={{ padding }} + {...p.extraTreeProps} /> ); }; @@ -30,6 +39,7 @@ const TypeOrKindTree = (p: { typeOrKind: TypeOrKind; level: Level }) => { export const SelectionInfo = ({ typeOrKind, level, + extraTreeProps, }: SelectionInfoProps): JSX.Element => { return (
@@ -40,7 +50,11 @@ export const SelectionInfo = ({
- +
diff --git a/src/components/Toolbar/index.tsx b/src/components/Toolbar/index.tsx index f03bbd38..8adc3f1f 100644 --- a/src/components/Toolbar/index.tsx +++ b/src/components/Toolbar/index.tsx @@ -23,7 +23,7 @@ export type ToolbarProps = { undoAvailable: boolean; onClickUndo: MouseEventHandler; }; -export type Mode = "text" | "tree"; +export type Mode = "text" | "tree 1" | "tree 2"; const iconClasses = "stroke-[2] p-1"; const heavyIconClasses = "w-7 stroke-[3] p-1"; @@ -32,7 +32,8 @@ const modeSvg = (m: Mode) => { switch (m) { case "text": return ; - case "tree": + case "tree 1": + case "tree 2": return ; } }; @@ -40,8 +41,10 @@ const modeSvg = (m: Mode) => { const nextMode = (m: Mode): Mode => { switch (m) { case "text": - return "tree"; - case "tree": + return "tree 1"; + case "tree 1": + return "tree 2"; + case "tree 2": return "text"; } }; diff --git a/src/components/TreeReactFlow/TreeReactFlow.stories.tsx b/src/components/TreeReactFlow/TreeReactFlow.stories.tsx index 3526ab63..384d6398 100644 --- a/src/components/TreeReactFlow/TreeReactFlow.stories.tsx +++ b/src/components/TreeReactFlow/TreeReactFlow.stories.tsx @@ -1,6 +1,7 @@ import { ComponentStory, ComponentMeta } from "@storybook/react"; import { defaultTreeReactFlowProps, + inlineTreeReactFlowProps, TreeReactFlow, TreeReactFlowProps, } from "./"; @@ -294,3 +295,20 @@ export const OddAndEvenMiscStyles: ComponentStory = ( contents: { def: def5.name, node: { nodeType: "BodyNode", meta: 5 } }, }, }); +export const OddAndEvenInline: ComponentStory = ( + args: TreeReactFlowProps +) => + treeSized({ + ...inlineTreeReactFlowProps, + ...args, + defs: oddEvenTrees.map(([baseName, term]) => ({ + name: { qualifiedModule: [], baseName }, + term, + type_: emptyTypeTree(baseName), + })), + typeDefs: [], + selection: { + tag: "SelectionDef", + contents: { def: def5.name, node: { nodeType: "BodyNode", meta: 5 } }, + }, + }); diff --git a/src/components/TreeReactFlow/Types.ts b/src/components/TreeReactFlow/Types.ts index 1f36074d..8729ea94 100644 --- a/src/components/TreeReactFlow/Types.ts +++ b/src/components/TreeReactFlow/Types.ts @@ -195,6 +195,7 @@ export type PrimerCommonNodeProps = { width: number; height: number; selected: boolean; + style: "inline" | "corner"; }; /** Our edge type. Much like `PrimerNode`, `PrimerEdge` extends ReactFlow's `Edge`. diff --git a/src/components/TreeReactFlow/index.tsx b/src/components/TreeReactFlow/index.tsx index b861e017..5e4ddb19 100644 --- a/src/components/TreeReactFlow/index.tsx +++ b/src/components/TreeReactFlow/index.tsx @@ -92,7 +92,9 @@ type ReactFlowParams = { }; /** These properties are needed to construct nodes, but are invariant across all nodes. */ +export type NodeStyle = "corner" | "inline"; type NodeParams = { + style: NodeStyle; nodeWidth: number; nodeHeight: number; boxPadding: number; @@ -123,6 +125,7 @@ export const defaultTreeReactFlowProps: Pick< TreeReactFlowProps, "treePadding" | "forestLayout" | "defParams" | "layout" | keyof NodeParams > = { + style: "corner", level: "Expert", forestLayout: "Horizontal", treePadding: 100, @@ -135,6 +138,15 @@ export const defaultTreeReactFlowProps: Pick< margins: { child: 25, sibling: 18 }, }, }; +export const inlineTreeReactFlowProps: typeof defaultTreeReactFlowProps = { + ...defaultTreeReactFlowProps, + style: "inline", + nodeWidth: 100, + layout: { + ...defaultTreeReactFlowProps.layout, + margins: { child: 15, sibling: 12 }, + }, +}; // These should probably take a `GlobalName` instead, but we're not // quite there yet. @@ -146,47 +158,70 @@ const handle = (type: HandleType, position: Position) => ( ); const nodeTypes = { - primer: ({ data }: { data: PrimerNodeProps & PrimerCommonNodeProps }) => ( - <> - {handle("target", Position.Top)} - {handle("target", Position.Left)} -
-
- {data.contents} -
+ primer: ({ data }: { data: PrimerNodeProps & PrimerCommonNodeProps }) => { + const classes = (() => { + switch (data.style) { + case "corner": + return { + root: classNames( + { + "ring-4 ring-offset-4": data.selected, + "hover:ring-opacity-50": !data.selected, + }, + "flex items-center justify-center border-4 text-grey-tertiary", + flavorClasses(data.flavor) + ), + label: classNames( + "z-20 p-1 absolute rounded-full text-sm xl:text-base", + data.syntax ? "-top-4" : "-right-2 -top-4", + flavorLabelClasses(data.flavor) + ), + contents: classNames( + "block truncate px-1 font-code text-sm xl:text-base", + flavorContentClasses(data.flavor) + ), + }; + case "inline": + return { + root: classNames( + { + "ring-4 ring-offset-4": data.selected, + "hover:ring-opacity-50": !data.selected, + }, + "grid grid-cols-[2rem_auto] border-4 overflow-hidden text-grey-tertiary", + flavorClasses(data.flavor) + ), + label: classNames( + "flex items-center justify-center pr-1 text-sm xl:text-base", + flavorLabelClasses(data.flavor) + ), + contents: classNames( + "flex items-center truncate justify-self-center pr-1 font-code text-sm xl:text-base", + flavorContentClasses(data.flavor) + ), + }; + } + })(); + return ( + <> + {handle("target", Position.Top)} + {handle("target", Position.Left)}
- {flavorLabel(data.flavor)} +
{flavorLabel(data.flavor)}
+
{data.contents}
-
- {handle("source", Position.Bottom)} - {handle("source", Position.Right)} - - ), + {handle("source", Position.Bottom)} + {handle("source", Position.Right)} + + ); + }, "primer-simple": ({ data, }: { @@ -518,6 +553,7 @@ const makePrimerNode = async ( height: p.nodeHeight, selected, nodeData, + style: p.style, }; const edgeCommon = ( child: PrimerNode, @@ -604,7 +640,7 @@ const makePrimerNode = async ( ...common, // TODO This is necessary to ensure that all syntax labels fit. // It can be removed when we have dynamic node sizes. - width: 130, + width: 150, }, zIndex, }, @@ -773,6 +809,7 @@ const defToTree = async ( const defNameNode: PrimerNodeWithNestedAndDef = { id: defNodeId, data: { + style: p.style, def: def.name, width: p.nodeWidth * p.nameNodeMultipliers.width, height: p.nodeHeight * p.nameNodeMultipliers.height, @@ -850,6 +887,7 @@ const typeDefToTree = async ( id, type: "primer-typedef-param", data: { + style: p.style, def: def.name, width: p.nodeWidth, height: p.nodeHeight, @@ -931,6 +969,7 @@ const typeDefToTree = async ( id: consId, type: "primer-typedef-cons", data: { + style: p.style, def: def.name, name: cons.name, width: p.nodeWidth, @@ -967,6 +1006,7 @@ const typeDefToTree = async ( id: rootId, type: "primer-typedef-name", data: { + style: p.style, def: def.name, name: def.name, height: p.nodeHeight * p.nameNodeMultipliers.height,