diff --git a/src/App.tsx b/src/App.tsx index 0b531d5b..90ddffc9 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -44,7 +44,7 @@ const App = (): JSX.Element => { const devToolsMaxHeight = 500; const [devOpts, setDevOpts] = useState({ showIDs: false, - alwaysShowLabels: true, + alwaysShowLabels: false, }); useEffect(() => { diff --git a/src/components/TreeReactFlow/Flavor.ts b/src/components/TreeReactFlow/Flavor.ts index 503220bc..3e5436cc 100644 --- a/src/components/TreeReactFlow/Flavor.ts +++ b/src/components/TreeReactFlow/Flavor.ts @@ -6,6 +6,7 @@ import { } from "@/primer-api"; import classNames from "classnames"; import "./reactflow.css"; +import { Label } from "./Types"; export type NodeFlavor = | NodeFlavorTextBody @@ -421,24 +422,117 @@ export const flavorEdgeClasses = (flavor: NodeFlavor): string => { } }; -export const flavorLabel = (flavor: NodeFlavor): string => { +// if these are going to have an `| undefined`, that only really makes sense if there's a `Level` input +// actually, idk, some stuff like `let` really don't need labels + +// only used in beginner mode +export const flavorLabelBeginnerModeSyntax = ( + flavor: NodeFlavorNoBody +): Label | undefined => { + const basicLabel = (contents: string): Label => ({ + contents, + position: ["corner", "right"], + }); switch (flavor) { case "Hole": - return "⚠️"; + return basicLabel("misfit"); case "EmptyHole": - return "?"; + return basicLabel("hole"); case "Ann": - return ":"; + return basicLabel("type annotation"); case "App": - return "←"; + return basicLabel("apply"); case "APP": - return "←"; + return basicLabel("apply type"); + case "Lam": + return basicLabel("lambda"); + case "LAM": + return basicLabel("type lambda"); + case "Let": + return undefined; + case "LetType": + return undefined; + case "Letrec": + return basicLabel("recursive let"); + case "Case": + return basicLabel("match"); + case "CaseWith": + return basicLabel("with"); + case "TEmptyHole": + return basicLabel("type hole"); + case "THole": + return basicLabel("misfit"); + case "TFun": + return basicLabel("function type"); + case "TApp": + return basicLabel("apply type"); + case "TForall": + return basicLabel("forall"); + case "TLet": + return basicLabel("type let"); + case "PatternWildcard": + return basicLabel("🤷🏽‍♀️"); + case "KType": + return basicLabel("type"); + case "KHole": + return basicLabel("kind hole"); + case "KFun": + return basicLabel("type constructor"); + } +}; + +export const flavorLabelTextNode = ( + flavor: NodeFlavorTextBody | NodeFlavorPrimBody +): Label | undefined => { + const basicLabel = (contents: string): Label => ({ + contents, + position: ["corner", "right"], + }); + switch (flavor) { + case "Con": + return basicLabel("V"); + case "GlobalVar": + case "LocalVar": + return basicLabel("Var"); + case "PrimCon": + return basicLabel("V"); + case "TCon": + return basicLabel("T"); + case "TVar": + return basicLabel("Var"); + case "PatternCon": + return basicLabel("V"); + case "PrimPattern": + return basicLabel("V"); + case "VarBind": + return basicLabel("bind"); + case "TVarBind": + return basicLabel("type bind"); + } +}; + +export const flavorLabel = (flavor: NodeFlavor): Label | undefined => { + const contents = flavorLabelOld(flavor); + return { contents, position: ["corner", "right"] }; +}; +export const flavorLabelOld = (flavor: NodeFlavor): string => { + switch (flavor) { + case "Hole": + return "misfit"; + case "EmptyHole": + return "hole"; + case "Ann": + return "type annotation"; + case "App": + return "apply"; + case "APP": + return "apply type"; case "Con": return "V"; case "Lam": - return "λ"; + return "lambda"; case "LAM": - return "Λ"; + return "type lambda"; case "GlobalVar": return "Var"; case "LocalVar": @@ -448,29 +542,29 @@ export const flavorLabel = (flavor: NodeFlavor): string => { case "LetType": return "let type"; case "Letrec": - return "let rec"; + return "recursive let"; case "Case": - return "m"; + return "match"; case "CaseWith": - return "w"; + return "with"; case "PrimCon": return "V"; case "TEmptyHole": - return "?"; + return "type hole"; case "THole": - return "⚠️"; + return "misfit"; case "TCon": return "T"; case "TFun": - return "→"; + return "function type"; case "TVar": return "Var"; case "TApp": - return "←"; + return "apply type"; case "TForall": - return "∀"; + return "forall"; case "TLet": - return "let"; + return "type let"; case "Pattern": return ""; case "PatternCon": @@ -480,11 +574,11 @@ export const flavorLabel = (flavor: NodeFlavor): string => { case "PatternWildcard": return "🤷🏽‍♀️"; case "KType": - return "✱"; + return "type"; case "KHole": - return "?"; + return "kind hole"; case "KFun": - return "➜"; + return "type constructor"; case "VarBind": return "bind"; case "TVarBind": @@ -510,52 +604,52 @@ export const flavorIsSyntax = (flavor: NodeFlavorTextBody): boolean => { } }; -export const noBodyFlavorContents = (flavor: NodeFlavorNoBody): string => { +export const syntaxNodeContents = (flavor: NodeFlavorNoBody): string => { switch (flavor) { + case "Hole": + return "⚠️"; + case "EmptyHole": + return "?"; case "Ann": - return "type annotation"; + return ":"; case "App": - return "apply"; + return "←"; case "APP": - return "apply type"; + return "←"; + case "Lam": + return "λ"; + case "LAM": + return "Λ"; + case "Let": + return "let"; + case "LetType": + return "let type"; + case "Letrec": + return "let rec"; case "Case": - return "match"; + return "m"; case "CaseWith": - return "with"; - case "TFun": - return "function type"; - case "TApp": - return "apply type"; - case "Hole": - return "misfit"; - case "EmptyHole": - return "hole"; + return "w"; case "TEmptyHole": - return "type hole"; + return "?"; case "THole": - return "misfit"; + return "⚠️"; + case "TFun": + return "→"; + case "TApp": + return "←"; + case "TForall": + return "∀"; + case "TLet": + return "let"; case "PatternWildcard": return "🤷🏽‍♀️"; case "KType": - return "type"; + return "✱"; case "KHole": - return "kind hole"; + return "?"; case "KFun": - return "type constructor"; - case "LAM": - return "type lambda"; - case "Lam": - return "lambda"; - case "Let": - return "let"; - case "LetType": - return "let type"; - case "Letrec": - return "recursive let"; - case "TForall": - return "forall"; - case "TLet": - return "type let"; + return "➜"; } }; diff --git a/src/components/TreeReactFlow/Types.ts b/src/components/TreeReactFlow/Types.ts index 18481fb2..f453945d 100644 --- a/src/components/TreeReactFlow/Types.ts +++ b/src/components/TreeReactFlow/Types.ts @@ -139,7 +139,6 @@ export type PrimerNode = { data: PrimerCommonNodeProps & T; } & ( | { type: "primer"; data: PrimerNodeProps } - | { type: "primer-simple"; data: PrimerSimpleNodeProps } | { type: "primer-box"; data: PrimerBoxNodeProps } | { type: "primer-def-name"; data: PrimerDefNameNodeProps } | { type: "primer-typedef-name"; data: PrimerTypeDefNameNodeProps } @@ -184,22 +183,25 @@ export type NodeData = name: string; }; -/** Node properties. */ -export type PrimerNodeProps = { - nodeData: NodeData; - centerLabel: boolean; - flavor: NodeFlavorTextBody | NodeFlavorPrimBody | NodeFlavorNoBody; +export type Label = { contents: string; - hideLabel: boolean; - showIDs: boolean; + position: "center" | ["inline" | "corner", "left" | "right"]; }; -/** Properties for a simple node. */ -export type PrimerSimpleNodeProps = { +/** Node properties. */ +export type PrimerNodeProps = { nodeData: NodeData; - flavor: NodeFlavorNoBody; + hideLabel: boolean; showIDs: boolean; -}; +} & ( + | { + flavor: NodeFlavorNoBody; + // this exists iff we are in beginner mode - is there a better way to model this? + // contents?: string; + beginner: boolean; + } + | { flavor: NodeFlavorTextBody | NodeFlavorPrimBody; contents: string } +); /** Properties for a box node. */ export type PrimerBoxNodeProps = { @@ -239,7 +241,6 @@ export type PrimerCommonNodeProps = { height: number; padding?: Padding; 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 36bc5ad3..83070e82 100644 --- a/src/components/TreeReactFlow/index.tsx +++ b/src/components/TreeReactFlow/index.tsx @@ -41,7 +41,6 @@ import { treeMap, primerNodeWith, graphMap, - PrimerSimpleNodeProps, PrimerBoxNodeProps, PrimerCommonNodeProps, treeNodes, @@ -64,8 +63,9 @@ import { flavorLabel, flavorLabelClasses, flavorSort, - noBodyFlavorContents, + // flavorSyntaxBeginnerDescription, sortClasses, + syntaxNodeContents, } from "./Flavor"; import { ZoomBar, ZoomBarProps } from "./ZoomBar"; import { WasmLayoutType } from "@zxch3n/tidy/wasm_dist"; @@ -130,7 +130,6 @@ export const defaultTreeReactFlowProps: Pick< TreeReactFlowProps, "treePadding" | "forestLayout" | "defParams" | "layout" | keyof NodeParams > = { - style: "corner", level: "Expert", forestLayout: "Horizontal", treePadding: 100, @@ -146,15 +145,6 @@ export const defaultTreeReactFlowProps: Pick< showIDs: false, alwaysShowLabels: false, }; -export const inlineTreeReactFlowProps: typeof defaultTreeReactFlowProps = { - ...defaultTreeReactFlowProps, - style: "inline", - boxPadding: 35, - layout: { - ...defaultTreeReactFlowProps.layout, - margins: { child: 15, sibling: 12 }, - }, -}; // These should probably take a `GlobalName` instead, but we're not // quite there yet. @@ -173,12 +163,13 @@ const nodeTypes = { data: PrimerNodeProps & PrimerCommonNodeProps; id: string; }) => { + const label = data.hideLabel ? undefined : flavorLabel(data.flavor); return ( <> {handle("target", Position.Top)} {handle("target", Position.Left)}
- {data.hideLabel ? ( - <> - ) : ( + {label ? (
- {data.showIDs ? id : flavorLabel(data.flavor)} + {data.showIDs ? id : label.contents}
+ ) : ( + <> )}
-
{data.contents}
+ {} + {/* // contents: flavorSyntaxBeginnerDescription(node.body.contents), */} + {"contents" in data ? ( +
{data.contents}
+ ) : ( +
{syntaxNodeContents(data.flavor)}
+ )}
{handle("source", Position.Bottom)} @@ -227,46 +221,6 @@ const nodeTypes = { ); }, - "primer-simple": ({ - data, - id, - }: { - data: PrimerSimpleNodeProps & PrimerCommonNodeProps; - id: string; - }) => ( - <> - {handle("target", Position.Top)} - {handle("target", Position.Left)} -
- { -
- {data.showIDs ? id : flavorLabel(data.flavor)} -
- } -
- {handle("source", Position.Bottom)} - {handle("source", Position.Right)} - - ), "primer-box": ({ data, id, @@ -302,7 +256,7 @@ const nodeTypes = { flavorLabelClasses(data.flavor) )} > - {data.showIDs ? id : flavorLabel(data.flavor)} + {data.showIDs ? id : flavorLabel(data.flavor)?.contents} {handle("source", Position.Bottom)} @@ -568,7 +522,6 @@ const makePrimerNode = async ( height: p.nodeHeight, selected, nodeData, - style: p.style, showIDs: p.showIDs, }; const edgeCommon = ( @@ -583,9 +536,10 @@ const makePrimerNode = async ( targetHandle: isRight ? Position.Left : Position.Top, }); const width = (hideLabel: boolean) => - p.style == "inline" && !hideLabel - ? common.width + common.height - : common.width; + // common.style == "inline" && !hideLabel + // ? common.width + common.height + // : + common.width; switch (node.body.tag) { case "PrimBody": { const hideLabel = hideLabels; @@ -622,7 +576,7 @@ const makePrimerNode = async ( data: { flavor, contents, - centerLabel: false, + // centerLabel: false, hideLabel, ...common, width: width(hideLabel), @@ -648,7 +602,7 @@ const makePrimerNode = async ( data: { flavor, contents: name.baseName, - centerLabel: false, + // centerLabel: false, hideLabel, ...common, width: width(hideLabel), @@ -671,55 +625,42 @@ const makePrimerNode = async ( data: { flavor }, ...edgeCommon(child, isRight), }); - if (p.level == "Beginner") { - return [ - { - id, - type: "primer", - data: { - flavor, - contents: noBodyFlavorContents(node.body.contents), - centerLabel: node.children >= 2, - hideLabel: false, - ...common, - // TODO This is necessary to ensure that all syntax labels fit. - // It can be removed when we have dynamic node sizes. - width: 150, - }, - zIndex, - }, - makeChild, - [], - ]; - } else { - return [ - { - id, - type: "primer-simple", - data: { - flavor, - ...common, - // Square, with same height as other nodes. - width: common.height, - ...(flavorSort(flavor) == "kind" - ? { - padding: { - // Since these nodes are rotated, their width, - // as reported to the layout engine, is off by a factor of √2. - // We don't pad vertically since allowing some overlap in the y-axis actually looks better, - // due to the rotation and the fact that all non-leaf kind nodes have precisely two children. - left: (common.height * Math.sqrt(2) - common.height) / 2, - right: (common.height * Math.sqrt(2) - common.height) / 2, - }, - } - : {}), - }, - zIndex, + return [ + { + id, + type: "primer", + data: { + beginner: p.level == "Beginner", + flavor, + hideLabel: hideLabels, + ...common, + // Square, with same height as other nodes. + width: + p.level == "Beginner" + ? // centerLabel: node.children >= 2, + // hideLabel: false, + // contents: flavorSyntaxBeginnerDescription(node.body.contents), + // TODO This is necessary to ensure that all syntax labels fit. + 150 + : common.height, + ...(p.level != "Beginner" && flavorSort(flavor) == "kind" + ? { + padding: { + // Since these nodes are rotated, their width, + // as reported to the layout engine, is off by a factor of √2. + // We don't pad vertically since allowing some overlap in the y-axis actually looks better, + // due to the rotation and the fact that all non-leaf kind nodes have precisely two children. + left: (common.height * Math.sqrt(2) - common.height) / 2, + right: (common.height * Math.sqrt(2) - common.height) / 2, + }, + } + : {}), }, - makeChild, - [], - ]; - } + zIndex, + }, + makeChild, + [], + ]; } case "BoxBody": { const { fst: flavor, snd: t } = node.body.contents; @@ -892,7 +833,6 @@ 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, @@ -996,7 +936,6 @@ const typeDefToTree = async ( id, type: "primer-typedef-param", data: { - style: p.style, def: def.name, width: p.nodeWidth, height: p.nodeHeight, @@ -1087,7 +1026,6 @@ const typeDefToTree = async ( id: consId, type: "primer-typedef-cons", data: { - style: p.style, def: def.name, name: cons.name, width: p.nodeWidth, @@ -1125,7 +1063,6 @@ 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,