Created
October 27, 2025 16:12
-
-
Save tjdevries/66ecc9c377e5b9277bdebac42b4f113b to your computer and use it in GitHub Desktop.
This file contains hidden or 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
| import { TextAttributes } from "@opentui/core"; | |
| import { render, useKeyboard } from "@opentui/react"; | |
| import { Atom, Result, useAtom, useAtomValue } from "@effect-atom/atom-react"; | |
| import { Schema, Effect } from "effect"; | |
| class Coffee extends Schema.Class<Coffee>("Coffee")({ | |
| id: Schema.String, | |
| name: Schema.String, | |
| price: Schema.Number, | |
| description: Schema.String, | |
| }) {} | |
| class CoffeeGroup extends Schema.Class<CoffeeGroup>("CoffeeGroup")({ | |
| name: Schema.String, | |
| coffees: Schema.Array(Coffee), | |
| }) {} | |
| function DisplayCoffeeGroup(props: { group: CoffeeGroup; selected: string }) { | |
| return ( | |
| <box width="100%" border={false} borderColor="green"> | |
| <text>{props.group.name}</text> | |
| {props.group.coffees.map((coffee, index) => ( | |
| <box | |
| key={index} | |
| width="100%" | |
| border={true} | |
| borderColor={props.selected == coffee.id ? "red" : "green"} | |
| > | |
| <text>{coffee.name}</text> | |
| </box> | |
| ))} | |
| </box> | |
| ); | |
| } | |
| class CoffeeGroups extends Effect.Service<CoffeeGroups>()("app/CoffeeGroups", { | |
| effect: Effect.gen(function* () { | |
| const getAll = Effect.succeed([ | |
| CoffeeGroup.make({ | |
| name: "Featured", | |
| coffees: [ | |
| Coffee.make({ | |
| id: "cron", | |
| name: "Cron", | |
| price: 10, | |
| description: "A cron job", | |
| }), | |
| ], | |
| }), | |
| CoffeeGroup.make({ | |
| name: "Originals", | |
| coffees: [ | |
| Coffee.make({ | |
| id: "404", | |
| name: "404", | |
| price: 10, | |
| description: "A 404 page", | |
| }), | |
| Coffee.make({ | |
| id: "object", | |
| name: "[Object object]", | |
| price: 10, | |
| description: "An object", | |
| }), | |
| Coffee.make({ | |
| id: "segfault", | |
| name: "Segfault", | |
| price: 10, | |
| description: "A segfault", | |
| }), | |
| ], | |
| }), | |
| ]); | |
| return { getAll } as const; | |
| }), | |
| }) {} | |
| const runtimeAtom: Atom.AtomRuntime<CoffeeGroups, never> = Atom.runtime( | |
| CoffeeGroups.Default, | |
| ); | |
| // You can then use the AtomRuntime to make Atom's that use the services from the Layer | |
| const coffeeGroupsAtom = runtimeAtom.atom( | |
| Effect.gen(function* () { | |
| const coffeeGroups = yield* CoffeeGroups; | |
| return yield* coffeeGroups.getAll; | |
| }), | |
| ); | |
| const coffeeGroupIdsAtom = Atom.make( | |
| Effect.fn(function* (get: Atom.Context) { | |
| const coffeeGroups = yield* get.result(coffeeGroupsAtom); | |
| return coffeeGroups.flatMap((group) => | |
| group.coffees.map((coffee) => coffee.id), | |
| ); | |
| }), | |
| ); | |
| const currentCoffeeIdxAtom = Atom.make(0); | |
| function App() { | |
| const coffeeGroups = useAtomValue(coffeeGroupsAtom); | |
| const [currentCoffeeIdx, setCurrentCoffeeIdx] = useAtom(currentCoffeeIdxAtom); | |
| useKeyboard((key) => { | |
| const groups = Result.getOrElse(coffeeGroups, () => { | |
| return []; | |
| }); | |
| console.log(groups.length); | |
| setCurrentCoffeeIdx((prev) => { | |
| if (key.name == "up") { | |
| return (prev + 1) % groups.length; | |
| } else if (key.name == "down") { | |
| return (((prev - 1) % groups.length) + groups.length) % groups.length; | |
| } | |
| return prev; | |
| }); | |
| }); | |
| return ( | |
| <box alignItems="flex-start" justifyContent="flex-start" flexGrow={1}> | |
| {Result.match(coffeeGroups, { | |
| onInitial() { | |
| return <text>Loading...</text>; | |
| }, | |
| onSuccess(groups) { | |
| const coffeeIds = groups.value.flatMap((group) => | |
| group.coffees.map((coffee) => coffee.id), | |
| ); | |
| return groups.value.map((group, index) => ( | |
| <box key={index} width="25%" border={true} borderColor="green"> | |
| <text> | |
| `${group.name}: ${currentCoffeeIdx} / ${coffeeIds.length}{" "} | |
| coffees` | |
| </text> | |
| <DisplayCoffeeGroup | |
| group={group} | |
| selected={coffeeIds[currentCoffeeIdx % coffeeIds.length]} | |
| /> | |
| </box> | |
| )); | |
| }, | |
| onFailure(error) { | |
| return <text>{`${error}`}</text>; | |
| }, | |
| })} | |
| </box> | |
| ); | |
| } | |
| render(<App />); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment