From bbc92d3840655643b52b52ccd4ab62a6edd7062e Mon Sep 17 00:00:00 2001 From: Stachu Korick Date: Mon, 3 Nov 2025 01:21:39 -0500 Subject: [PATCH 1/2] misc experiment with json canvas --- canvases/dark-jsoncanvas/config.yml | 2 + canvases/dark-jsoncanvas/main.dark | 920 ++++++++++++++++++++++++++++ packages/stachu/jsoncanvas.dark | 274 +++++++++ 3 files changed, 1196 insertions(+) create mode 100644 canvases/dark-jsoncanvas/config.yml create mode 100644 canvases/dark-jsoncanvas/main.dark create mode 100644 packages/stachu/jsoncanvas.dark diff --git a/canvases/dark-jsoncanvas/config.yml b/canvases/dark-jsoncanvas/config.yml new file mode 100644 index 0000000000..c6fec492d6 --- /dev/null +++ b/canvases/dark-jsoncanvas/config.yml @@ -0,0 +1,2 @@ +id: 22222222-2222-2222-2222-222222222222 +main: main diff --git a/canvases/dark-jsoncanvas/main.dark b/canvases/dark-jsoncanvas/main.dark new file mode 100644 index 0000000000..c2d614d5a5 --- /dev/null +++ b/canvases/dark-jsoncanvas/main.dark @@ -0,0 +1,920 @@ +// JSON Canvas demonstration canvas +// This demonstrates the Stachu.JsonCanvas module with HTTP endpoints + +[] +let _handler _req = + Stdlib.Http.response (Stdlib.String.toBytes "JSON Canvas demo is running!") 200L + + +[] +let _handler _req = + let html = + " + + + + + JSON Canvas Viewer + + + +
+

JSON Canvas Demo

