-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
189 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
# Todo App Example | ||
|
||
A simple todo list application demonstrating how to build a web application using cross-stream's [built-in HTTP server](https://cablehead.github.io/xs/reference/http-server/). | ||
|
||
<img width="726" alt="image" src="https://github.com/user-attachments/assets/ccfcaa62-436e-4a85-95df-29a142a426cd" /> | ||
|
||
## Features | ||
|
||
- Add new todos | ||
- Toggle todo completion status | ||
- Persistent storage using cross-stream's event store | ||
|
||
## Structure | ||
|
||
- `handler.nu` - Event handler for processing HTTP requests and managing todos | ||
- `index.html` - Frontend interface with styling and JavaScript | ||
|
||
## Running the App | ||
|
||
1. Start cross-stream with HTTP server enabled: | ||
```bash | ||
xs serve ./store --http :5007 | ||
``` | ||
|
||
2. Load the handler and template: | ||
```nushell | ||
# Install minijinja-cli first: https://github.com/mitsuhiko/minijinja | ||
cat handler.nu | .append todo.handler.register | ||
cat index.html | .append index.html | ||
``` | ||
|
||
3. Visit: http://localhost:5007 in your browser | ||
|
||
## Event Structure | ||
|
||
The todo state is rebuilt from two event types: | ||
|
||
- `todo` - Contains the text content of new todos | ||
- `todo.toggle` - Records completion status changes | ||
|
||
The handler aggregates these events to maintain the current state of all todos, with each todo having a unique ID, text content, and completion status. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
{ | ||
process: {|frame| | ||
if $frame.topic != "http.request" { return } | ||
|
||
match $frame.meta { | ||
{uri: "/" method: "GET"} => { | ||
# Get todos | ||
let todos = .cat | where topic =~ "todo" | reduce --fold {} {|f acc| | ||
match $f.topic { | ||
"todo" => ($acc | insert $f.id {text: (.cas $f.hash) done: false}) | ||
todo.toggle => ($acc | update ($f.meta.id) { $in | update done { not ($in) } }) | ||
_ => $acc | ||
} | ||
} | ||
|
||
# Render template | ||
{todos: $todos} | to json -r | minijinja-cli -f json -t (.head index.html | .cas $in.hash) '' - | .append http.response --meta { | ||
request_id: $frame.id | ||
status: 200 | ||
headers: { | ||
Content-Type: "text/html" | ||
} | ||
} | ||
} | ||
|
||
{uri: "/" method: "POST"} => { | ||
# Get the todo content and store it | ||
.cas $frame.hash | url split-query | transpose -rdl | get todo | .append todo | ||
|
||
# Redirect to GET / | ||
.append http.response --meta { | ||
request_id: $frame.id | ||
status: 303 | ||
headers: { | ||
Location: "/" | ||
} | ||
} | ||
} | ||
|
||
{uri: "/toggle" method: "POST"} => { | ||
.cas $frame.hash | from json | .append todo.toggle --meta $in | ||
"OK" | .append http.response --meta {request_id: $frame.id status: 200 headers: {Content-Type: "text/plain"}} | ||
} | ||
|
||
_ => { | ||
"not found :/" | .append http.response --meta { | ||
request_id: $frame.id | ||
status: 404 | ||
headers: { | ||
Content-Type: "text/html" | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<title>Todo List</title> | ||
<style> | ||
body { | ||
max-width: 600px; | ||
margin: 0 auto; | ||
padding: 20px; | ||
font-family: system-ui, sans-serif; | ||
} | ||
.todo-item { | ||
padding: 10px; | ||
margin: 5px 0; | ||
background: #f5f5f5; | ||
border-radius: 4px; | ||
display: flex; | ||
align-items: center; | ||
gap: 10px; | ||
} | ||
.todo-item.done span { | ||
text-decoration: line-through; | ||
color: #666; | ||
} | ||
.todo-form { | ||
margin-top: 20px; | ||
} | ||
textarea { | ||
width: 100%; | ||
padding: 10px; | ||
margin-bottom: 10px; | ||
border: 1px solid #ddd; | ||
border-radius: 4px; | ||
} | ||
button { | ||
padding: 8px 16px; | ||
background: #0066cc; | ||
color: white; | ||
border: none; | ||
border-radius: 4px; | ||
cursor: pointer; | ||
} | ||
button:hover { | ||
background: #0052a3; | ||
} | ||
input[type="checkbox"] { | ||
width: 20px; | ||
height: 20px; | ||
} | ||
</style> | ||
</head> | ||
<body> | ||
<h1>Todos</h1> | ||
|
||
<div class="todo-list"> | ||
{% for id, todo in todos | items %} | ||
<div class="todo-item {% if todo.done %}done{% endif %}"> | ||
<input | ||
type="checkbox" | ||
{% | ||
if | ||
todo.done | ||
%}checked{% | ||
endif | ||
%} | ||
onchange="toggleTodo('{{ id }}', this.checked)" | ||
/> | ||
<span>{{ todo.text }}</span> | ||
</div> | ||
{% else %} | ||
<p>No todos yet. Add one below!</p> | ||
{% endfor %} | ||
</div> | ||
|
||
<form class="todo-form" method="POST"> | ||
<textarea name="todo" rows="3" placeholder="Enter a new todo..."></textarea> | ||
<button type="submit">Add Todo</button> | ||
</form> | ||
|
||
<script> | ||
function toggleTodo(id, done) { | ||
fetch(`/toggle`, { | ||
method: "POST", | ||
headers: { | ||
"Content-Type": "application/json", | ||
}, | ||
body: JSON.stringify({ id: id }), | ||
}).then(() => window.location.reload()); | ||
} | ||
</script> | ||
</body> | ||
</html> |