Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions packages-demos/slugify/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/.idea
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md
5 changes: 5 additions & 0 deletions packages-demos/slugify/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
bin/
obj/
*.sln.iml
.idea/**/*
**/.idea/*
93 changes: 93 additions & 0 deletions packages-demos/slugify/Apps/SlugApp.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
namespace SlugApp;

[App(icon: Icons.Pencil, title: "Slug Generator")]
public class SlugApp : ViewBase
{
public override object? Build()
{
// Declare states
var inputState = UseState("");
var slugState = UseState("");
var errorState = UseState("");

var yesNoOptions = new[] { "Yes", "No" }.ToOptions();

var lowerCaseState = UseState("Yes");
var collapseWhitespaceState = UseState("Yes");
var collapseDashesState = UseState("Yes");
var trimWhitespaceState = UseState("Yes");

bool ToBool(string? v) => (v ?? "no").ToLowerInvariant() == "yes";

var onSlugify = (Event<Button> e) =>
{
try
{
errorState.Set("");

var config = new SlugHelperConfiguration
{
ForceLowerCase = ToBool(lowerCaseState.Value),
TrimWhitespace = ToBool(trimWhitespaceState.Value),
CollapseDashes = ToBool(collapseDashesState.Value),
};

var slugifier = new SlugHelper(config);
slugState.Set(slugifier.GenerateSlug(inputState.Value ?? ""));
}
catch (ArgumentException ex)
{
errorState.Set($"Error: {ex.Message}");
}
catch (InvalidOperationException ex)
{
errorState.Set($"Error: {ex.Message}");
}
catch (Exception ex) when (
ex is not OutOfMemoryException &&
ex is not StackOverflowException &&
ex is not ThreadAbortException
)
{
errorState.Set($"Unexpected error: {ex.Message}");
}
};

// UI
return Layout.Vertical(
new Card(
Layout.Vertical().Gap(5)
| Text.H2("Slug Generator")
| Text.Muted("Convert any text into SEO-friendly slugs")

// Input
| inputState.ToTextInput().Placeholder("Enter text…").WithLabel("Input")

// Options Row 1
| Layout.Horizontal(
lowerCaseState.ToSelectInput(yesNoOptions).WithLabel("Force lowercase"),
collapseWhitespaceState.ToSelectInput(yesNoOptions).WithLabel("Collapse whitespace")
)

// Options Row 2
| Layout.Horizontal(
collapseDashesState.ToSelectInput(yesNoOptions).WithLabel("Collapse dashes"),
trimWhitespaceState.ToSelectInput(yesNoOptions).WithLabel("Trim whitespace")
)

// Generate Button
| new Button("Generate Slug", onSlugify).Primary().Width(Size.Full())

// Result
| Text.H2("Result")
| (string.IsNullOrEmpty(slugState.Value)
? Text.Muted("Slug will appear here…")
: Text.Code(slugState.Value))

| (string.IsNullOrEmpty(errorState.Value)
? null
: new Callout(errorState.Value).Variant(CalloutVariant.Error))
).Width(Size.Full())
);
}
}
49 changes: 49 additions & 0 deletions packages-demos/slugify/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# ----------------------------
# Base runtime image
# ----------------------------
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
WORKDIR /app
EXPOSE 80

# ----------------------------
# Build stage
# ----------------------------
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src

# Copy only the project file(s) first
COPY ["Slugify.csproj", "./"]

# ----------------------------
# Pre-fetch NuGet packages
# ----------------------------
# This step downloads all packages used by the project and caches them in a layer
RUN dotnet restore "Slugify.csproj" --disable-parallel --no-cache

# Copy the rest of the source code
COPY . .

# Build the project
RUN dotnet build "Slugify.csproj" -c $BUILD_CONFIGURATION -o /app/build

# ----------------------------
# Publish stage
# ----------------------------
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "Slugify.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=true

# ----------------------------
# Final runtime image
# ----------------------------
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .

# Environment variables
ENV PORT=80
ENV ASPNETCORE_URLS="http://+:80"

# Run the application
ENTRYPOINT ["dotnet", "Slugify.dll"]
11 changes: 11 additions & 0 deletions packages-demos/slugify/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
global using Ivy;
global using Ivy.Apps;
global using Ivy.Chrome;
global using Ivy.Core;
global using Ivy.Shared;
global using Ivy.Views;
global using Ivy.Widgets.Inputs;
global using System.Globalization;
global using Slugify;

namespace Slugify;
19 changes: 19 additions & 0 deletions packages-demos/slugify/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using SlugApp;
CultureInfo.DefaultThreadCurrentCulture = CultureInfo.DefaultThreadCurrentUICulture = new CultureInfo("en-US");

var server = new Server();
#if DEBUG
server.UseHotReload();
#endif

server.AddAppsFromAssembly();
server.AddConnectionsFromAssembly();


var chromeSettings = new ChromeSettings()
.DefaultApp<SlugApp.SlugApp>()
.UseTabs(preventDuplicates: true);

server.UseChrome(chromeSettings);

await server.RunAsync();
81 changes: 81 additions & 0 deletions packages-demos/slugify/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Slugify Slug Generator App

<img width="1430" height="752" alt="Screenshot 2025-11-18 at 15 07 08" src="https://github.com/user-attachments/assets/40cba7dd-79d1-4259-922b-fa5c28575f85" />


## One-Click Development Environment

[![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%2Fslugify%2Fdevcontainer.json&location=EuropeWest)

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

Web application created using [Ivy-Framework](https://github.com/Ivy-Interactive/Ivy-Framework).

**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.

Ivy is a web framework for building interactive web applications using C# and .NET.

**Interactive Example For Slug Creation**

This example demonstrates slug generation operations using the Slugify.Core library integrated with Ivy. Slugify.Core is a free and open-source C# library that converts text into SEO-friendly, URL-safe slugs with configurable options.

**What This Application Does:**

This specific implementation creates a Slug Generator application that allows users to:
• Generate SEO-Friendly Slugs: Enter any text to create clean, hyphenated slugs suitable for URLs
• Customizable Options:
• Force lowercase
• Trim whitespace
• Collapse consecutive dashes
• Collapse whitespace
• Interactive UI: See instant slug results as you generate them
• Error Handling: Displays meaningful error messages if slug generation fails

**Technical Implementation:**

• Uses Slugify.Core 5.1.1 for fast, memory-efficient slug generation
• Implements Ivy’s reactive UseState and event-driven UI controls
• Provides toggle/select inputs for all configurable slug options
• Generates slugs in real-time and updates the UI dynamically
• Uses expandable panels to display generated slugs clearly
• Handles invalid inputs and displays user-friendly error messages

## How to Run
1. **Prerequisites**: .NET 9.0 SDK
2. **Navigate to the example**:
```bash
cd slugify
```
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`)

## How to Deploy

Deploy this example to Ivy's hosting platform:

1. **Navigate to the example**:
```bash
cd slugify
```
2. **Deploy to Ivy hosting**:
```bash
ivy deploy
```
This will deploy your Slug Generator application with a single command.

## Learn More

- Slugify for .NET overview: (https://github.com/ctolkien/Slugify)
- Ivy Documentation: [docs.ivy.app](https://docs.ivy.app)
28 changes: 28 additions & 0 deletions packages-demos/slugify/Slugify.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<NoWarn>CS8618;CS8603;CS8602;CS8604;CS9113</NoWarn>
<RootNamespace>Slugify</RootNamespace>
</PropertyGroup>

<ItemGroup>
<EmbeddedResource Include="Assets/**/*" />
</ItemGroup>


<ItemGroup>
<PackageReference Include="Ivy" Version="1.0.135.0" />
<PackageReference Include="Slugify.Core" Version="5.1.1" />
</ItemGroup>


<ItemGroup>
<Folder Include="Apps" />
<Folder Include="Connections" />
</ItemGroup>

</Project>