diff --git a/.devcontainer/constructs/devcontainer.json b/.devcontainer/constructs/devcontainer.json new file mode 100644 index 00000000..c585f907 --- /dev/null +++ b/.devcontainer/constructs/devcontainer.json @@ -0,0 +1,27 @@ +{ + "name": "Constructs", + "image": "mcr.microsoft.com/dotnet/sdk:9.0", + "features": {}, + "workspaceFolder": "/workspaces/Ivy-Examples/constructs", + "postCreateCommand": "dotnet tool install -g Ivy.Console", + "postStartCommand": "dotnet watch", + "forwardPorts": [5010], + "portsAttributes": { + "5010": { + "label": "Constructs", + "onAutoForward": "openPreview" + } + }, + "customizations": { + "vscode": { + "extensions": [ + "ms-dotnettools.csharp", + "ms-vscode.powershell", + "ms-dotnettools.csdevkit" + ] + }, + "codespaces": { + "openFiles": ["Apps/ConstructsApp.cs"] + } + } + } \ No newline at end of file diff --git a/constructs/.nvmrc b/constructs/.nvmrc deleted file mode 100644 index 2edeafb0..00000000 --- a/constructs/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -20 \ No newline at end of file diff --git a/constructs/Apps/ConstructsApp.cs b/constructs/Apps/ConstructsApp.cs index d63c0481..a49375e1 100644 --- a/constructs/Apps/ConstructsApp.cs +++ b/constructs/Apps/ConstructsApp.cs @@ -1,10 +1,10 @@ using Constructs; -using Ivy.Examples.Constructs.Domain; -using Ivy.Examples.Constructs.Extensions; +using ConstructsExample.Domain; +using ConstructsExample.Extensions; -namespace Ivy.Examples.Constructs.Apps; +namespace ConstructsExample.Apps; -[App(icon: Icons.PartyPopper, title: "Constructs (AWS)")] +[App(icon: Icons.FileCode2, title: "Constructs")] public class ConstructsApp : ViewBase { private RootConstruct? _root; @@ -16,13 +16,13 @@ public class ConstructsApp : ViewBase var parent = UseState(string.Empty); var childId = UseState(string.Empty); var maxLines = UseState(MaxLines); + var client = UseService(); if (_root is null) { try { _root = DemoConstruct.BuildRoot(); - parent.Set(_root.Node.Path); } catch (Exception ex) { @@ -47,8 +47,7 @@ public class ConstructsApp : ViewBase LayoutView left = Layout.Vertical().Gap(6) | Text.H2("AWS Constructs — interactive demo") | Text.Block("Write a parent path, add a child construct, or reset to the canonical tree.") - | Text.Block("Parent path, ex. Root/Demo/Nested. Start typing to filter tree view.") - | parent.ToInput(placeholder: "Root") + | parent.ToInput(placeholder: "Parent path, ex. Root/Demo/Nested. Start typing to filter tree view.") | Text.Block("New child id") | childId.ToInput(placeholder: "ChildX") | ( @@ -62,35 +61,37 @@ public class ConstructsApp : ViewBase string newChildId = childId.Value.Trim(); if (string.IsNullOrWhiteSpace(newChildId)) { - status.Set("Enter a non-empty child id."); + client.Toast("Enter a non-empty child id."); return; } Construct? parentNode = _root.FindByPath(parentPath); if (parentNode == null) { - status.Set($"Parent path not found: {parentPath}"); + client.Toast($"Parent path not found: {parentPath}"); return; } _ = new Construct(parentNode, newChildId); childId.Set(string.Empty); - status.Set($"Added: {parentNode.Node.Path}/{newChildId}"); - }) - | new Button("Reset", onClick: () => + client.Toast($"Added: {parentNode.Node.Path}/{newChildId}"); + }).Icon(Icons.Plus) + | new Button("Reset", _ => { _root = DemoConstruct.BuildRoot(); - parent.Set(_root.Node.Path); - status.Set("Tree reset."); + parent.Set(string.Empty); + client.Toast("Tree reset."); maxLines.Set(MaxLines); - }) - ) - | (string.IsNullOrWhiteSpace(status.Value) ? Text.Block(string.Empty) : Text.Block(status.Value)); + }).Icon(Icons.RefreshCw) + ) + | Text.Small("This demo uses the AWS Constructs library to build composable configuration models.") + | Text.Markdown("Built with [Ivy Framework](https://github.com/Ivy-Interactive/Ivy-Framework) and [AWS Constructs](https://github.com/aws/constructs)") + ; // Build right (tree view) panel Card right = new Card( - Layout.Vertical().Gap(6).Padding(3) - | Text.Block("Current tree (subtree of the selected parent)") + Layout.Vertical().Gap(3).Padding(3) + | Text.H4("Current tree") | new Separator() | Text.Markdown("```text\n" + string.Join('\n', visible) + "\n```") | (hasMore @@ -99,14 +100,13 @@ public class ConstructsApp : ViewBase ? new Button("Collapse", onClick: () => maxLines.Set(MaxLines)) : Text.Block(string.Empty))) ) - .Width(Size.Units(180).Max(900)); + .Width(Size.Fraction(0.8f)); return Layout.Center() | new Card( Layout.Horizontal().Gap(12).Padding(3) | left | right - ) - .Width(Size.Units(340).Max(1000)); + ).Width(Size.Fraction(0.8f)); } } diff --git a/constructs/Ivy.Examples.Constructs.csproj b/constructs/ConstructsExample.csproj similarity index 91% rename from constructs/Ivy.Examples.Constructs.csproj rename to constructs/ConstructsExample.csproj index 82eb5e52..5053f64e 100644 --- a/constructs/Ivy.Examples.Constructs.csproj +++ b/constructs/ConstructsExample.csproj @@ -6,7 +6,7 @@ enable enable CS8618;CS8603;CS8602;CS8604;CS9113 - Ivy.Examples.Constructs + ConstructsExample diff --git a/constructs/Ivy.Examples.Constructs.sln b/constructs/ConstructsExample.sln similarity index 87% rename from constructs/Ivy.Examples.Constructs.sln rename to constructs/ConstructsExample.sln index bc4f3b89..3546062f 100644 --- a/constructs/Ivy.Examples.Constructs.sln +++ b/constructs/ConstructsExample.sln @@ -2,7 +2,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.5.2.0 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Constructs", "Ivy.Examples.Constructs.csproj", "{039638F3-A6D7-FB67-E733-27BD896EFDC4}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConstructsExample", "ConstructsExample.csproj", "{039638F3-A6D7-FB67-E733-27BD896EFDC4}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/constructs/Dockerfile b/constructs/Dockerfile index cd9ec834..2a496e4f 100644 --- a/constructs/Dockerfile +++ b/constructs/Dockerfile @@ -9,17 +9,17 @@ ARG BUILD_CONFIGURATION=Release WORKDIR /src # Copy and restore -COPY ["Ivy.Examples.Constructs.csproj", "./"] -RUN dotnet restore "Ivy.Examples.Constructs.csproj" +COPY ["ConstructsExample.csproj", "./"] +RUN dotnet restore "ConstructsExample.csproj" # Copy everything and build COPY . . -RUN dotnet build "Ivy.Examples.Constructs.csproj" -c $BUILD_CONFIGURATION -o /app/build +RUN dotnet build "ConstructsExample.csproj" -c $BUILD_CONFIGURATION -o /app/build # Publish stage FROM build AS publish ARG BUILD_CONFIGURATION=Release -RUN dotnet publish "Ivy.Examples.Constructs.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=true +RUN dotnet publish "ConstructsExample.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=true # Final runtime image FROM base AS final @@ -31,4 +31,4 @@ ENV PORT=80 ENV ASPNETCORE_URLS="http://+:80" # Run the executable -ENTRYPOINT ["dotnet","./Ivy.Examples.Constructs.dll"] \ No newline at end of file +ENTRYPOINT ["dotnet","./ConstructsExample.dll"] \ No newline at end of file diff --git a/constructs/Domain/DemoConstruct.cs b/constructs/Domain/DemoConstruct.cs index fbed0b81..0e88f77c 100644 --- a/constructs/Domain/DemoConstruct.cs +++ b/constructs/Domain/DemoConstruct.cs @@ -1,6 +1,6 @@ using Constructs; -namespace Ivy.Examples.Constructs.Domain; +namespace ConstructsExample.Domain; /// /// Represents a demonstration construct that builds a small construct tree for examples and tests. diff --git a/constructs/Domain/Traversal.cs b/constructs/Domain/Traversal.cs index 258b7a2f..30e4e8a6 100644 --- a/constructs/Domain/Traversal.cs +++ b/constructs/Domain/Traversal.cs @@ -1,6 +1,6 @@ using Constructs; -namespace Ivy.Examples.Constructs.Domain; +namespace ConstructsExample.Domain; /// /// Provides helpers for traversing and flattening a tree of instances. diff --git a/constructs/Extensions/ConstructExtensions.cs b/constructs/Extensions/ConstructExtensions.cs index 0efc5adf..7fa5e8ce 100644 --- a/constructs/Extensions/ConstructExtensions.cs +++ b/constructs/Extensions/ConstructExtensions.cs @@ -1,6 +1,6 @@ using Constructs; -namespace Ivy.Examples.Constructs.Extensions +namespace ConstructsExample.Extensions { /// /// Provides extension methods for working with instances. diff --git a/constructs/GlobalUsings.cs b/constructs/GlobalUsings.cs index 970fe426..cb65d1db 100644 --- a/constructs/GlobalUsings.cs +++ b/constructs/GlobalUsings.cs @@ -25,4 +25,4 @@ global using System.Globalization; global using System.Reactive.Linq; -namespace Ivy.Examples.Constructs; +namespace ConstructsExample; diff --git a/constructs/Program.cs b/constructs/Program.cs index 5c10b444..8e3d6b7a 100644 --- a/constructs/Program.cs +++ b/constructs/Program.cs @@ -1,4 +1,4 @@ -using Ivy.Examples.Constructs.Apps; +using ConstructsExample.Apps; CultureInfo.DefaultThreadCurrentCulture = CultureInfo.DefaultThreadCurrentUICulture = new CultureInfo("en-US"); var server = new Server(); #if DEBUG @@ -6,6 +6,18 @@ #endif server.AddAppsFromAssembly(); server.AddConnectionsFromAssembly(); -var chromeSettings = new ChromeSettings().DefaultApp().UseTabs(preventDuplicates: true); +var customHeader = Layout.Vertical().Gap(2).Align(Align.Center) + | new Html(@" +
+ + + +
+ ") + | new Button("Source Code").Url("https://github.com/Ivy-Interactive/Ivy-Examples/tree/main/constructs").Icon(Icons.ExternalLink).Width(Size.Full()).Height(Size.Units(10)); +var chromeSettings = new ChromeSettings() + .DefaultApp() + .UseTabs(preventDuplicates: true) + .Header(customHeader); server.UseChrome(chromeSettings); await server.RunAsync(); diff --git a/constructs/README.md b/constructs/README.md index d5c1f717..0754deb5 100644 --- a/constructs/README.md +++ b/constructs/README.md @@ -1,100 +1,76 @@ -# Ivy Example: AWS Constructs +# AWS Constructs -An interactive Ivy demo showcasing the basics of the [`Constructs`](https://github.com/aws/constructs) .NET package inside a pure C# Ivy app: build a small construct tree, inspect node paths, and add child nodes at runtime. +Image ---- +## One-Click Development Environment -## Requirements +[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new?hide_repo_select=true&ref=main&repo=Ivy-Interactive%2FIvy-Examples&machine=standardLinux32gb&devcontainer_path=.devcontainer%2Fconstructs%2Fdevcontainer.json&location=EuropeWest) -- **.NET 9 SDK** -- **Node.js 20 LTS** for **Constructs/jsii** - The **Constructs** .NET package is built with **jsii** and launches a Node.js child process. +Click the badge above to open Ivy Examples repository in GitHub Codespaces with: +- **.NET 9.0** SDK pre-installed +- **Ready-to-run** development environment +- **No local setup** required ---- +## Created Using Ivy -## Run +Web application created using [Ivy-Framework](https://github.com/Ivy-Interactive/Ivy-Framework). -From this `constructs/` folder: +**Ivy** - The ultimate framework for building internal tools with LLM code generation by unifying front-end and back-end into a single C# codebase. With Ivy, you can build robust internal tools and dashboards using C# and AI assistance based on your existing database. -```bash -dotnet restore -dotnet watch -# Open the printed URL (e.g., http://localhost:5010) -``` +Ivy is a web framework for building interactive web applications using C# and .NET. -If you see jsii/Node warnings or `$jsii.byref` errors, switch to **Node 20/22 LTS** and restart. That requirement comes from **Constructs/jsii**. +## Interactive Example For AWS Constructs ---- +This example demonstrates building composable configuration models using the [AWS Constructs library](https://github.com/aws/constructs) integrated with Ivy. Constructs are classes that define "pieces of system state" and can be composed together to form higher-level building blocks. -## What this demo shows +**What This Application Does:** -- **RootConstruct** as the root (no parent). -- **DemoConstruct** builds a canonical tree: +- **Build Construct Trees**: Create hierarchical construct structures with parent-child relationships +- **Interactive Tree Navigation**: Filter and view subtrees by specifying parent paths +- **Dynamic Node Addition**: Add new child constructs at runtime with custom IDs +- **ASCII Tree Visualization**: View the construct tree structure in a clean ASCII format +- **Tree Management**: Reset to canonical structure or expand/collapse large trees -``` -/Root -└─ Demo - ├─ ChildA - ├─ ChildB - └─ Nested - ├─ Leaf1 - └─ Leaf2 -``` +**Technical Implementation:** -- **Interactive actions** +- Uses AWS Constructs .NET package with jsii bridge to Node.js runtime +- Implements `RootConstruct` as the root node with no parent +- Creates canonical tree structure with Demo, ChildA, ChildB, and Nested constructs +- Provides interactive UI for tree manipulation and visualization +- Single C# view (`Apps/ConstructsApp.cs`) built with Ivy UI primitives - - Write **Parent path** (e.g., `Root`, `/Root/Demo/Nested`). - - **Add child** with any id (`new Construct(parent, id)`). - - **Reset** to the canonical tree. - - Compact **ASCII subtree** view with “Show more / Collapse”. +## How to Run ---- +1. **Prerequisites**: .NET 9.0 SDK +2. **Navigate to the example**: + ```bash + cd constructs + ``` +3. **Restore dependencies**: + ```bash + dotnet restore + ``` +4. **Run the application**: + ```bash + dotnet watch + ``` +5. **Open your browser** to the URL shown in the terminal (typically `http://localhost:5010`) -## UI overview +## How to Deploy -- **Left**: Controls (parent input, new child id, Add, Reset, status) -- **Right**: Current tree (ASCII, limited to N lines) +Deploy this example to Ivy's hosting platform: -![screenshot](screenshot.jpg) +1. **Navigate to the example**: + ```bash + cd constructs + ``` +2. **Deploy to Ivy hosting**: + ```bash + ivy deploy + ``` +This will deploy your AWS Constructs interactive demo with a single command. ---- +## Learn More -## Project structure - -``` -constructs/ -├─ Apps/ -│ └─ ConstructsApp.cs -├─ Domain/ -│ ├─ Traversal.cs -│ └─ DemoConstruct.cs -├─ Extensions/ -│ └─ ConstructExtensions.cs -├─ README.md -├─ screenshot.jpg -└─ Ivy.Examples.Constructs.csproj -``` - -Namespaces follow `Ivy.Examples.Constructs.*`. The app is registered with `[App(...)]` in `ConstructsApp.cs`. - ---- - -## Why Node.js is needed (jsii) - -The **Constructs** .NET package is generated via **jsii** and talks to a **Node.js** runtime under the hood. Ivy does not require Node, but when you use jsii-based packages (like Constructs), the jsii bridge spins up a Node child process. That’s why **Node 20 LTS** is mandatory here. - ---- - -## Troubleshooting - -- **Infinite spinner / `$jsii.byref` / init failed** - Use **Node 20 LTS**, then: - - ```bash - dotnet clean - dotnet restore - dotnet watch - ``` - -- **Long tree output** - Select a deeper parent path (subtree view) and use “Show more / Collapse”. +- AWS Constructs GitHub repository: [github.com/aws/constructs](https://github.com/aws/constructs) +- Ivy Documentation: [docs.ivy.app](https://docs.ivy.app) \ No newline at end of file diff --git a/constructs/screenshot.jpg b/constructs/screenshot.jpg deleted file mode 100644 index 7538e217..00000000 Binary files a/constructs/screenshot.jpg and /dev/null differ