0.1.0 (#1)
Co-authored-by: Julien Valverdé <julien.valverde@mailo.com> Reviewed-on: https://gitea:3000/Thilawyn/reffuse/pulls/1
This commit was merged in pull request #1.
This commit is contained in:
7
packages/example/src/todos/reffuse.ts
Normal file
7
packages/example/src/todos/reffuse.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { GlobalContext } from "@/reffuse"
|
||||
import { Reffuse, ReffuseContext } from "reffuse"
|
||||
import { TodosState } from "./services"
|
||||
|
||||
|
||||
export const TodosContext = ReffuseContext.make<TodosState.TodosState>()
|
||||
export const R = Reffuse.make(GlobalContext, TodosContext)
|
||||
69
packages/example/src/todos/services/TodosState.ts
Normal file
69
packages/example/src/todos/services/TodosState.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { Todo } from "@/domain"
|
||||
import { KeyValueStore } from "@effect/platform"
|
||||
import { BrowserKeyValueStore } from "@effect/platform-browser"
|
||||
import { PlatformError } from "@effect/platform/Error"
|
||||
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, 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>
|
||||
}>() {}
|
||||
|
||||
|
||||
export const make = (key: string) => Layer.effect(TodosState, Effect.gen(function*() {
|
||||
const todos = yield* SubscriptionRef.make(Chunk.empty<Todo.Todo>())
|
||||
|
||||
const readFromLocalStorage = KeyValueStore.KeyValueStore.pipe(
|
||||
Effect.flatMap(kv => kv.get(key)),
|
||||
Effect.flatMap(identity),
|
||||
Effect.flatMap(Schema.parseJson().pipe(
|
||||
Schema.compose(Schema.Chunk(Todo.TodoFromJson)),
|
||||
Schema.decode,
|
||||
)),
|
||||
Effect.flatMap(v => Ref.set(todos, v)),
|
||||
|
||||
Effect.catchTag("NoSuchElementException", () => Ref.set(todos, Chunk.empty())),
|
||||
|
||||
Effect.provide(BrowserKeyValueStore.layerLocalStorage),
|
||||
)
|
||||
|
||||
const saveToLocalStorage = Effect.all([KeyValueStore.KeyValueStore, todos]).pipe(
|
||||
Effect.flatMap(([kv, values]) => values.pipe(
|
||||
Schema.parseJson().pipe(
|
||||
Schema.compose(Schema.Chunk(Todo.TodoFromJson)),
|
||||
Schema.encode,
|
||||
),
|
||||
Effect.flatMap(v => kv.set(key, v)),
|
||||
)),
|
||||
|
||||
Effect.provide(BrowserKeyValueStore.layerLocalStorage),
|
||||
)
|
||||
|
||||
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*() {
|
||||
|
||||
// })
|
||||
|
||||
yield* readFromLocalStorage
|
||||
|
||||
return {
|
||||
todos,
|
||||
readFromLocalStorage,
|
||||
saveToLocalStorage,
|
||||
prepend,
|
||||
replace,
|
||||
remove,
|
||||
}
|
||||
}))
|
||||
1
packages/example/src/todos/services/index.ts
Normal file
1
packages/example/src/todos/services/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * as TodosState from "./TodosState"
|
||||
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 { R } from "../reffuse"
|
||||
import { TodosState } from "../services"
|
||||
|
||||
|
||||
export function VNewTodo() {
|
||||
|
||||
const runSync = R.useRunSync()
|
||||
|
||||
const createEmptyTodo = Todo.generateUniqueID.pipe(
|
||||
Effect.map(id => Todo.Todo.make({
|
||||
id,
|
||||
content: "",
|
||||
completedAt: Option.none(),
|
||||
}, true))
|
||||
)
|
||||
|
||||
const todoRef = R.useRefFromEffect(createEmptyTodo)
|
||||
const [todo, setTodo] = R.useRefState(todoRef)
|
||||
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Card>
|
||||
<Flex direction="column" align="stretch" gap="2">
|
||||
<TextArea
|
||||
value={todo.content}
|
||||
onChange={e => setTodo(prev =>
|
||||
Todo.Todo.make({ ...prev, 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>
|
||||
)
|
||||
|
||||
}
|
||||
56
packages/example/src/todos/views/VTodo.tsx
Normal file
56
packages/example/src/todos/views/VTodo.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import { Todo } from "@/domain"
|
||||
import { Box, Card, Flex, IconButton, TextArea } from "@radix-ui/themes"
|
||||
import { Effect } from "effect"
|
||||
import { Delete } from "lucide-react"
|
||||
import { useState } from "react"
|
||||
import { R } from "../reffuse"
|
||||
import { TodosState } from "../services"
|
||||
|
||||
|
||||
export interface VTodoProps {
|
||||
readonly index: number
|
||||
readonly todo: Todo.Todo
|
||||
}
|
||||
|
||||
export function VTodo({ index, todo }: VTodoProps) {
|
||||
|
||||
const runSync = R.useRunSync()
|
||||
const editorMode = useState(false)
|
||||
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Card>
|
||||
<Flex direction="column" align="stretch" gap="1">
|
||||
<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 direction="row" justify="between" align="center">
|
||||
<Box></Box>
|
||||
|
||||
<Flex direction="row" align="center" gap="1">
|
||||
<IconButton
|
||||
onClick={() => TodosState.TodosState.pipe(
|
||||
Effect.flatMap(state => state.remove(index)),
|
||||
runSync,
|
||||
)}
|
||||
>
|
||||
<Delete />
|
||||
</IconButton>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Card>
|
||||
</Box>
|
||||
)
|
||||
|
||||
}
|
||||
36
packages/example/src/todos/views/VTodos.tsx
Normal file
36
packages/example/src/todos/views/VTodos.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import { Box, Flex } from "@radix-ui/themes"
|
||||
import { Chunk, Effect, Stream } from "effect"
|
||||
import { R } from "../reffuse"
|
||||
import { TodosState } from "../services"
|
||||
import { VNewTodo } from "./VNewTodo"
|
||||
import { VTodo } from "./VTodo"
|
||||
|
||||
|
||||
export function VTodos() {
|
||||
|
||||
// Sync changes to the todos with the local storage
|
||||
R.useFork(TodosState.TodosState.pipe(
|
||||
Effect.flatMap(state =>
|
||||
Stream.runForEach(state.todos.changes, () => state.saveToLocalStorage)
|
||||
)
|
||||
))
|
||||
|
||||
const todosRef = R.useMemo(TodosState.TodosState.pipe(Effect.map(state => state.todos)))
|
||||
const [todos] = R.useRefState(todosRef)
|
||||
|
||||
|
||||
return (
|
||||
<Flex direction="column" align="center" gap="3">
|
||||
<Box width="500px">
|
||||
<VNewTodo />
|
||||
</Box>
|
||||
|
||||
{Chunk.map(todos, (todo, index) => (
|
||||
<Box key={todo.id} width="500px">
|
||||
<VTodo index={index} todo={todo} />
|
||||
</Box>
|
||||
))}
|
||||
</Flex>
|
||||
)
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user