0.1.0 #1
@@ -34,6 +34,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@effect/platform": "^0.73.1",
|
"@effect/platform": "^0.73.1",
|
||||||
"@effect/platform-browser": "^0.52.1"
|
"@effect/platform-browser": "^0.52.1",
|
||||||
|
"@radix-ui/themes": "^3.1.6",
|
||||||
|
"@typed/id": "^0.17.1",
|
||||||
|
"mobx": "^6.13.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,28 @@
|
|||||||
import { ThSchema } from "@thilawyn/thilaschema"
|
import { ThSchema } from "@thilawyn/thilaschema"
|
||||||
import { Schema } from "effect"
|
import { CuidState, DateTimes, GetRandomValues, makeCuid } from "@typed/id"
|
||||||
|
import { Effect, Schema } from "effect"
|
||||||
|
|
||||||
|
|
||||||
export class Todo extends Schema.Class<Todo>("Todo")({
|
export class Todo extends Schema.Class<Todo>("Todo")({
|
||||||
|
_tag: Schema.tag("Todo"),
|
||||||
id: Schema.String,
|
id: Schema.String,
|
||||||
content: Schema.String,
|
content: Schema.String,
|
||||||
completedAt: Schema.DateTimeUtcFromSelf,
|
completedAt: Schema.OptionFromSelf(Schema.DateTimeUtcFromSelf),
|
||||||
}) {}
|
}) {}
|
||||||
|
|
||||||
|
|
||||||
export const TodoFromJsonStruct = Schema.Struct({
|
export const TodoFromJsonStruct = Schema.Struct({
|
||||||
...Todo.fields,
|
...Todo.fields,
|
||||||
completedAt: Schema.DateTimeUtc,
|
completedAt: Schema.Option(Schema.DateTimeUtc),
|
||||||
}).pipe(
|
}).pipe(
|
||||||
ThSchema.assertEncodedJsonifiable
|
ThSchema.assertEncodedJsonifiable
|
||||||
)
|
)
|
||||||
|
|
||||||
export const TodoFromJson = TodoFromJsonStruct.pipe(Schema.compose(Todo))
|
export const TodoFromJson = TodoFromJsonStruct.pipe(Schema.compose(Todo))
|
||||||
|
|
||||||
|
|
||||||
|
export const generateUniqueID = makeCuid.pipe(
|
||||||
|
Effect.provide(CuidState.layer("@reffuse/example")),
|
||||||
|
Effect.provide(GetRandomValues.CryptoRandom),
|
||||||
|
Effect.provide(DateTimes.Default),
|
||||||
|
)
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { ReffuseRuntime } from "@thilawyn/reffuse"
|
|||||||
import { Layer } from "effect"
|
import { Layer } from "effect"
|
||||||
import { StrictMode } from "react"
|
import { StrictMode } from "react"
|
||||||
import { createRoot } from "react-dom/client"
|
import { createRoot } from "react-dom/client"
|
||||||
import "./index.css"
|
|
||||||
import { GlobalContext } from "./reffuse"
|
import { GlobalContext } from "./reffuse"
|
||||||
import { routeTree } from "./routeTree.gen"
|
import { routeTree } from "./routeTree.gen"
|
||||||
import { FetchData } from "./services"
|
import { FetchData } from "./services"
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
|
import { Container, Flex, Theme } from "@radix-ui/themes"
|
||||||
|
import "@radix-ui/themes/styles.css"
|
||||||
import { createRootRoute, Link, Outlet } from "@tanstack/react-router"
|
import { createRootRoute, Link, Outlet } from "@tanstack/react-router"
|
||||||
import { TanStackRouterDevtools } from "@tanstack/router-devtools"
|
import { TanStackRouterDevtools } from "@tanstack/router-devtools"
|
||||||
|
import "../index.css"
|
||||||
|
|
||||||
|
|
||||||
export const Route = createRootRoute({
|
export const Route = createRootRoute({
|
||||||
@@ -7,14 +10,18 @@ export const Route = createRootRoute({
|
|||||||
})
|
})
|
||||||
|
|
||||||
function Root() {
|
function Root() {
|
||||||
return <>
|
return (
|
||||||
<div className="container flex-row gap-2 justify-center items-center mx-auto mb-4">
|
<Theme>
|
||||||
<Link to="/">Index</Link>
|
<Container>
|
||||||
<Link to="/time">Time</Link>
|
<Flex direction="row" justify="center" align="center" gap="2">
|
||||||
<Link to="/count">Count</Link>
|
<Link to="/">Index</Link>
|
||||||
</div>
|
<Link to="/time">Time</Link>
|
||||||
|
<Link to="/count">Count</Link>
|
||||||
|
</Flex>
|
||||||
|
</Container>
|
||||||
|
|
||||||
<Outlet />
|
<Outlet />
|
||||||
<TanStackRouterDevtools />
|
<TanStackRouterDevtools />
|
||||||
</>
|
</Theme>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { TodosContext } from "@/todos/reffuse"
|
import { TodosContext } from "@/todos/reffuse"
|
||||||
import { TodosState } from "@/todos/services"
|
import { TodosState } from "@/todos/services"
|
||||||
import { VTodos } from "@/todos/views/VTodos"
|
import { VTodos } from "@/todos/views/VTodos"
|
||||||
|
import { Container } from "@radix-ui/themes"
|
||||||
import { createFileRoute } from "@tanstack/react-router"
|
import { createFileRoute } from "@tanstack/react-router"
|
||||||
import { Layer } from "effect"
|
import { Layer } from "effect"
|
||||||
import { useMemo } from "react"
|
import { useMemo } from "react"
|
||||||
@@ -18,11 +19,11 @@ function Index() {
|
|||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto">
|
<Container>
|
||||||
<TodosContext.Provider layer={todosLayer}>
|
<TodosContext.Provider layer={todosLayer}>
|
||||||
<VTodos />
|
<VTodos />
|
||||||
</TodosContext.Provider>
|
</TodosContext.Provider>
|
||||||
</div>
|
</Container>
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,16 +2,17 @@ import { Todo } from "@/domain"
|
|||||||
import { KeyValueStore } from "@effect/platform"
|
import { KeyValueStore } from "@effect/platform"
|
||||||
import { BrowserKeyValueStore } from "@effect/platform-browser"
|
import { BrowserKeyValueStore } from "@effect/platform-browser"
|
||||||
import { PlatformError } from "@effect/platform/Error"
|
import { PlatformError } from "@effect/platform/Error"
|
||||||
import { Cause, Chunk, Context, Effect, identity, Layer, ParseResult, Ref, Schema, SubscriptionRef } from "effect"
|
import { Chunk, Context, Effect, identity, Layer, ParseResult, Ref, Schema, SubscriptionRef } from "effect"
|
||||||
|
|
||||||
|
|
||||||
export class TodosState extends Context.Tag("TodosState")<TodosState, {
|
export class TodosState extends Context.Tag("TodosState")<TodosState, {
|
||||||
readonly todos: SubscriptionRef.SubscriptionRef<Chunk.Chunk<Todo.Todo>>
|
readonly todos: SubscriptionRef.SubscriptionRef<Chunk.Chunk<Todo.Todo>>
|
||||||
|
|
||||||
readonly readFromLocalStorage: Effect.Effect<void, Cause.NoSuchElementException | PlatformError | ParseResult.ParseError>
|
readonly readFromLocalStorage: Effect.Effect<void, PlatformError | ParseResult.ParseError>
|
||||||
readonly saveToLocalStorage: Effect.Effect<void, PlatformError | ParseResult.ParseError>
|
readonly saveToLocalStorage: Effect.Effect<void, PlatformError | ParseResult.ParseError>
|
||||||
|
|
||||||
readonly prepend: (todo: Todo.Todo) => Effect.Effect<void>
|
readonly prepend: (todo: Todo.Todo) => Effect.Effect<void>
|
||||||
|
readonly replace: (index: number, todo: Todo.Todo) => Effect.Effect<void>
|
||||||
readonly remove: (index: number) => Effect.Effect<void>
|
readonly remove: (index: number) => Effect.Effect<void>
|
||||||
// readonly moveUp: (index: number) => Effect.Effect<void, Cause.NoSuchElementException>
|
// readonly moveUp: (index: number) => Effect.Effect<void, Cause.NoSuchElementException>
|
||||||
// readonly moveDown: (index: number) => Effect.Effect<void, Cause.NoSuchElementException>
|
// readonly moveDown: (index: number) => Effect.Effect<void, Cause.NoSuchElementException>
|
||||||
@@ -30,6 +31,8 @@ export const make = (key: string) => Layer.effect(TodosState, Effect.gen(functio
|
|||||||
)),
|
)),
|
||||||
Effect.flatMap(v => Ref.set(todos, v)),
|
Effect.flatMap(v => Ref.set(todos, v)),
|
||||||
|
|
||||||
|
Effect.catchTag("NoSuchElementException", () => Ref.set(todos, Chunk.empty())),
|
||||||
|
|
||||||
Effect.provide(BrowserKeyValueStore.layerLocalStorage),
|
Effect.provide(BrowserKeyValueStore.layerLocalStorage),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -46,6 +49,7 @@ export const make = (key: string) => Layer.effect(TodosState, Effect.gen(functio
|
|||||||
)
|
)
|
||||||
|
|
||||||
const prepend = (todo: Todo.Todo) => Ref.update(todos, Chunk.prepend(todo))
|
const prepend = (todo: Todo.Todo) => Ref.update(todos, Chunk.prepend(todo))
|
||||||
|
const replace = (index: number, todo: Todo.Todo) => Ref.update(todos, Chunk.replace(index, todo))
|
||||||
const remove = (index: number) => Ref.update(todos, Chunk.remove(index))
|
const remove = (index: number) => Ref.update(todos, Chunk.remove(index))
|
||||||
|
|
||||||
// const moveUp = (index: number) => Effect.gen(function*() {
|
// const moveUp = (index: number) => Effect.gen(function*() {
|
||||||
@@ -59,6 +63,7 @@ export const make = (key: string) => Layer.effect(TodosState, Effect.gen(functio
|
|||||||
readFromLocalStorage,
|
readFromLocalStorage,
|
||||||
saveToLocalStorage,
|
saveToLocalStorage,
|
||||||
prepend,
|
prepend,
|
||||||
|
replace,
|
||||||
remove,
|
remove,
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|||||||
52
packages/example/src/todos/views/VNewTodo.tsx
Normal file
52
packages/example/src/todos/views/VNewTodo.tsx
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import { Todo } from "@/domain"
|
||||||
|
import { Box, Button, Card, Flex, TextArea } from "@radix-ui/themes"
|
||||||
|
import { Effect, Option } from "effect"
|
||||||
|
import { Reffuse } from "../reffuse"
|
||||||
|
import { TodosState } from "../services"
|
||||||
|
|
||||||
|
|
||||||
|
export function VNewTodo() {
|
||||||
|
|
||||||
|
const runSync = Reffuse.useRunSync()
|
||||||
|
|
||||||
|
const createEmptyTodo = Todo.generateUniqueID.pipe(
|
||||||
|
Effect.map(id => Todo.Todo.make({
|
||||||
|
id,
|
||||||
|
content: "",
|
||||||
|
completedAt: Option.none(),
|
||||||
|
}, true))
|
||||||
|
)
|
||||||
|
|
||||||
|
const todoRef = Reffuse.useRefFromEffect(createEmptyTodo)
|
||||||
|
const [todo, setTodo] = Reffuse.useRefState(todoRef)
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Card>
|
||||||
|
<Flex direction="column" align="stretch" gap="2">
|
||||||
|
<TextArea
|
||||||
|
value={todo.content}
|
||||||
|
onChange={e => setTodo(
|
||||||
|
Todo.Todo.make({ ...todo, content: e.target.value }, true)
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Flex direction="row" justify="center" align="center">
|
||||||
|
<Button
|
||||||
|
onClick={() => TodosState.TodosState.pipe(
|
||||||
|
Effect.flatMap(state => state.prepend(todo)),
|
||||||
|
Effect.flatMap(() => createEmptyTodo),
|
||||||
|
Effect.map(setTodo),
|
||||||
|
runSync,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
Add
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</Card>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,5 +1,9 @@
|
|||||||
import { Todo } from "@/domain"
|
import { Todo } from "@/domain"
|
||||||
|
import { Box, Card, Flex, Heading, TextArea } from "@radix-ui/themes"
|
||||||
|
import { Effect } from "effect"
|
||||||
|
import { useState } from "react"
|
||||||
import { Reffuse } from "../reffuse"
|
import { Reffuse } from "../reffuse"
|
||||||
|
import { TodosState } from "../services"
|
||||||
|
|
||||||
|
|
||||||
export interface VTodoProps {
|
export interface VTodoProps {
|
||||||
@@ -10,12 +14,29 @@ export interface VTodoProps {
|
|||||||
export function VTodo({ index, todo }: VTodoProps) {
|
export function VTodo({ index, todo }: VTodoProps) {
|
||||||
|
|
||||||
const runSync = Reffuse.useRunSync()
|
const runSync = Reffuse.useRunSync()
|
||||||
|
const editorMode = useState(false)
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="">
|
<Box>
|
||||||
|
<Card>
|
||||||
|
<Flex direction="column" align="stretch" gap="1">
|
||||||
|
<Heading>#{todo.id}</Heading>
|
||||||
|
|
||||||
</div>
|
<TextArea
|
||||||
|
value={todo.content}
|
||||||
|
onChange={e => TodosState.TodosState.pipe(
|
||||||
|
Effect.flatMap(state => state.replace(
|
||||||
|
index,
|
||||||
|
Todo.Todo.make({ ...todo, content: e.target.value }, true),
|
||||||
|
)),
|
||||||
|
runSync,
|
||||||
|
)}
|
||||||
|
disabled={!editorMode}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
</Card>
|
||||||
|
</Box>
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { Effect, Stream } from "effect"
|
import { Flex } from "@radix-ui/themes"
|
||||||
|
import { Chunk, Effect, Stream } from "effect"
|
||||||
import { Reffuse } from "../reffuse"
|
import { Reffuse } from "../reffuse"
|
||||||
import { TodosState } from "../services"
|
import { TodosState } from "../services"
|
||||||
|
import { VNewTodo } from "./VNewTodo"
|
||||||
|
import { VTodo } from "./VTodo"
|
||||||
|
|
||||||
|
|
||||||
export function VTodos() {
|
export function VTodos() {
|
||||||
@@ -16,6 +19,18 @@ export function VTodos() {
|
|||||||
const [todos] = Reffuse.useRefState(todosRef)
|
const [todos] = Reffuse.useRefState(todosRef)
|
||||||
|
|
||||||
|
|
||||||
return <></>
|
return (
|
||||||
|
<Flex direction="column" align="stretch" gap="3">
|
||||||
|
<VNewTodo />
|
||||||
|
|
||||||
|
{Chunk.map(todos, (todo, index) => (
|
||||||
|
<VTodo
|
||||||
|
key={todo.id}
|
||||||
|
index={index}
|
||||||
|
todo={todo}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Flex>
|
||||||
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user