import * as Domain from "@/domain" import { DateTimeUtcFromZonedInput } from "@/lib/schema" import { TextAreaInput } from "@/lib/TextAreaInput" import { TextFieldInput } from "@/lib/TextFieldInput" import { Box, Button, Flex, IconButton } from "@radix-ui/themes" import { GetRandomValues, makeUuid4 } from "@typed/id" import { Chunk, Effect, Match, Option, Ref, Runtime, Schema, SubscriptionRef } from "effect" import { Component, Memo } from "effect-fc" import { Hooks } from "effect-fc/hooks" import { SubscriptionSubRef } from "effect-fc/types" import { FaArrowDown, FaArrowUp } from "react-icons/fa" import { FaDeleteLeft } from "react-icons/fa6" import { TodosState } from "./TodosState.service" const StringTextAreaInput = TextAreaInput({ schema: Schema.String }) const OptionalDateInput = TextFieldInput({ optional: true, schema: DateTimeUtcFromZonedInput }) const makeTodo = makeUuid4.pipe( Effect.map(id => Domain.Todo.Todo.make({ id, content: "", completedAt: Option.none(), })), Effect.provide(GetRandomValues.CryptoRandom), ) export type TodoProps = ( | { readonly _tag: "new", readonly index?: never } | { readonly _tag: "edit", readonly index: number } ) export class Todo extends Component.makeUntraced(function* Todo(props: TodoProps) { const runtime = yield* Effect.runtime() const state = yield* TodosState const [ref, contentRef, completedAtRef] = yield* Hooks.useMemo(() => Match.value(props).pipe( Match.tag("new", () => Effect.andThen(makeTodo, SubscriptionRef.make)), Match.tag("edit", ({ index }) => Effect.succeed(SubscriptionSubRef.makeFromChunkRef(state.ref, index))), Match.exhaustive, Effect.map(ref => [ ref, SubscriptionSubRef.makeFromPath(ref, ["content"]), SubscriptionSubRef.makeFromPath(ref, ["completedAt"]), ] as const), ), [props._tag, props.index]) const [size] = yield* Hooks.useSubscribeRefs(state.sizeRef) const StringTextAreaInputFC = yield* StringTextAreaInput const OptionalDateInputFC = yield* OptionalDateInput return ( {props._tag === "new" && } {props._tag === "edit" && Runtime.runSync(runtime)( SubscriptionRef.updateEffect(state.ref, todos => Effect.gen(function*() { if (props.index <= 0) return yield* Option.none() return todos.pipe( Chunk.replace(props.index, yield* Chunk.get(todos, props.index - 1)), Chunk.replace(props.index - 1, yield* ref), ) })) )} > = size - 1} onClick={() => Runtime.runSync(runtime)( SubscriptionRef.updateEffect(state.ref, todos => Effect.gen(function*() { if (props.index >= size - 1) return yield* Option.none() return todos.pipe( Chunk.replace(props.index, yield* Chunk.get(todos, props.index + 1)), Chunk.replace(props.index + 1, yield* ref), ) })) )} > Runtime.runSync(runtime)( Ref.update(state.ref, Chunk.remove(props.index)) )} > } ) }).pipe( Memo.memo ) {}