@@ -2,16 +2,17 @@ import { Todo } from "@/domain"
|
||||
import { KeyValueStore } from "@effect/platform"
|
||||
import { BrowserKeyValueStore } from "@effect/platform-browser"
|
||||
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, {
|
||||
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 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 moveUp: (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.catchTag("NoSuchElementException", () => Ref.set(todos, Chunk.empty())),
|
||||
|
||||
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 replace = (index: number, todo: Todo.Todo) => Ref.update(todos, Chunk.replace(index, todo))
|
||||
const remove = (index: number) => Ref.update(todos, Chunk.remove(index))
|
||||
|
||||
// const moveUp = (index: number) => Effect.gen(function*() {
|
||||
@@ -59,6 +63,7 @@ export const make = (key: string) => Layer.effect(TodosState, Effect.gen(functio
|
||||
readFromLocalStorage,
|
||||
saveToLocalStorage,
|
||||
prepend,
|
||||
replace,
|
||||
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 { Box, Card, Flex, Heading, TextArea } from "@radix-ui/themes"
|
||||
import { Effect } from "effect"
|
||||
import { useState } from "react"
|
||||
import { Reffuse } from "../reffuse"
|
||||
import { TodosState } from "../services"
|
||||
|
||||
|
||||
export interface VTodoProps {
|
||||
@@ -10,12 +14,29 @@ export interface VTodoProps {
|
||||
export function VTodo({ index, todo }: VTodoProps) {
|
||||
|
||||
const runSync = Reffuse.useRunSync()
|
||||
const editorMode = useState(false)
|
||||
|
||||
|
||||
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 { TodosState } from "../services"
|
||||
import { VNewTodo } from "./VNewTodo"
|
||||
import { VTodo } from "./VTodo"
|
||||
|
||||
|
||||
export function VTodos() {
|
||||
@@ -16,6 +19,18 @@ export function VTodos() {
|
||||
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