+ + + + +
+
+ +
+ + +" + + Stdlib.Http.response (Stdlib.String.toBytes html) 200L + + +// Create a simple canvas with some nodes and edges +[] +let _handler _req = + let canvas = Stachu.JsonCanvas.empty () + + // Create some nodes + let node1 = + Stachu.JsonCanvas.Node.TextNode( + Stachu.JsonCanvas.TextNodeData + { id = "node1" + x = 0L + y = 0L + width = 250L + height = 100L + text = "Hello from Darklang!" + color = Stdlib.Option.Option.None } + ) + + let node2 = + Stachu.JsonCanvas.Node.TextNode( + Stachu.JsonCanvas.TextNodeData + { id = "node2" + x = 300L + y = 50L + width = 250L + height = 100L + text = "This is a JSON Canvas" + color = Stdlib.Option.Option.None } + ) + + let node3 = + Stachu.JsonCanvas.Node.LinkNode( + Stachu.JsonCanvas.LinkNodeData + { id = "node3" + x = 100L + y = 200L + width = 300L + height = 80L + url = "https://jsoncanvas.org" + color = Stdlib.Option.Option.None } + ) + + let node4 = + Stachu.JsonCanvas.Node.GroupNode( + Stachu.JsonCanvas.GroupNodeData + { id = "group1" + x = -50L + y = -50L + width = 650L + height = 400L + label = Stdlib.Option.Option.Some("Main Group") + background = Stdlib.Option.Option.None + backgroundStyle = Stdlib.Option.Option.None + color = Stdlib.Option.Option.None } + ) + + // Create edges connecting the nodes + let edge1 = Stachu.JsonCanvas.Edge.simple "edge1" "node1" "node2" + + let edge2 = + (Stachu.JsonCanvas.Edge.simple "edge2" "node2" "node3") + |> Stachu.JsonCanvas.Edge.withLabel "learn more" + + // Build the canvas + let canvas = + canvas + |> Stachu.JsonCanvas.addNode node4 + |> Stachu.JsonCanvas.addNode node1 + |> Stachu.JsonCanvas.addNode node2 + |> Stachu.JsonCanvas.addNode node3 + |> Stachu.JsonCanvas.addEdge edge1 + |> Stachu.JsonCanvas.addEdge edge2 + + let nodeIds = Stachu.JsonCanvas.getNodeIds canvas + let nodeIdsStr = Stdlib.String.join nodeIds ", " + + let stats = Stachu.JsonCanvas.countNodesByType canvas + + let responseText = + $"Canvas created successfully!\n\nNode IDs: {nodeIdsStr}\n\nNode counts: {stats}\n\nTotal edges: {Builtin.int64ToString (Stdlib.List.length canvas.edges)}" + + Stdlib.Http.response (Stdlib.String.toBytes responseText) 200L + + +// Get information about a canvas structure +[] +let _handler _req = + let canvas = Stachu.JsonCanvas.empty () + + // Add a variety of nodes + let canvas = + canvas + |> Stachu.JsonCanvas.addNode + (Stachu.JsonCanvas.Node.TextNode( + Stachu.JsonCanvas.TextNodeData + { id = "text1" + x = 0L + y = 0L + width = 200L + height = 100L + text = "Text node 1" + color = Stdlib.Option.Option.None } + )) + |> Stachu.JsonCanvas.addNode + (Stachu.JsonCanvas.Node.TextNode( + Stachu.JsonCanvas.TextNodeData + { id = "text2" + x = 250L + y = 0L + width = 200L + height = 100L + text = "Text node 2" + color = Stdlib.Option.Option.None } + )) + |> Stachu.JsonCanvas.addNode + (Stachu.JsonCanvas.Node.FileNode( + Stachu.JsonCanvas.FileNodeData + { id = "file1" + x = 0L + y = 150L + width = 200L + height = 100L + file = "/path/to/file.md" + subpath = Stdlib.Option.Option.None + color = Stdlib.Option.Option.None } + )) + |> Stachu.JsonCanvas.addNode + (Stachu.JsonCanvas.Node.LinkNode( + Stachu.JsonCanvas.LinkNodeData + { id = "link1" + x = 250L + y = 150L + width = 200L + height = 100L + url = "https://darklang.com" + color = Stdlib.Option.Option.None } + )) + + let stats = Stachu.JsonCanvas.countNodesByType canvas + + let responseText = $"Canvas Statistics:\n{stats}\n\nTotal nodes: {Builtin.int64ToString (Stdlib.List.length canvas.nodes)}" + + Stdlib.Http.response (Stdlib.String.toBytes responseText) 200L + + +// Demonstrate building a mind map structure +[] +let _handler _req = + let canvas = Stachu.JsonCanvas.empty () + + // Central idea + let centerNode = + Stachu.JsonCanvas.Node.TextNode( + Stachu.JsonCanvas.TextNodeData + { id = "center" + x = 400L + y = 300L + width = 200L + height = 80L + text = "Darklang" + color = Stdlib.Option.Option.None } + ) + + // Branch ideas + let branch1 = + Stachu.JsonCanvas.Node.TextNode( + Stachu.JsonCanvas.TextNodeData + { id = "branch1" + x = 100L + y = 200L + width = 150L + height = 60L + text = "Type Safety" + color = Stdlib.Option.Option.None } + ) + + let branch2 = + Stachu.JsonCanvas.Node.TextNode( + Stachu.JsonCanvas.TextNodeData + { id = "branch2" + x = 100L + y = 400L + width = 150L + height = 60L + text = "Packages" + color = Stdlib.Option.Option.None } + ) + + let branch3 = + Stachu.JsonCanvas.Node.TextNode( + Stachu.JsonCanvas.TextNodeData + { id = "branch3" + x = 700L + y = 200L + width = 150L + height = 60L + text = "HTTP Handlers" + color = Stdlib.Option.Option.None } + ) + + let branch4 = + Stachu.JsonCanvas.Node.TextNode( + Stachu.JsonCanvas.TextNodeData + { id = "branch4" + x = 700L + y = 400L + width = 150L + height = 60L + text = "Cloud Native" + color = Stdlib.Option.Option.None } + ) + + // Connect branches to center + let edge1 = + (Stachu.JsonCanvas.Edge.simple "e1" "branch1" "center") + |> Stachu.JsonCanvas.Edge.withLabel "enables" + + let edge2 = + (Stachu.JsonCanvas.Edge.simple "e2" "branch2" "center") + |> Stachu.JsonCanvas.Edge.withLabel "extend" + + let edge3 = + (Stachu.JsonCanvas.Edge.simple "e3" "center" "branch3") + |> Stachu.JsonCanvas.Edge.withLabel "supports" + + let edge4 = + (Stachu.JsonCanvas.Edge.simple "e4" "center" "branch4") + |> Stachu.JsonCanvas.Edge.withLabel "runs in" + + // Build the mindmap + let mindmap = + canvas + |> Stachu.JsonCanvas.addNode centerNode + |> Stachu.JsonCanvas.addNode branch1 + |> Stachu.JsonCanvas.addNode branch2 + |> Stachu.JsonCanvas.addNode branch3 + |> Stachu.JsonCanvas.addNode branch4 + |> Stachu.JsonCanvas.addEdge edge1 + |> Stachu.JsonCanvas.addEdge edge2 + |> Stachu.JsonCanvas.addEdge edge3 + |> Stachu.JsonCanvas.addEdge edge4 + + let nodeCount = Stdlib.List.length mindmap.nodes + let edgeCount = Stdlib.List.length mindmap.edges + + let responseText = + $"Mind Map Created!\n\nNodes: {Builtin.int64ToString nodeCount}\nEdges: {Builtin.int64ToString edgeCount}\n\n{Stachu.JsonCanvas.countNodesByType mindmap}" + + Stdlib.Http.response (Stdlib.String.toBytes responseText) 200L + + +// Demonstrate workflow/flowchart structure +[] +let _handler _req = + let canvas = Stachu.JsonCanvas.empty () + + // Workflow steps + let step1 = + Stachu.JsonCanvas.Node.TextNode( + Stachu.JsonCanvas.TextNodeData + { id = "step1" + x = 50L + y = 50L + width = 180L + height = 80L + text = "Receive Request" + color = Stdlib.Option.Option.None } + ) + + let step2 = + Stachu.JsonCanvas.Node.TextNode( + Stachu.JsonCanvas.TextNodeData + { id = "step2" + x = 50L + y = 180L + width = 180L + height = 80L + text = "Parse JSON" + color = Stdlib.Option.Option.None } + ) + + let step3 = + Stachu.JsonCanvas.Node.TextNode( + Stachu.JsonCanvas.TextNodeData + { id = "step3" + x = 50L + y = 310L + width = 180L + height = 80L + text = "Validate Data" + color = Stdlib.Option.Option.None } + ) + + let step4 = + Stachu.JsonCanvas.Node.TextNode( + Stachu.JsonCanvas.TextNodeData + { id = "step4" + x = 50L + y = 440L + width = 180L + height = 80L + text = "Process & Respond" + color = Stdlib.Option.Option.None } + ) + + let errorNode = + Stachu.JsonCanvas.Node.TextNode( + Stachu.JsonCanvas.TextNodeData + { id = "error" + x = 300L + y = 250L + width = 180L + height = 80L + text = "Return Error" + color = Stdlib.Option.Option.None } + ) + + // Connect the workflow + let flow1 = Stachu.JsonCanvas.Edge.simple "f1" "step1" "step2" + let flow2 = Stachu.JsonCanvas.Edge.simple "f2" "step2" "step3" + + let flow3 = + (Stachu.JsonCanvas.Edge.simple "f3" "step3" "step4") + |> Stachu.JsonCanvas.Edge.withLabel "valid" + + let flow4 = + (Stachu.JsonCanvas.Edge.simple "f4" "step3" "error") + |> Stachu.JsonCanvas.Edge.withLabel "invalid" + + // Build the workflow + let workflow = + canvas + |> Stachu.JsonCanvas.addNode step1 + |> Stachu.JsonCanvas.addNode step2 + |> Stachu.JsonCanvas.addNode step3 + |> Stachu.JsonCanvas.addNode step4 + |> Stachu.JsonCanvas.addNode errorNode + |> Stachu.JsonCanvas.addEdge flow1 + |> Stachu.JsonCanvas.addEdge flow2 + |> Stachu.JsonCanvas.addEdge flow3 + |> Stachu.JsonCanvas.addEdge flow4 + + let responseText = + $"Workflow Canvas Created!\n\nThis demonstrates a simple HTTP request processing workflow.\n\nNodes: {Builtin.int64ToString (Stdlib.List.length workflow.nodes)}\nEdges: {Builtin.int64ToString (Stdlib.List.length workflow.edges)}" + + Stdlib.Http.response (Stdlib.String.toBytes responseText) 200L + + +// JSON API Endpoints +[] +let _handler _req = + let canvas = Stachu.JsonCanvas.empty () + + let node4 = + Stachu.JsonCanvas.Node.GroupNode( + Stachu.JsonCanvas.GroupNodeData + { id = "group1" + x = 0L + y = 0L + width = 600L + height = 400L + label = Stdlib.Option.Option.Some("Main Group") + background = Stdlib.Option.Option.None + backgroundStyle = Stdlib.Option.Option.None + color = Stdlib.Option.Option.None } + ) + + let node1 = + Stachu.JsonCanvas.Node.TextNode( + Stachu.JsonCanvas.TextNodeData + { id = "node1" + x = 20L + y = 50L + width = 250L + height = 100L + text = "Hello from Darklang!" + color = Stdlib.Option.Option.None } + ) + + let node2 = + Stachu.JsonCanvas.Node.TextNode( + Stachu.JsonCanvas.TextNodeData + { id = "node2" + x = 320L + y = 50L + width = 250L + height = 100L + text = "This is a JSON Canvas" + color = Stdlib.Option.Option.None } + ) + + let node3 = + Stachu.JsonCanvas.Node.LinkNode( + Stachu.JsonCanvas.LinkNodeData + { id = "node3" + x = 150L + y = 220L + width = 300L + height = 80L + url = "https://jsoncanvas.org" + color = Stdlib.Option.Option.None } + ) + + let edge1 = Stachu.JsonCanvas.Edge.simple "edge1" "node1" "node2" + + let edge2 = + (Stachu.JsonCanvas.Edge.simple "edge2" "node2" "node3") + |> Stachu.JsonCanvas.Edge.withLabel "learn more" + + let canvas = + canvas + |> Stachu.JsonCanvas.addNode node4 + |> Stachu.JsonCanvas.addNode node1 + |> Stachu.JsonCanvas.addNode node2 + |> Stachu.JsonCanvas.addNode node3 + |> Stachu.JsonCanvas.addEdge edge1 + |> Stachu.JsonCanvas.addEdge edge2 + + let json = Stachu.JsonCanvas.Json.canvasToJson canvas + + Stdlib.Http.response (Stdlib.String.toBytes json) 200L + + +[] +let _handler _req = + let canvas = Stachu.JsonCanvas.empty () + + let centerNode = + Stachu.JsonCanvas.Node.TextNode( + Stachu.JsonCanvas.TextNodeData + { id = "center" + x = 400L + y = 300L + width = 200L + height = 80L + text = "Darklang" + color = Stdlib.Option.Option.None } + ) + + let branch1 = + Stachu.JsonCanvas.Node.TextNode( + Stachu.JsonCanvas.TextNodeData + { id = "branch1" + x = 100L + y = 200L + width = 150L + height = 60L + text = "Type Safety" + color = Stdlib.Option.Option.None } + ) + + let branch2 = + Stachu.JsonCanvas.Node.TextNode( + Stachu.JsonCanvas.TextNodeData + { id = "branch2" + x = 100L + y = 400L + width = 150L + height = 60L + text = "Packages" + color = Stdlib.Option.Option.None } + ) + + let branch3 = + Stachu.JsonCanvas.Node.TextNode( + Stachu.JsonCanvas.TextNodeData + { id = "branch3" + x = 700L + y = 200L + width = 150L + height = 60L + text = "HTTP Handlers" + color = Stdlib.Option.Option.None } + ) + + let branch4 = + Stachu.JsonCanvas.Node.TextNode( + Stachu.JsonCanvas.TextNodeData + { id = "branch4" + x = 700L + y = 400L + width = 150L + height = 60L + text = "Cloud Native" + color = Stdlib.Option.Option.None } + ) + + let edge1 = + (Stachu.JsonCanvas.Edge.simple "e1" "branch1" "center") + |> Stachu.JsonCanvas.Edge.withLabel "enables" + + let edge2 = + (Stachu.JsonCanvas.Edge.simple "e2" "branch2" "center") + |> Stachu.JsonCanvas.Edge.withLabel "extend" + + let edge3 = + (Stachu.JsonCanvas.Edge.simple "e3" "center" "branch3") + |> Stachu.JsonCanvas.Edge.withLabel "supports" + + let edge4 = + (Stachu.JsonCanvas.Edge.simple "e4" "center" "branch4") + |> Stachu.JsonCanvas.Edge.withLabel "runs in" + + let mindmap = + canvas + |> Stachu.JsonCanvas.addNode centerNode + |> Stachu.JsonCanvas.addNode branch1 + |> Stachu.JsonCanvas.addNode branch2 + |> Stachu.JsonCanvas.addNode branch3 + |> Stachu.JsonCanvas.addNode branch4 + |> Stachu.JsonCanvas.addEdge edge1 + |> Stachu.JsonCanvas.addEdge edge2 + |> Stachu.JsonCanvas.addEdge edge3 + |> Stachu.JsonCanvas.addEdge edge4 + + let json = Stachu.JsonCanvas.Json.canvasToJson mindmap + + Stdlib.Http.response (Stdlib.String.toBytes json) 200L + + +[] +let _handler _req = + let canvas = Stachu.JsonCanvas.empty () + + let step1 = + Stachu.JsonCanvas.Node.TextNode( + Stachu.JsonCanvas.TextNodeData + { id = "step1" + x = 50L + y = 50L + width = 180L + height = 80L + text = "Receive Request" + color = Stdlib.Option.Option.None } + ) + + let step2 = + Stachu.JsonCanvas.Node.TextNode( + Stachu.JsonCanvas.TextNodeData + { id = "step2" + x = 50L + y = 180L + width = 180L + height = 80L + text = "Parse JSON" + color = Stdlib.Option.Option.None } + ) + + let step3 = + Stachu.JsonCanvas.Node.TextNode( + Stachu.JsonCanvas.TextNodeData + { id = "step3" + x = 50L + y = 310L + width = 180L + height = 80L + text = "Validate Data" + color = Stdlib.Option.Option.None } + ) + + let step4 = + Stachu.JsonCanvas.Node.TextNode( + Stachu.JsonCanvas.TextNodeData + { id = "step4" + x = 50L + y = 440L + width = 180L + height = 80L + text = "Process & Respond" + color = Stdlib.Option.Option.None } + ) + + let errorNode = + Stachu.JsonCanvas.Node.TextNode( + Stachu.JsonCanvas.TextNodeData + { id = "error" + x = 300L + y = 250L + width = 180L + height = 80L + text = "Return Error" + color = Stdlib.Option.Option.None } + ) + + let flow1 = Stachu.JsonCanvas.Edge.simple "f1" "step1" "step2" + let flow2 = Stachu.JsonCanvas.Edge.simple "f2" "step2" "step3" + + let flow3 = + (Stachu.JsonCanvas.Edge.simple "f3" "step3" "step4") + |> Stachu.JsonCanvas.Edge.withLabel "valid" + + let flow4 = + (Stachu.JsonCanvas.Edge.simple "f4" "step3" "error") + |> Stachu.JsonCanvas.Edge.withLabel "invalid" + + let workflow = + canvas + |> Stachu.JsonCanvas.addNode step1 + |> Stachu.JsonCanvas.addNode step2 + |> Stachu.JsonCanvas.addNode step3 + |> Stachu.JsonCanvas.addNode step4 + |> Stachu.JsonCanvas.addNode errorNode + |> Stachu.JsonCanvas.addEdge flow1 + |> Stachu.JsonCanvas.addEdge flow2 + |> Stachu.JsonCanvas.addEdge flow3 + |> Stachu.JsonCanvas.addEdge flow4 + + let json = Stachu.JsonCanvas.Json.canvasToJson workflow + + Stdlib.Http.response (Stdlib.String.toBytes json) 200L diff --git a/packages/stachu/jsoncanvas.dark b/packages/stachu/jsoncanvas.dark new file mode 100644 index 0000000000..beedd776ea --- /dev/null +++ b/packages/stachu/jsoncanvas.dark @@ -0,0 +1,274 @@ +// JSON Canvas type definitions +// Spec: https://jsoncanvas.org/spec/1.0/ + +module Stachu = + module JsonCanvas = + // Color can be either a preset number (1-6) or a hex string + type Color = + | Preset of Int64 + | Hex of String + + // Side of a node where an edge connects + type Side = + | Top + | Right + | Bottom + | Left + + // Endpoint shape for edges + type EndpointStyle = + | None + | Arrow + + // Background style for group nodes + type BackgroundStyle = + | Cover + | Ratio + | Repeat + + // Record types for each node variant + type TextNodeData = + { id: String + x: Int64 + y: Int64 + width: Int64 + height: Int64 + text: String + color: Stdlib.Option.Option } + + type FileNodeData = + { id: String + x: Int64 + y: Int64 + width: Int64 + height: Int64 + file: String + subpath: Stdlib.Option.Option + color: Stdlib.Option.Option } + + type LinkNodeData = + { id: String + x: Int64 + y: Int64 + width: Int64 + height: Int64 + url: String + color: Stdlib.Option.Option } + + type GroupNodeData = + { id: String + x: Int64 + y: Int64 + width: Int64 + height: Int64 + label: Stdlib.Option.Option + background: Stdlib.Option.Option + backgroundStyle: Stdlib.Option.Option + color: Stdlib.Option.Option } + + // Node types using record types + type Node = + | TextNode of TextNodeData + | FileNode of FileNodeData + | LinkNode of LinkNodeData + | GroupNode of GroupNodeData + + // Edge connecting nodes + type Edge = + { id: String + fromNode: String + toNode: String + fromSide: Stdlib.Option.Option + toSide: Stdlib.Option.Option + fromEnd: Stdlib.Option.Option + toEnd: Stdlib.Option.Option + color: Stdlib.Option.Option + label: Stdlib.Option.Option } + + // Canvas is the top-level structure + type Canvas = + { nodes: List + edges: List } + + + // Helper functions for creating edges + module Edge = + let simple (id: String) (fromNode: String) (toNode: String) : Edge = + Edge + { id = id + fromNode = fromNode + toNode = toNode + fromSide = Stdlib.Option.Option.None + toSide = Stdlib.Option.Option.None + fromEnd = Stdlib.Option.Option.None + toEnd = Stdlib.Option.Option.None + color = Stdlib.Option.Option.None + label = Stdlib.Option.Option.None } + + let withLabel + (edge: Edge) + (label: String) + : Edge = + Edge + { id = edge.id + fromNode = edge.fromNode + toNode = edge.toNode + fromSide = edge.fromSide + toSide = edge.toSide + fromEnd = edge.fromEnd + toEnd = edge.toEnd + color = edge.color + label = Stdlib.Option.Option.Some(label) } + + + // Create an empty canvas + let empty () : Canvas = Canvas { nodes = []; edges = [] } + + // Add a node to a canvas + let addNode (canvas: Canvas) (node: Node) : Canvas = + Canvas + { nodes = Stdlib.List.append canvas.nodes [ node ] + edges = canvas.edges } + + // Add an edge to a canvas + let addEdge (canvas: Canvas) (edge: Edge) : Canvas = + Canvas + { nodes = canvas.nodes + edges = Stdlib.List.append canvas.edges [ edge ] } + + // Get all node IDs + let getNodeIds (canvas: Canvas) : List = + canvas.nodes + |> Stdlib.List.map (fun node -> + match node with + | TextNode data -> data.id + | FileNode data -> data.id + | LinkNode data -> data.id + | GroupNode data -> data.id) + + // Count nodes by type + let countNodesByType (canvas: Canvas) : String = + let textCount = + canvas.nodes + |> Stdlib.List.filter (fun node -> + match node with + | TextNode _ -> true + | _ -> false) + |> Stdlib.List.length + + let fileCount = + canvas.nodes + |> Stdlib.List.filter (fun node -> + match node with + | FileNode _ -> true + | _ -> false) + |> Stdlib.List.length + + let linkCount = + canvas.nodes + |> Stdlib.List.filter (fun node -> + match node with + | LinkNode _ -> true + | _ -> false) + |> Stdlib.List.length + + let groupCount = + canvas.nodes + |> Stdlib.List.filter (fun node -> + match node with + | GroupNode _ -> true + | _ -> false) + |> Stdlib.List.length + + $"Text: {Builtin.int64ToString textCount}, File: {Builtin.int64ToString fileCount}, Link: {Builtin.int64ToString linkCount}, Group: {Builtin.int64ToString groupCount}" + + + // JSON Serialization + module Json = + let nodeToJson (node: Node) : Darklang.Stdlib.AltJson.Json = + match node with + | TextNode data -> + Darklang.Stdlib.AltJson.Json.Object( + [ ("id", Darklang.Stdlib.AltJson.Json.String(data.id)) + ("type", Darklang.Stdlib.AltJson.Json.String("text")) + ("x", Darklang.Stdlib.AltJson.Json.Number(Builtin.int64ToFloat data.x)) + ("y", Darklang.Stdlib.AltJson.Json.Number(Builtin.int64ToFloat data.y)) + ("width", Darklang.Stdlib.AltJson.Json.Number(Builtin.int64ToFloat data.width)) + ("height", Darklang.Stdlib.AltJson.Json.Number(Builtin.int64ToFloat data.height)) + ("text", Darklang.Stdlib.AltJson.Json.String(data.text)) ] + ) + + | LinkNode data -> + Darklang.Stdlib.AltJson.Json.Object( + [ ("id", Darklang.Stdlib.AltJson.Json.String(data.id)) + ("type", Darklang.Stdlib.AltJson.Json.String("link")) + ("x", Darklang.Stdlib.AltJson.Json.Number(Builtin.int64ToFloat data.x)) + ("y", Darklang.Stdlib.AltJson.Json.Number(Builtin.int64ToFloat data.y)) + ("width", Darklang.Stdlib.AltJson.Json.Number(Builtin.int64ToFloat data.width)) + ("height", Darklang.Stdlib.AltJson.Json.Number(Builtin.int64ToFloat data.height)) + ("url", Darklang.Stdlib.AltJson.Json.String(data.url)) ] + ) + + | FileNode data -> + Darklang.Stdlib.AltJson.Json.Object( + [ ("id", Darklang.Stdlib.AltJson.Json.String(data.id)) + ("type", Darklang.Stdlib.AltJson.Json.String("file")) + ("x", Darklang.Stdlib.AltJson.Json.Number(Builtin.int64ToFloat data.x)) + ("y", Darklang.Stdlib.AltJson.Json.Number(Builtin.int64ToFloat data.y)) + ("width", Darklang.Stdlib.AltJson.Json.Number(Builtin.int64ToFloat data.width)) + ("height", Darklang.Stdlib.AltJson.Json.Number(Builtin.int64ToFloat data.height)) + ("file", Darklang.Stdlib.AltJson.Json.String(data.file)) ] + ) + + | GroupNode data -> + let fields = + [ ("id", Darklang.Stdlib.AltJson.Json.String(data.id)) + ("type", Darklang.Stdlib.AltJson.Json.String("group")) + ("x", Darklang.Stdlib.AltJson.Json.Number(Builtin.int64ToFloat data.x)) + ("y", Darklang.Stdlib.AltJson.Json.Number(Builtin.int64ToFloat data.y)) + ("width", Darklang.Stdlib.AltJson.Json.Number(Builtin.int64ToFloat data.width)) + ("height", Darklang.Stdlib.AltJson.Json.Number(Builtin.int64ToFloat data.height)) ] + + let fieldsWithLabel = + match data.label with + | Some label -> + Stdlib.List.append + fields + [ ("label", Darklang.Stdlib.AltJson.Json.String(label)) ] + | None -> fields + + Darklang.Stdlib.AltJson.Json.Object(fieldsWithLabel) + + let edgeToJson (edge: Edge) : Darklang.Stdlib.AltJson.Json = + let fields = + [ ("id", Darklang.Stdlib.AltJson.Json.String(edge.id)) + ("fromNode", Darklang.Stdlib.AltJson.Json.String(edge.fromNode)) + ("toNode", Darklang.Stdlib.AltJson.Json.String(edge.toNode)) ] + + let fieldsWithLabel = + match edge.label with + | Some label -> + Stdlib.List.append + fields + [ ("label", Darklang.Stdlib.AltJson.Json.String(label)) ] + | None -> fields + + Darklang.Stdlib.AltJson.Json.Object(fieldsWithLabel) + + let canvasToJson (canvas: Canvas) : String = + let nodesJson = + canvas.nodes + |> Stdlib.List.map (fun node -> nodeToJson node) + |> Darklang.Stdlib.AltJson.Json.Array + + let edgesJson = + canvas.edges + |> Stdlib.List.map (fun edge -> edgeToJson edge) + |> Darklang.Stdlib.AltJson.Json.Array + + let canvasJson = + Darklang.Stdlib.AltJson.Json.Object( + [ ("nodes", nodesJson); ("edges", edgesJson) ] + ) + + Darklang.Stdlib.AltJson.format canvasJson From f186a5769c4393cd00814f7cfb91d7778be008d9 Mon Sep 17 00:00:00 2001 From: Stachu Korick Date: Mon, 3 Nov 2025 13:55:39 -0500 Subject: [PATCH 2/2] minor cleanup --- canvases/dark-jsoncanvas/main.dark | 555 +------------------------- packages/stachu/jsoncanvas.dark | 612 ++++++++++++++++------------- 2 files changed, 353 insertions(+), 814 deletions(-) diff --git a/canvases/dark-jsoncanvas/main.dark b/canvases/dark-jsoncanvas/main.dark index c2d614d5a5..581c8f86f3 100644 --- a/canvases/dark-jsoncanvas/main.dark +++ b/canvases/dark-jsoncanvas/main.dark @@ -311,79 +311,9 @@ let _handler _req = // Create a simple canvas with some nodes and edges [] let _handler _req = - let canvas = Stachu.JsonCanvas.empty () - - // Create some nodes - let node1 = - Stachu.JsonCanvas.Node.TextNode( - Stachu.JsonCanvas.TextNodeData - { id = "node1" - x = 0L - y = 0L - width = 250L - height = 100L - text = "Hello from Darklang!" - color = Stdlib.Option.Option.None } - ) - - let node2 = - Stachu.JsonCanvas.Node.TextNode( - Stachu.JsonCanvas.TextNodeData - { id = "node2" - x = 300L - y = 50L - width = 250L - height = 100L - text = "This is a JSON Canvas" - color = Stdlib.Option.Option.None } - ) - - let node3 = - Stachu.JsonCanvas.Node.LinkNode( - Stachu.JsonCanvas.LinkNodeData - { id = "node3" - x = 100L - y = 200L - width = 300L - height = 80L - url = "https://jsoncanvas.org" - color = Stdlib.Option.Option.None } - ) - - let node4 = - Stachu.JsonCanvas.Node.GroupNode( - Stachu.JsonCanvas.GroupNodeData - { id = "group1" - x = -50L - y = -50L - width = 650L - height = 400L - label = Stdlib.Option.Option.Some("Main Group") - background = Stdlib.Option.Option.None - backgroundStyle = Stdlib.Option.Option.None - color = Stdlib.Option.Option.None } - ) - - // Create edges connecting the nodes - let edge1 = Stachu.JsonCanvas.Edge.simple "edge1" "node1" "node2" - - let edge2 = - (Stachu.JsonCanvas.Edge.simple "edge2" "node2" "node3") - |> Stachu.JsonCanvas.Edge.withLabel "learn more" - - // Build the canvas - let canvas = - canvas - |> Stachu.JsonCanvas.addNode node4 - |> Stachu.JsonCanvas.addNode node1 - |> Stachu.JsonCanvas.addNode node2 - |> Stachu.JsonCanvas.addNode node3 - |> Stachu.JsonCanvas.addEdge edge1 - |> Stachu.JsonCanvas.addEdge edge2 - + let canvas = Stachu.JsonCanvas.Examples.basic () let nodeIds = Stachu.JsonCanvas.getNodeIds canvas let nodeIdsStr = Stdlib.String.join nodeIds ", " - let stats = Stachu.JsonCanvas.countNodesByType canvas let responseText = @@ -395,59 +325,14 @@ let _handler _req = // Get information about a canvas structure [] let _handler _req = - let canvas = Stachu.JsonCanvas.empty () - - // Add a variety of nodes let canvas = - canvas - |> Stachu.JsonCanvas.addNode - (Stachu.JsonCanvas.Node.TextNode( - Stachu.JsonCanvas.TextNodeData - { id = "text1" - x = 0L - y = 0L - width = 200L - height = 100L - text = "Text node 1" - color = Stdlib.Option.Option.None } - )) - |> Stachu.JsonCanvas.addNode - (Stachu.JsonCanvas.Node.TextNode( - Stachu.JsonCanvas.TextNodeData - { id = "text2" - x = 250L - y = 0L - width = 200L - height = 100L - text = "Text node 2" - color = Stdlib.Option.Option.None } - )) - |> Stachu.JsonCanvas.addNode - (Stachu.JsonCanvas.Node.FileNode( - Stachu.JsonCanvas.FileNodeData - { id = "file1" - x = 0L - y = 150L - width = 200L - height = 100L - file = "/path/to/file.md" - subpath = Stdlib.Option.Option.None - color = Stdlib.Option.Option.None } - )) - |> Stachu.JsonCanvas.addNode - (Stachu.JsonCanvas.Node.LinkNode( - Stachu.JsonCanvas.LinkNodeData - { id = "link1" - x = 250L - y = 150L - width = 200L - height = 100L - url = "https://darklang.com" - color = Stdlib.Option.Option.None } - )) + (Stachu.JsonCanvas.empty ()) + |> Stachu.JsonCanvas.addNode (Stachu.JsonCanvas.Node.text "text1" 0L 0L 200L 100L "Text node 1") + |> Stachu.JsonCanvas.addNode (Stachu.JsonCanvas.Node.text "text2" 250L 0L 200L 100L "Text node 2") + |> Stachu.JsonCanvas.addNode (Stachu.JsonCanvas.Node.file "file1" 0L 150L 200L 100L "/path/to/file.md") + |> Stachu.JsonCanvas.addNode (Stachu.JsonCanvas.Node.link "link1" 250L 150L 200L 100L "https://darklang.com") let stats = Stachu.JsonCanvas.countNodesByType canvas - let responseText = $"Canvas Statistics:\n{stats}\n\nTotal nodes: {Builtin.int64ToString (Stdlib.List.length canvas.nodes)}" Stdlib.Http.response (Stdlib.String.toBytes responseText) 200L @@ -456,100 +341,7 @@ let _handler _req = // Demonstrate building a mind map structure [] let _handler _req = - let canvas = Stachu.JsonCanvas.empty () - - // Central idea - let centerNode = - Stachu.JsonCanvas.Node.TextNode( - Stachu.JsonCanvas.TextNodeData - { id = "center" - x = 400L - y = 300L - width = 200L - height = 80L - text = "Darklang" - color = Stdlib.Option.Option.None } - ) - - // Branch ideas - let branch1 = - Stachu.JsonCanvas.Node.TextNode( - Stachu.JsonCanvas.TextNodeData - { id = "branch1" - x = 100L - y = 200L - width = 150L - height = 60L - text = "Type Safety" - color = Stdlib.Option.Option.None } - ) - - let branch2 = - Stachu.JsonCanvas.Node.TextNode( - Stachu.JsonCanvas.TextNodeData - { id = "branch2" - x = 100L - y = 400L - width = 150L - height = 60L - text = "Packages" - color = Stdlib.Option.Option.None } - ) - - let branch3 = - Stachu.JsonCanvas.Node.TextNode( - Stachu.JsonCanvas.TextNodeData - { id = "branch3" - x = 700L - y = 200L - width = 150L - height = 60L - text = "HTTP Handlers" - color = Stdlib.Option.Option.None } - ) - - let branch4 = - Stachu.JsonCanvas.Node.TextNode( - Stachu.JsonCanvas.TextNodeData - { id = "branch4" - x = 700L - y = 400L - width = 150L - height = 60L - text = "Cloud Native" - color = Stdlib.Option.Option.None } - ) - - // Connect branches to center - let edge1 = - (Stachu.JsonCanvas.Edge.simple "e1" "branch1" "center") - |> Stachu.JsonCanvas.Edge.withLabel "enables" - - let edge2 = - (Stachu.JsonCanvas.Edge.simple "e2" "branch2" "center") - |> Stachu.JsonCanvas.Edge.withLabel "extend" - - let edge3 = - (Stachu.JsonCanvas.Edge.simple "e3" "center" "branch3") - |> Stachu.JsonCanvas.Edge.withLabel "supports" - - let edge4 = - (Stachu.JsonCanvas.Edge.simple "e4" "center" "branch4") - |> Stachu.JsonCanvas.Edge.withLabel "runs in" - - // Build the mindmap - let mindmap = - canvas - |> Stachu.JsonCanvas.addNode centerNode - |> Stachu.JsonCanvas.addNode branch1 - |> Stachu.JsonCanvas.addNode branch2 - |> Stachu.JsonCanvas.addNode branch3 - |> Stachu.JsonCanvas.addNode branch4 - |> Stachu.JsonCanvas.addEdge edge1 - |> Stachu.JsonCanvas.addEdge edge2 - |> Stachu.JsonCanvas.addEdge edge3 - |> Stachu.JsonCanvas.addEdge edge4 - + let mindmap = Stachu.JsonCanvas.Examples.mindmap () let nodeCount = Stdlib.List.length mindmap.nodes let edgeCount = Stdlib.List.length mindmap.edges @@ -562,93 +354,7 @@ let _handler _req = // Demonstrate workflow/flowchart structure [] let _handler _req = - let canvas = Stachu.JsonCanvas.empty () - - // Workflow steps - let step1 = - Stachu.JsonCanvas.Node.TextNode( - Stachu.JsonCanvas.TextNodeData - { id = "step1" - x = 50L - y = 50L - width = 180L - height = 80L - text = "Receive Request" - color = Stdlib.Option.Option.None } - ) - - let step2 = - Stachu.JsonCanvas.Node.TextNode( - Stachu.JsonCanvas.TextNodeData - { id = "step2" - x = 50L - y = 180L - width = 180L - height = 80L - text = "Parse JSON" - color = Stdlib.Option.Option.None } - ) - - let step3 = - Stachu.JsonCanvas.Node.TextNode( - Stachu.JsonCanvas.TextNodeData - { id = "step3" - x = 50L - y = 310L - width = 180L - height = 80L - text = "Validate Data" - color = Stdlib.Option.Option.None } - ) - - let step4 = - Stachu.JsonCanvas.Node.TextNode( - Stachu.JsonCanvas.TextNodeData - { id = "step4" - x = 50L - y = 440L - width = 180L - height = 80L - text = "Process & Respond" - color = Stdlib.Option.Option.None } - ) - - let errorNode = - Stachu.JsonCanvas.Node.TextNode( - Stachu.JsonCanvas.TextNodeData - { id = "error" - x = 300L - y = 250L - width = 180L - height = 80L - text = "Return Error" - color = Stdlib.Option.Option.None } - ) - - // Connect the workflow - let flow1 = Stachu.JsonCanvas.Edge.simple "f1" "step1" "step2" - let flow2 = Stachu.JsonCanvas.Edge.simple "f2" "step2" "step3" - - let flow3 = - (Stachu.JsonCanvas.Edge.simple "f3" "step3" "step4") - |> Stachu.JsonCanvas.Edge.withLabel "valid" - - let flow4 = - (Stachu.JsonCanvas.Edge.simple "f4" "step3" "error") - |> Stachu.JsonCanvas.Edge.withLabel "invalid" - - // Build the workflow - let workflow = - canvas - |> Stachu.JsonCanvas.addNode step1 - |> Stachu.JsonCanvas.addNode step2 - |> Stachu.JsonCanvas.addNode step3 - |> Stachu.JsonCanvas.addNode step4 - |> Stachu.JsonCanvas.addNode errorNode - |> Stachu.JsonCanvas.addEdge flow1 - |> Stachu.JsonCanvas.addEdge flow2 - |> Stachu.JsonCanvas.addEdge flow3 - |> Stachu.JsonCanvas.addEdge flow4 + let workflow = Stachu.JsonCanvas.Examples.workflow () let responseText = $"Workflow Canvas Created!\n\nThis demonstrates a simple HTTP request processing workflow.\n\nNodes: {Builtin.int64ToString (Stdlib.List.length workflow.nodes)}\nEdges: {Builtin.int64ToString (Stdlib.List.length workflow.edges)}" @@ -659,73 +365,7 @@ let _handler _req = // JSON API Endpoints [] let _handler _req = - let canvas = Stachu.JsonCanvas.empty () - - let node4 = - Stachu.JsonCanvas.Node.GroupNode( - Stachu.JsonCanvas.GroupNodeData - { id = "group1" - x = 0L - y = 0L - width = 600L - height = 400L - label = Stdlib.Option.Option.Some("Main Group") - background = Stdlib.Option.Option.None - backgroundStyle = Stdlib.Option.Option.None - color = Stdlib.Option.Option.None } - ) - - let node1 = - Stachu.JsonCanvas.Node.TextNode( - Stachu.JsonCanvas.TextNodeData - { id = "node1" - x = 20L - y = 50L - width = 250L - height = 100L - text = "Hello from Darklang!" - color = Stdlib.Option.Option.None } - ) - - let node2 = - Stachu.JsonCanvas.Node.TextNode( - Stachu.JsonCanvas.TextNodeData - { id = "node2" - x = 320L - y = 50L - width = 250L - height = 100L - text = "This is a JSON Canvas" - color = Stdlib.Option.Option.None } - ) - - let node3 = - Stachu.JsonCanvas.Node.LinkNode( - Stachu.JsonCanvas.LinkNodeData - { id = "node3" - x = 150L - y = 220L - width = 300L - height = 80L - url = "https://jsoncanvas.org" - color = Stdlib.Option.Option.None } - ) - - let edge1 = Stachu.JsonCanvas.Edge.simple "edge1" "node1" "node2" - - let edge2 = - (Stachu.JsonCanvas.Edge.simple "edge2" "node2" "node3") - |> Stachu.JsonCanvas.Edge.withLabel "learn more" - - let canvas = - canvas - |> Stachu.JsonCanvas.addNode node4 - |> Stachu.JsonCanvas.addNode node1 - |> Stachu.JsonCanvas.addNode node2 - |> Stachu.JsonCanvas.addNode node3 - |> Stachu.JsonCanvas.addEdge edge1 - |> Stachu.JsonCanvas.addEdge edge2 - + let canvas = Stachu.JsonCanvas.Examples.basic () let json = Stachu.JsonCanvas.Json.canvasToJson canvas Stdlib.Http.response (Stdlib.String.toBytes json) 200L @@ -733,96 +373,7 @@ let _handler _req = [] let _handler _req = - let canvas = Stachu.JsonCanvas.empty () - - let centerNode = - Stachu.JsonCanvas.Node.TextNode( - Stachu.JsonCanvas.TextNodeData - { id = "center" - x = 400L - y = 300L - width = 200L - height = 80L - text = "Darklang" - color = Stdlib.Option.Option.None } - ) - - let branch1 = - Stachu.JsonCanvas.Node.TextNode( - Stachu.JsonCanvas.TextNodeData - { id = "branch1" - x = 100L - y = 200L - width = 150L - height = 60L - text = "Type Safety" - color = Stdlib.Option.Option.None } - ) - - let branch2 = - Stachu.JsonCanvas.Node.TextNode( - Stachu.JsonCanvas.TextNodeData - { id = "branch2" - x = 100L - y = 400L - width = 150L - height = 60L - text = "Packages" - color = Stdlib.Option.Option.None } - ) - - let branch3 = - Stachu.JsonCanvas.Node.TextNode( - Stachu.JsonCanvas.TextNodeData - { id = "branch3" - x = 700L - y = 200L - width = 150L - height = 60L - text = "HTTP Handlers" - color = Stdlib.Option.Option.None } - ) - - let branch4 = - Stachu.JsonCanvas.Node.TextNode( - Stachu.JsonCanvas.TextNodeData - { id = "branch4" - x = 700L - y = 400L - width = 150L - height = 60L - text = "Cloud Native" - color = Stdlib.Option.Option.None } - ) - - let edge1 = - (Stachu.JsonCanvas.Edge.simple "e1" "branch1" "center") - |> Stachu.JsonCanvas.Edge.withLabel "enables" - - let edge2 = - (Stachu.JsonCanvas.Edge.simple "e2" "branch2" "center") - |> Stachu.JsonCanvas.Edge.withLabel "extend" - - let edge3 = - (Stachu.JsonCanvas.Edge.simple "e3" "center" "branch3") - |> Stachu.JsonCanvas.Edge.withLabel "supports" - - let edge4 = - (Stachu.JsonCanvas.Edge.simple "e4" "center" "branch4") - |> Stachu.JsonCanvas.Edge.withLabel "runs in" - - let mindmap = - canvas - |> Stachu.JsonCanvas.addNode centerNode - |> Stachu.JsonCanvas.addNode branch1 - |> Stachu.JsonCanvas.addNode branch2 - |> Stachu.JsonCanvas.addNode branch3 - |> Stachu.JsonCanvas.addNode branch4 - |> Stachu.JsonCanvas.addEdge edge1 - |> Stachu.JsonCanvas.addEdge edge2 - |> Stachu.JsonCanvas.addEdge edge3 - |> Stachu.JsonCanvas.addEdge edge4 - + let mindmap = Stachu.JsonCanvas.Examples.mindmap () let json = Stachu.JsonCanvas.Json.canvasToJson mindmap Stdlib.Http.response (Stdlib.String.toBytes json) 200L @@ -830,91 +381,7 @@ let _handler _req = [] let _handler _req = - let canvas = Stachu.JsonCanvas.empty () - - let step1 = - Stachu.JsonCanvas.Node.TextNode( - Stachu.JsonCanvas.TextNodeData - { id = "step1" - x = 50L - y = 50L - width = 180L - height = 80L - text = "Receive Request" - color = Stdlib.Option.Option.None } - ) - - let step2 = - Stachu.JsonCanvas.Node.TextNode( - Stachu.JsonCanvas.TextNodeData - { id = "step2" - x = 50L - y = 180L - width = 180L - height = 80L - text = "Parse JSON" - color = Stdlib.Option.Option.None } - ) - - let step3 = - Stachu.JsonCanvas.Node.TextNode( - Stachu.JsonCanvas.TextNodeData - { id = "step3" - x = 50L - y = 310L - width = 180L - height = 80L - text = "Validate Data" - color = Stdlib.Option.Option.None } - ) - - let step4 = - Stachu.JsonCanvas.Node.TextNode( - Stachu.JsonCanvas.TextNodeData - { id = "step4" - x = 50L - y = 440L - width = 180L - height = 80L - text = "Process & Respond" - color = Stdlib.Option.Option.None } - ) - - let errorNode = - Stachu.JsonCanvas.Node.TextNode( - Stachu.JsonCanvas.TextNodeData - { id = "error" - x = 300L - y = 250L - width = 180L - height = 80L - text = "Return Error" - color = Stdlib.Option.Option.None } - ) - - let flow1 = Stachu.JsonCanvas.Edge.simple "f1" "step1" "step2" - let flow2 = Stachu.JsonCanvas.Edge.simple "f2" "step2" "step3" - - let flow3 = - (Stachu.JsonCanvas.Edge.simple "f3" "step3" "step4") - |> Stachu.JsonCanvas.Edge.withLabel "valid" - - let flow4 = - (Stachu.JsonCanvas.Edge.simple "f4" "step3" "error") - |> Stachu.JsonCanvas.Edge.withLabel "invalid" - - let workflow = - canvas - |> Stachu.JsonCanvas.addNode step1 - |> Stachu.JsonCanvas.addNode step2 - |> Stachu.JsonCanvas.addNode step3 - |> Stachu.JsonCanvas.addNode step4 - |> Stachu.JsonCanvas.addNode errorNode - |> Stachu.JsonCanvas.addEdge flow1 - |> Stachu.JsonCanvas.addEdge flow2 - |> Stachu.JsonCanvas.addEdge flow3 - |> Stachu.JsonCanvas.addEdge flow4 - + let workflow = Stachu.JsonCanvas.Examples.workflow () let json = Stachu.JsonCanvas.Json.canvasToJson workflow Stdlib.Http.response (Stdlib.String.toBytes json) 200L diff --git a/packages/stachu/jsoncanvas.dark b/packages/stachu/jsoncanvas.dark index beedd776ea..77ae690e3e 100644 --- a/packages/stachu/jsoncanvas.dark +++ b/packages/stachu/jsoncanvas.dark @@ -1,274 +1,346 @@ // JSON Canvas type definitions // Spec: https://jsoncanvas.org/spec/1.0/ -module Stachu = - module JsonCanvas = - // Color can be either a preset number (1-6) or a hex string - type Color = - | Preset of Int64 - | Hex of String - - // Side of a node where an edge connects - type Side = - | Top - | Right - | Bottom - | Left - - // Endpoint shape for edges - type EndpointStyle = - | None - | Arrow - - // Background style for group nodes - type BackgroundStyle = - | Cover - | Ratio - | Repeat - - // Record types for each node variant - type TextNodeData = - { id: String - x: Int64 - y: Int64 - width: Int64 - height: Int64 - text: String - color: Stdlib.Option.Option } - - type FileNodeData = - { id: String - x: Int64 - y: Int64 - width: Int64 - height: Int64 - file: String - subpath: Stdlib.Option.Option - color: Stdlib.Option.Option } - - type LinkNodeData = - { id: String - x: Int64 - y: Int64 - width: Int64 - height: Int64 - url: String - color: Stdlib.Option.Option } - - type GroupNodeData = - { id: String - x: Int64 - y: Int64 - width: Int64 - height: Int64 - label: Stdlib.Option.Option - background: Stdlib.Option.Option - backgroundStyle: Stdlib.Option.Option - color: Stdlib.Option.Option } - - // Node types using record types - type Node = - | TextNode of TextNodeData - | FileNode of FileNodeData - | LinkNode of LinkNodeData - | GroupNode of GroupNodeData - - // Edge connecting nodes - type Edge = - { id: String - fromNode: String - toNode: String - fromSide: Stdlib.Option.Option - toSide: Stdlib.Option.Option - fromEnd: Stdlib.Option.Option - toEnd: Stdlib.Option.Option - color: Stdlib.Option.Option - label: Stdlib.Option.Option } - - // Canvas is the top-level structure - type Canvas = - { nodes: List - edges: List } - - - // Helper functions for creating edges - module Edge = - let simple (id: String) (fromNode: String) (toNode: String) : Edge = - Edge - { id = id - fromNode = fromNode - toNode = toNode - fromSide = Stdlib.Option.Option.None - toSide = Stdlib.Option.Option.None - fromEnd = Stdlib.Option.Option.None - toEnd = Stdlib.Option.Option.None - color = Stdlib.Option.Option.None - label = Stdlib.Option.Option.None } - - let withLabel - (edge: Edge) - (label: String) - : Edge = - Edge - { id = edge.id - fromNode = edge.fromNode - toNode = edge.toNode - fromSide = edge.fromSide - toSide = edge.toSide - fromEnd = edge.fromEnd - toEnd = edge.toEnd - color = edge.color - label = Stdlib.Option.Option.Some(label) } - - - // Create an empty canvas - let empty () : Canvas = Canvas { nodes = []; edges = [] } - - // Add a node to a canvas - let addNode (canvas: Canvas) (node: Node) : Canvas = - Canvas - { nodes = Stdlib.List.append canvas.nodes [ node ] - edges = canvas.edges } - - // Add an edge to a canvas - let addEdge (canvas: Canvas) (edge: Edge) : Canvas = - Canvas - { nodes = canvas.nodes - edges = Stdlib.List.append canvas.edges [ edge ] } - - // Get all node IDs - let getNodeIds (canvas: Canvas) : List = +module Stachu.JsonCanvas + +// Color can be either a preset number (1-6) or a hex string +type Color = + | Preset of Int64 + | Hex of String + +// Side of a node where an edge connects +type Side = + | Top + | Right + | Bottom + | Left + +// Endpoint shape for edges +type EndpointStyle = + | None + | Arrow + +// Background style for group nodes +type BackgroundStyle = + | Cover + | Ratio + | Repeat + + +// Record types for each node variant +type TextNodeData = + { id: String + x: Int64 + y: Int64 + width: Int64 + height: Int64 + text: String + color: Stdlib.Option.Option } + +type FileNodeData = + { id: String + x: Int64 + y: Int64 + width: Int64 + height: Int64 + file: String + subpath: Stdlib.Option.Option + color: Stdlib.Option.Option } + +type LinkNodeData = + { id: String + x: Int64 + y: Int64 + width: Int64 + height: Int64 + url: String + color: Stdlib.Option.Option } + +type GroupNodeData = + { id: String + x: Int64 + y: Int64 + width: Int64 + height: Int64 + label: Stdlib.Option.Option + background: Stdlib.Option.Option + backgroundStyle: Stdlib.Option.Option + color: Stdlib.Option.Option } + +// Node types using record types +type Node = + | TextNode of TextNodeData + | FileNode of FileNodeData + | LinkNode of LinkNodeData + | GroupNode of GroupNodeData + +// Edge connecting nodes +type Edge = + { id: String + fromNode: String + toNode: String + fromSide: Stdlib.Option.Option + toSide: Stdlib.Option.Option + fromEnd: Stdlib.Option.Option + toEnd: Stdlib.Option.Option + color: Stdlib.Option.Option + label: Stdlib.Option.Option } + +// Canvas is the top-level structure +type Canvas = + { nodes: List + edges: List } + + +// Helper functions for creating nodes +module Node = + let text (id: String) (x: Int64) (y: Int64) (width: Int64) (height: Int64) (text: String) : Node = + (TextNodeData + { id = id + x = x + y = y + width = width + height = height + text = text + color = Stdlib.Option.Option.None }) + |> Node.TextNode + + let link (id: String) (x: Int64) (y: Int64) (width: Int64) (height: Int64) (url: String) : Node = + (LinkNodeData + { id = id + x = x + y = y + width = width + height = height + url = url + color = Stdlib.Option.Option.None }) + |> Node.LinkNode + + let file (id: String) (x: Int64) (y: Int64) (width: Int64) (height: Int64) (filePath: String) : Node = + (FileNodeData + { id = id + x = x + y = y + width = width + height = height + file = filePath + subpath = Stdlib.Option.Option.None + color = Stdlib.Option.Option.None } + ) |> Node.FileNode + + let group (id: String) (x: Int64) (y: Int64) (width: Int64) (height: Int64) (label: String) : Node = + (GroupNodeData + { id = id + x = x + y = y + width = width + height = height + label = Stdlib.Option.Option.Some(label) + background = Stdlib.Option.Option.None + backgroundStyle = Stdlib.Option.Option.None + color = Stdlib.Option.Option.None }) + |> Node.GroupNode + + +// Helper functions for creating edges +module Edge = + let simple (id: String) (fromNode: String) (toNode: String) : Edge = + Edge + { id = id + fromNode = fromNode + toNode = toNode + fromSide = Stdlib.Option.Option.None + toSide = Stdlib.Option.Option.None + fromEnd = Stdlib.Option.Option.None + toEnd = Stdlib.Option.Option.None + color = Stdlib.Option.Option.None + label = Stdlib.Option.Option.None } + + let withLabel (label: String) (edge: Edge) : Edge = + { edge with label = Stdlib.Option.Option.Some(label) } + + +// Create an empty canvas +let empty () : Canvas = + Canvas { nodes = []; edges = [] } + +// Add a node to a canvas +let addNode (canvas: Canvas) (node: Node) : Canvas = + Canvas + { nodes = Stdlib.List.append canvas.nodes [ node ] + edges = canvas.edges } + +// Add an edge to a canvas +let addEdge (canvas: Canvas) (edge: Edge) : Canvas = + Canvas + { nodes = canvas.nodes + edges = Stdlib.List.append canvas.edges [ edge ] } + +// Get all node IDs +let getNodeIds (canvas: Canvas) : List = + canvas.nodes + |> Stdlib.List.map (fun node -> + match node with + | TextNode data -> data.id + | FileNode data -> data.id + | LinkNode data -> data.id + | GroupNode data -> data.id) + +// Count nodes by type +let countNodesByType (canvas: Canvas) : String = + let textCount = + canvas.nodes + |> Stdlib.List.filter (fun node -> + match node with + | TextNode _ -> true + | _ -> false) + |> Stdlib.List.length + + let fileCount = + canvas.nodes + |> Stdlib.List.filter (fun node -> + match node with + | FileNode _ -> true + | _ -> false) + |> Stdlib.List.length + + let linkCount = + canvas.nodes + |> Stdlib.List.filter (fun node -> + match node with + | LinkNode _ -> true + | _ -> false) + |> Stdlib.List.length + + let groupCount = + canvas.nodes + |> Stdlib.List.filter (fun node -> + match node with + | GroupNode _ -> true + | _ -> false) + |> Stdlib.List.length + + $"Text: {Builtin.int64ToString textCount}, File: {Builtin.int64ToString fileCount}, Link: {Builtin.int64ToString linkCount}, Group: {Builtin.int64ToString groupCount}" + + +// Demo/Example Canvas Builders +module Examples = + let basic () : Canvas = + (empty ()) + |> addNode (Node.group "group1" 0L 0L 600L 400L "Main Group") + |> addNode (Node.text "node1" 20L 50L 250L 100L "Hello from Darklang!") + |> addNode (Node.text "node2" 320L 50L 250L 100L "This is a JSON Canvas") + |> addNode (Node.link "node3" 150L 220L 300L 80L "https://jsoncanvas.org") + |> addEdge (Edge.simple "edge1" "node1" "node2") + |> addEdge ((Edge.simple "edge2" "node2" "node3") |> Edge.withLabel "learn more") + + let mindmap () : Canvas = + (empty ()) + |> addNode (Node.text "center" 400L 300L 200L 80L "Darklang") + |> addNode (Node.text "branch1" 100L 200L 150L 60L "Type Safety") + |> addNode (Node.text "branch2" 100L 400L 150L 60L "Packages") + |> addNode (Node.text "branch3" 700L 200L 150L 60L "HTTP Handlers") + |> addNode (Node.text "branch4" 700L 400L 150L 60L "Cloud Native") + |> addEdge ((Edge.simple "e1" "branch1" "center") |> Edge.withLabel "enables") + |> addEdge ((Edge.simple "e2" "branch2" "center") |> Edge.withLabel "extend") + |> addEdge ((Edge.simple "e3" "center" "branch3") |> Edge.withLabel "supports") + |> addEdge ((Edge.simple "e4" "center" "branch4") |> Edge.withLabel "runs in") + + let workflow () : Canvas = + (empty ()) + |> addNode (Node.text "step1" 50L 50L 180L 80L "Receive Request") + |> addNode (Node.text "step2" 50L 180L 180L 80L "Parse JSON") + |> addNode (Node.text "step3" 50L 310L 180L 80L "Validate Data") + |> addNode (Node.text "step4" 50L 440L 180L 80L "Process & Respond") + |> addNode (Node.text "error" 300L 250L 180L 80L "Return Error") + |> addEdge (Edge.simple "f1" "step1" "step2") + |> addEdge (Edge.simple "f2" "step2" "step3") + |> addEdge ((Edge.simple "f3" "step3" "step4") |> Edge.withLabel "valid") + |> addEdge ((Edge.simple "f4" "step3" "error") |> Edge.withLabel "invalid") + + +// JSON Serialization +module Json = + let nodeToJson (node: Node) : Stdlib.AltJson.Json = + match node with + | TextNode data -> + [ ("id", Stdlib.AltJson.Json.String(data.id)) + ("type", Stdlib.AltJson.Json.String("text")) + ("x", Stdlib.AltJson.Json.Number(Builtin.int64ToFloat data.x)) + ("y", Stdlib.AltJson.Json.Number(Builtin.int64ToFloat data.y)) + ("width", Stdlib.AltJson.Json.Number(Builtin.int64ToFloat data.width)) + ("height", Stdlib.AltJson.Json.Number(Builtin.int64ToFloat data.height)) + ("text", Stdlib.AltJson.Json.String(data.text)) ] + |> Stdlib.AltJson.Json.Object + + | LinkNode data -> + [ ("id", Stdlib.AltJson.Json.String(data.id)) + ("type", Stdlib.AltJson.Json.String("link")) + ("x", Stdlib.AltJson.Json.Number(Builtin.int64ToFloat data.x)) + ("y", Stdlib.AltJson.Json.Number(Builtin.int64ToFloat data.y)) + ("width", Stdlib.AltJson.Json.Number(Builtin.int64ToFloat data.width)) + ("height", Stdlib.AltJson.Json.Number(Builtin.int64ToFloat data.height)) + ("url", Stdlib.AltJson.Json.String(data.url)) ] + |> Stdlib.AltJson.Json.Object + + | FileNode data -> + [ ("id", Stdlib.AltJson.Json.String(data.id)) + ("type", Stdlib.AltJson.Json.String("file")) + ("x", Stdlib.AltJson.Json.Number(Builtin.int64ToFloat data.x)) + ("y", Stdlib.AltJson.Json.Number(Builtin.int64ToFloat data.y)) + ("width", Stdlib.AltJson.Json.Number(Builtin.int64ToFloat data.width)) + ("height", Stdlib.AltJson.Json.Number(Builtin.int64ToFloat data.height)) + ("file", Stdlib.AltJson.Json.String(data.file)) ] + |> Stdlib.AltJson.Json.Object + + | GroupNode data -> + let fields = + [ ("id", Stdlib.AltJson.Json.String(data.id)) + ("type", Stdlib.AltJson.Json.String("group")) + ("x", Stdlib.AltJson.Json.Number(Builtin.int64ToFloat data.x)) + ("y", Stdlib.AltJson.Json.Number(Builtin.int64ToFloat data.y)) + ("width", Stdlib.AltJson.Json.Number(Builtin.int64ToFloat data.width)) + ("height", Stdlib.AltJson.Json.Number(Builtin.int64ToFloat data.height)) ] + + let fields = + match data.label with + | Some label -> + Stdlib.List.append + fields + [ ("label", Stdlib.AltJson.Json.String(label)) ] + | None -> fields + + Stdlib.AltJson.Json.Object(fields) + + let edgeToJson (edge: Edge) : Stdlib.AltJson.Json = + let fields = + [ ("id", Stdlib.AltJson.Json.String(edge.id)) + ("fromNode", Stdlib.AltJson.Json.String(edge.fromNode)) + ("toNode", Stdlib.AltJson.Json.String(edge.toNode)) ] + + let fields = + match edge.label with + | Some label -> + Stdlib.List.append + fields + [ ("label", Stdlib.AltJson.Json.String(label)) ] + | None -> fields + + Stdlib.AltJson.Json.Object(fields) + + let canvasToJson (canvas: Canvas) : String = + let nodesJson = canvas.nodes - |> Stdlib.List.map (fun node -> - match node with - | TextNode data -> data.id - | FileNode data -> data.id - | LinkNode data -> data.id - | GroupNode data -> data.id) - - // Count nodes by type - let countNodesByType (canvas: Canvas) : String = - let textCount = - canvas.nodes - |> Stdlib.List.filter (fun node -> - match node with - | TextNode _ -> true - | _ -> false) - |> Stdlib.List.length - - let fileCount = - canvas.nodes - |> Stdlib.List.filter (fun node -> - match node with - | FileNode _ -> true - | _ -> false) - |> Stdlib.List.length - - let linkCount = - canvas.nodes - |> Stdlib.List.filter (fun node -> - match node with - | LinkNode _ -> true - | _ -> false) - |> Stdlib.List.length - - let groupCount = - canvas.nodes - |> Stdlib.List.filter (fun node -> - match node with - | GroupNode _ -> true - | _ -> false) - |> Stdlib.List.length - - $"Text: {Builtin.int64ToString textCount}, File: {Builtin.int64ToString fileCount}, Link: {Builtin.int64ToString linkCount}, Group: {Builtin.int64ToString groupCount}" - - - // JSON Serialization - module Json = - let nodeToJson (node: Node) : Darklang.Stdlib.AltJson.Json = - match node with - | TextNode data -> - Darklang.Stdlib.AltJson.Json.Object( - [ ("id", Darklang.Stdlib.AltJson.Json.String(data.id)) - ("type", Darklang.Stdlib.AltJson.Json.String("text")) - ("x", Darklang.Stdlib.AltJson.Json.Number(Builtin.int64ToFloat data.x)) - ("y", Darklang.Stdlib.AltJson.Json.Number(Builtin.int64ToFloat data.y)) - ("width", Darklang.Stdlib.AltJson.Json.Number(Builtin.int64ToFloat data.width)) - ("height", Darklang.Stdlib.AltJson.Json.Number(Builtin.int64ToFloat data.height)) - ("text", Darklang.Stdlib.AltJson.Json.String(data.text)) ] - ) - - | LinkNode data -> - Darklang.Stdlib.AltJson.Json.Object( - [ ("id", Darklang.Stdlib.AltJson.Json.String(data.id)) - ("type", Darklang.Stdlib.AltJson.Json.String("link")) - ("x", Darklang.Stdlib.AltJson.Json.Number(Builtin.int64ToFloat data.x)) - ("y", Darklang.Stdlib.AltJson.Json.Number(Builtin.int64ToFloat data.y)) - ("width", Darklang.Stdlib.AltJson.Json.Number(Builtin.int64ToFloat data.width)) - ("height", Darklang.Stdlib.AltJson.Json.Number(Builtin.int64ToFloat data.height)) - ("url", Darklang.Stdlib.AltJson.Json.String(data.url)) ] - ) - - | FileNode data -> - Darklang.Stdlib.AltJson.Json.Object( - [ ("id", Darklang.Stdlib.AltJson.Json.String(data.id)) - ("type", Darklang.Stdlib.AltJson.Json.String("file")) - ("x", Darklang.Stdlib.AltJson.Json.Number(Builtin.int64ToFloat data.x)) - ("y", Darklang.Stdlib.AltJson.Json.Number(Builtin.int64ToFloat data.y)) - ("width", Darklang.Stdlib.AltJson.Json.Number(Builtin.int64ToFloat data.width)) - ("height", Darklang.Stdlib.AltJson.Json.Number(Builtin.int64ToFloat data.height)) - ("file", Darklang.Stdlib.AltJson.Json.String(data.file)) ] - ) - - | GroupNode data -> - let fields = - [ ("id", Darklang.Stdlib.AltJson.Json.String(data.id)) - ("type", Darklang.Stdlib.AltJson.Json.String("group")) - ("x", Darklang.Stdlib.AltJson.Json.Number(Builtin.int64ToFloat data.x)) - ("y", Darklang.Stdlib.AltJson.Json.Number(Builtin.int64ToFloat data.y)) - ("width", Darklang.Stdlib.AltJson.Json.Number(Builtin.int64ToFloat data.width)) - ("height", Darklang.Stdlib.AltJson.Json.Number(Builtin.int64ToFloat data.height)) ] - - let fieldsWithLabel = - match data.label with - | Some label -> - Stdlib.List.append - fields - [ ("label", Darklang.Stdlib.AltJson.Json.String(label)) ] - | None -> fields - - Darklang.Stdlib.AltJson.Json.Object(fieldsWithLabel) - - let edgeToJson (edge: Edge) : Darklang.Stdlib.AltJson.Json = - let fields = - [ ("id", Darklang.Stdlib.AltJson.Json.String(edge.id)) - ("fromNode", Darklang.Stdlib.AltJson.Json.String(edge.fromNode)) - ("toNode", Darklang.Stdlib.AltJson.Json.String(edge.toNode)) ] - - let fieldsWithLabel = - match edge.label with - | Some label -> - Stdlib.List.append - fields - [ ("label", Darklang.Stdlib.AltJson.Json.String(label)) ] - | None -> fields - - Darklang.Stdlib.AltJson.Json.Object(fieldsWithLabel) - - let canvasToJson (canvas: Canvas) : String = - let nodesJson = - canvas.nodes - |> Stdlib.List.map (fun node -> nodeToJson node) - |> Darklang.Stdlib.AltJson.Json.Array - - let edgesJson = - canvas.edges - |> Stdlib.List.map (fun edge -> edgeToJson edge) - |> Darklang.Stdlib.AltJson.Json.Array - - let canvasJson = - Darklang.Stdlib.AltJson.Json.Object( - [ ("nodes", nodesJson); ("edges", edgesJson) ] - ) - - Darklang.Stdlib.AltJson.format canvasJson + |> Stdlib.List.map (fun node -> nodeToJson node) + |> Stdlib.AltJson.Json.Array + + let edgesJson = + canvas.edges + |> Stdlib.List.map (fun edge -> edgeToJson edge) + |> Stdlib.AltJson.Json.Array + + let canvasJson = + [ ("nodes", nodesJson); ("edges", edgesJson) ] + |> Stdlib.AltJson.Json.Object + + Stdlib.AltJson.format canvasJson