@reffuse/extension-query 0.1.4 #15
@@ -26,9 +26,9 @@ function Todos() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<TodosContext.Provider layer={todosLayer}>
|
<TodosContext.AsyncProvider layer={todosLayer}>
|
||||||
<VTodos />
|
<VTodos />
|
||||||
</TodosContext.Provider>
|
</TodosContext.AsyncProvider>
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -2,14 +2,14 @@ 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 { Chunk, Context, Effect, identity, Layer, ParseResult, Ref, Schema, SubscriptionRef } from "effect"
|
import { Chunk, Context, Effect, identity, Layer, ParseResult, Ref, Schema, Stream, 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, PlatformError | ParseResult.ParseError>
|
readonly load: Effect.Effect<void, PlatformError | ParseResult.ParseError>
|
||||||
readonly saveToLocalStorage: Effect.Effect<void, PlatformError | ParseResult.ParseError>
|
readonly save: 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 replace: (index: number, todo: Todo.Todo) => Effect.Effect<void>
|
||||||
@@ -20,34 +20,31 @@ export class TodosState extends Context.Tag("TodosState")<TodosState, {
|
|||||||
|
|
||||||
|
|
||||||
export const make = (key: string) => Layer.effect(TodosState, Effect.gen(function*() {
|
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(
|
const readFromLocalStorage = KeyValueStore.KeyValueStore.pipe(
|
||||||
Effect.flatMap(kv => kv.get(key)),
|
Effect.flatMap(kv => kv.get(key)),
|
||||||
Effect.flatMap(identity),
|
Effect.flatMap(identity),
|
||||||
Effect.flatMap(Schema.parseJson().pipe(
|
Effect.flatMap(Schema.decode(
|
||||||
Schema.compose(Schema.Chunk(Todo.TodoFromJson)),
|
Schema.compose(Schema.parseJson(), Schema.Chunk(Todo.TodoFromJson))
|
||||||
Schema.decode,
|
|
||||||
)),
|
)),
|
||||||
Effect.flatMap(v => Ref.set(todos, v)),
|
Effect.catchTag("NoSuchElementException", () => Effect.succeed(Chunk.empty<Todo.Todo>())),
|
||||||
|
|
||||||
Effect.catchTag("NoSuchElementException", () => Ref.set(todos, Chunk.empty())),
|
|
||||||
|
|
||||||
Effect.provide(BrowserKeyValueStore.layerLocalStorage),
|
Effect.provide(BrowserKeyValueStore.layerLocalStorage),
|
||||||
)
|
)
|
||||||
|
|
||||||
const saveToLocalStorage = Effect.all([KeyValueStore.KeyValueStore, todos]).pipe(
|
const writeToLocalStorage = (values: Chunk.Chunk<Todo.Todo>) => KeyValueStore.KeyValueStore.pipe(
|
||||||
Effect.flatMap(([kv, values]) => values.pipe(
|
Effect.flatMap(kv => values.pipe(
|
||||||
Schema.parseJson().pipe(
|
Schema.encode(
|
||||||
Schema.compose(Schema.Chunk(Todo.TodoFromJson)),
|
Schema.compose(Schema.parseJson(), Schema.Chunk(Todo.TodoFromJson))
|
||||||
Schema.encode,
|
|
||||||
),
|
),
|
||||||
Effect.flatMap(v => kv.set(key, v)),
|
Effect.flatMap(v => kv.set(key, v)),
|
||||||
)),
|
)),
|
||||||
|
|
||||||
Effect.provide(BrowserKeyValueStore.layerLocalStorage),
|
Effect.provide(BrowserKeyValueStore.layerLocalStorage),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const todos = yield* SubscriptionRef.make(yield* readFromLocalStorage)
|
||||||
|
|
||||||
|
const load = Effect.flatMap(readFromLocalStorage, v => Ref.set(todos, v))
|
||||||
|
const save = Effect.flatMap(todos, writeToLocalStorage)
|
||||||
|
|
||||||
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 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))
|
||||||
@@ -56,12 +53,16 @@ export const make = (key: string) => Layer.effect(TodosState, Effect.gen(functio
|
|||||||
|
|
||||||
// })
|
// })
|
||||||
|
|
||||||
yield* readFromLocalStorage
|
// Sync changes with local storage
|
||||||
|
yield* Effect.forkScoped(todos.changes.pipe(
|
||||||
|
Stream.debounce("500 millis"),
|
||||||
|
Stream.runForEach(writeToLocalStorage),
|
||||||
|
))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
todos,
|
todos,
|
||||||
readFromLocalStorage,
|
load,
|
||||||
saveToLocalStorage,
|
save,
|
||||||
prepend,
|
prepend,
|
||||||
replace,
|
replace,
|
||||||
remove,
|
remove,
|
||||||
|
|||||||
@@ -1,25 +1,27 @@
|
|||||||
import { Todo } from "@/domain"
|
import { Todo } from "@/domain"
|
||||||
import { Box, Button, Card, Flex, TextArea } from "@radix-ui/themes"
|
import { Box, Button, Card, Flex, TextArea } from "@radix-ui/themes"
|
||||||
import { Effect, Option, SubscriptionRef } from "effect"
|
import { Effect, Option, Ref } from "effect"
|
||||||
import { R } from "../reffuse"
|
import { R } from "../reffuse"
|
||||||
import { TodosState } from "../services"
|
import { TodosState } from "../services"
|
||||||
|
|
||||||
|
|
||||||
const createEmptyTodo = Todo.generateUniqueID.pipe(
|
const createEmptyTodo = Effect.map(Todo.generateUniqueID, id => Todo.Todo.make({
|
||||||
Effect.map(id => Todo.Todo.make({
|
id,
|
||||||
id,
|
content: "",
|
||||||
content: "",
|
completedAt: Option.none(),
|
||||||
completedAt: Option.none(),
|
}, true))
|
||||||
}, true))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
export function VNewTodo() {
|
export function VNewTodo() {
|
||||||
|
|
||||||
const runSync = R.useRunSync()
|
const todoRef = R.useRef(() => createEmptyTodo)
|
||||||
|
const [content, setContent] = R.useRefState(R.useSubRef(todoRef, ["content"]))
|
||||||
|
|
||||||
const todoRef = R.useMemo(() => createEmptyTodo.pipe(Effect.flatMap(SubscriptionRef.make)), [])
|
const add = R.useCallbackSync(() => Effect.all([TodosState.TodosState, todoRef]).pipe(
|
||||||
const [todo, setTodo] = R.useRefState(todoRef)
|
Effect.flatMap(([state, todo]) => state.prepend(todo)),
|
||||||
|
Effect.andThen(createEmptyTodo),
|
||||||
|
Effect.flatMap(v => Ref.set(todoRef, v)),
|
||||||
|
), [todoRef])
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -27,23 +29,12 @@ export function VNewTodo() {
|
|||||||
<Card>
|
<Card>
|
||||||
<Flex direction="column" align="stretch" gap="2">
|
<Flex direction="column" align="stretch" gap="2">
|
||||||
<TextArea
|
<TextArea
|
||||||
value={todo.content}
|
value={content}
|
||||||
onChange={e => setTodo(prev =>
|
onChange={e => setContent(e.target.value)}
|
||||||
Todo.Todo.make({ ...prev, content: e.target.value }, true)
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Flex direction="row" justify="center" align="center">
|
<Flex direction="row" justify="center" align="center">
|
||||||
<Button
|
<Button onClick={add}>Add</Button>
|
||||||
onClick={() => TodosState.TodosState.pipe(
|
|
||||||
Effect.flatMap(state => state.prepend(todo)),
|
|
||||||
Effect.andThen(createEmptyTodo),
|
|
||||||
Effect.map(setTodo),
|
|
||||||
runSync,
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
Add
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Box, Flex } from "@radix-ui/themes"
|
import { Box, Flex } from "@radix-ui/themes"
|
||||||
import { Chunk, Effect, Stream } from "effect"
|
import { Chunk, Effect } from "effect"
|
||||||
import { R } from "../reffuse"
|
import { R } from "../reffuse"
|
||||||
import { TodosState } from "../services"
|
import { TodosState } from "../services"
|
||||||
import { VNewTodo } from "./VNewTodo"
|
import { VNewTodo } from "./VNewTodo"
|
||||||
@@ -8,13 +8,6 @@ import { VTodo } from "./VTodo"
|
|||||||
|
|
||||||
export function VTodos() {
|
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 todosRef = R.useMemo(() => TodosState.TodosState.pipe(Effect.map(state => state.todos)), [])
|
||||||
const [todos] = R.useSubscribeRefs(todosRef)
|
const [todos] = R.useSubscribeRefs(todosRef)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user