0.2.1 #26

Merged
Thilawyn merged 144 commits from next into master 2025-12-01 23:37:40 +01:00
5 changed files with 25 additions and 34 deletions
Showing only changes of commit 6f96608f64 - Show all commits

View File

@@ -105,7 +105,7 @@ export const make: {
export const run = <A, I, R, SA, SE, SR>( export const run = <A, I, R, SA, SE, SR>(
self: Form<A, I, R, SA, SE, SR> self: Form<A, I, R, SA, SE, SR>
): Effect.Effect<void, never, Scope.Scope | R> => Stream.runForEach( ): Effect.Effect<void, never, Scope.Scope | R | SR> => Stream.runForEach(
self.encodedValueRef.changes.pipe( self.encodedValueRef.changes.pipe(
Option.isSome(self.debounce) ? Stream.debounce(self.debounce.value) : identity Option.isSome(self.debounce) ? Stream.debounce(self.debounce.value) : identity
), ),
@@ -125,21 +125,22 @@ export const run = <A, I, R, SA, SE, SR>(
Effect.andThen(SubscriptionRef.set(self.errorRef, Option.none())), Effect.andThen(SubscriptionRef.set(self.errorRef, Option.none())),
Effect.as(Option.some(v)), Effect.as(Option.some(v)),
), ),
onFailure: c => Option.match( onFailure: c => Chunk.findFirst(Cause.failures(c), e => e._tag === "ParseError").pipe(
Chunk.findFirst(Cause.failures(c), e => e._tag === "ParseError"), Option.match({
{ onSome: e => SubscriptionRef.set(self.errorRef, Option.some(e)),
onSome: e => Effect.as(SubscriptionRef.set(self.errorRef, Option.some(e)), Option.none()), onNone: () => Effect.void,
onNone: () => Effect.succeed(Option.none()), }),
}, Effect.as(Option.none<A>()),
), ),
}), }),
Effect.uninterruptible, Effect.uninterruptible,
)), )),
Effect.scoped,
Effect.andThen(value => Option.isSome(value) && self.autosubmit Effect.andThen(value => Option.isSome(value) && self.autosubmit
? ? Effect.asVoid(Effect.forkScoped(submit(self)))
: Effect.void : Effect.void
), ),
Effect.scoped,
Effect.forkScoped, Effect.forkScoped,
) )
), ),
@@ -175,7 +176,7 @@ export namespace service {
export const service = <A, I = A, R = never, SA = void, SE = A, SR = never>( export const service = <A, I = A, R = never, SA = void, SE = A, SR = never>(
options: service.Options<A, I, R, SA, SE, SR> options: service.Options<A, I, R, SA, SE, SR>
): Effect.Effect<Form<A, I, R, SA, SE, SR>, never, Scope.Scope | R> => Effect.tap( ): Effect.Effect<Form<A, I, R, SA, SE, SR>, never, Scope.Scope | R | SR> => Effect.tap(
make(options), make(options),
form => Effect.forkScoped(run(form)), form => Effect.forkScoped(run(form)),
) )

View File

@@ -2,7 +2,7 @@ import { Flex, Text, TextField } from "@radix-ui/themes"
import { createFileRoute } from "@tanstack/react-router" import { createFileRoute } from "@tanstack/react-router"
import { GetRandomValues, makeUuid4 } from "@typed/id" import { GetRandomValues, makeUuid4 } from "@typed/id"
import { Effect } from "effect" import { Effect } from "effect"
import { Async, Component, Hooks, Memoized } from "effect-fc" import { Async, Component, Memoized } from "effect-fc"
import * as React from "react" import * as React from "react"
import { runtime } from "@/runtime" import { runtime } from "@/runtime"
@@ -69,7 +69,7 @@ class AsyncComponent extends Component.makeUntraced("AsyncComponent")(function*(
class MemoizedAsyncComponent extends Memoized.memoized(AsyncComponent) {} class MemoizedAsyncComponent extends Memoized.memoized(AsyncComponent) {}
class SubComponent extends Component.makeUntraced("SubComponent")(function*() { class SubComponent extends Component.makeUntraced("SubComponent")(function*() {
const [state] = React.useState(yield* Hooks.useOnce(() => Effect.provide(makeUuid4, GetRandomValues.CryptoRandom))) const [state] = React.useState(yield* Component.useOnMount(() => Effect.provide(makeUuid4, GetRandomValues.CryptoRandom)))
return <Text>{state}</Text> return <Text>{state}</Text>
}) {} }) {}

View File

@@ -1,7 +1,7 @@
import { Button, Container, Flex } from "@radix-ui/themes" import { Button, Container, Flex } from "@radix-ui/themes"
import { createFileRoute } from "@tanstack/react-router" import { createFileRoute } from "@tanstack/react-router"
import { Console, Effect, Option, ParseResult, Schema } from "effect" import { Console, Effect, Option, ParseResult, Schema } from "effect"
import { Component, Form, Hooks } from "effect-fc" import { Component, Form, Subscribable } from "effect-fc"
import { TextFieldFormInput } from "@/lib/form/TextFieldFormInput" import { TextFieldFormInput } from "@/lib/form/TextFieldFormInput"
import { DateTimeUtcFromZonedInput } from "@/lib/schema" import { DateTimeUtcFromZonedInput } from "@/lib/schema"
import { runtime } from "@/runtime" import { runtime } from "@/runtime"
@@ -50,7 +50,7 @@ class RegisterForm extends Effect.Service<RegisterForm>()("RegisterForm", {
class RegisterFormView extends Component.makeUntraced("RegisterFormView")(function*() { class RegisterFormView extends Component.makeUntraced("RegisterFormView")(function*() {
const form = yield* RegisterForm const form = yield* RegisterForm
const submit = yield* Form.useSubmit(form) const submit = yield* Form.useSubmit(form)
const [canSubmit] = yield* Hooks.useSubscribables(form.canSubmitSubscribable) const [canSubmit] = yield* Subscribable.useSubscribables(form.canSubmitSubscribable)
const TextFieldFormInputFC = yield* TextFieldFormInput const TextFieldFormInputFC = yield* TextFieldFormInput
@@ -87,7 +87,7 @@ class RegisterFormView extends Component.makeUntraced("RegisterFormView")(functi
const RegisterPage = Component.makeUntraced("RegisterPage")(function*() { const RegisterPage = Component.makeUntraced("RegisterPage")(function*() {
const RegisterFormViewFC = yield* Effect.provide( const RegisterFormViewFC = yield* Effect.provide(
RegisterFormView, RegisterFormView,
yield* Hooks.useContext(RegisterForm.Default, { finalizerExecutionMode: "fork" }), yield* Component.useContext(RegisterForm.Default, { finalizerExecutionMode: "fork" }),
) )
return <RegisterFormViewFC /> return <RegisterFormViewFC />

View File

@@ -1,6 +1,6 @@
import { createFileRoute } from "@tanstack/react-router" import { createFileRoute } from "@tanstack/react-router"
import { Effect } from "effect" import { Effect } from "effect"
import { Component, Hooks } from "effect-fc" import { Component } from "effect-fc"
import { runtime } from "@/runtime" import { runtime } from "@/runtime"
import { Todos } from "@/todo/Todos" import { Todos } from "@/todo/Todos"
import { TodosState } from "@/todo/TodosState.service" import { TodosState } from "@/todo/TodosState.service"
@@ -11,7 +11,7 @@ const TodosStateLive = TodosState.Default("todos")
const Index = Component.makeUntraced("Index")(function*() { const Index = Component.makeUntraced("Index")(function*() {
const TodosFC = yield* Effect.provide( const TodosFC = yield* Effect.provide(
Todos, Todos,
yield* Hooks.useContext(TodosStateLive, { finalizerExecutionMode: "fork" }), yield* Component.useContext(TodosStateLive, { finalizerExecutionMode: "fork" }),
) )
return <TodosFC /> return <TodosFC />

View File

@@ -1,7 +1,7 @@
import { Box, Button, Flex, IconButton } from "@radix-ui/themes" import { Box, Button, Flex, IconButton } from "@radix-ui/themes"
import { GetRandomValues, makeUuid4 } from "@typed/id" import { GetRandomValues, makeUuid4 } from "@typed/id"
import { Chunk, DateTime, Effect, Match, Option, Ref, Runtime, Schema, Stream, SubscriptionRef } from "effect" import { Chunk, Effect, Match, Option, Ref, Runtime, Schema, Stream } from "effect"
import { Component, Form, Hooks, Memoized, Subscribable, SubscriptionSubRef } from "effect-fc" import { Component, Form, Subscribable } from "effect-fc"
import { FaArrowDown, FaArrowUp } from "react-icons/fa" import { FaArrowDown, FaArrowUp } from "react-icons/fa"
import { FaDeleteLeft } from "react-icons/fa6" import { FaDeleteLeft } from "react-icons/fa6"
import * as Domain from "@/domain" import * as Domain from "@/domain"
@@ -61,6 +61,7 @@ export class Todo extends Component.makeUntraced("Todo")(function*(props: TodoPr
Match.exhaustive, Match.exhaustive,
) )
}, },
autosubmit: props._tag === "edit",
}) })
return [ return [
@@ -71,7 +72,7 @@ export class Todo extends Component.makeUntraced("Todo")(function*(props: TodoPr
] as const ] as const
}), [props._tag, props._tag === "edit" ? props.id : undefined]) }), [props._tag, props._tag === "edit" ? props.id : undefined])
const [index, size] = yield* Hooks.useSubscribables(indexRef, state.sizeSubscribable) const [index, size] = yield* Subscribable.useSubscribables(indexRef, state.sizeSubscribable)
const submit = yield* Form.useSubmit(form) const submit = yield* Form.useSubmit(form)
const TextFieldFormInputFC = yield* TextFieldFormInput const TextFieldFormInputFC = yield* TextFieldFormInput
@@ -80,9 +81,7 @@ export class Todo extends Component.makeUntraced("Todo")(function*(props: TodoPr
<Flex direction="row" align="center" gap="2"> <Flex direction="row" align="center" gap="2">
<Box flexGrow="1"> <Box flexGrow="1">
<Flex direction="column" align="stretch" gap="2"> <Flex direction="column" align="stretch" gap="2">
<TextFieldFormInputFC <TextFieldFormInputFC field={contentField} />
field={contentField}
/>
<Flex direction="row" justify="center" align="center" gap="2"> <Flex direction="row" justify="center" align="center" gap="2">
<TextFieldFormInputFC <TextFieldFormInputFC
@@ -93,14 +92,7 @@ export class Todo extends Component.makeUntraced("Todo")(function*(props: TodoPr
/> />
{props._tag === "new" && {props._tag === "new" &&
<Button <Button onClick={() => submit()}>
onClick={() => ref.pipe(
Effect.andThen(todo => Ref.update(state.ref, Chunk.prepend(todo))),
Effect.andThen(makeTodo),
Effect.andThen(todo => Ref.set(ref, todo)),
Runtime.runSync(runtime),
)}
>
Add Add
</Button> </Button>
} }
@@ -131,6 +123,4 @@ export class Todo extends Component.makeUntraced("Todo")(function*(props: TodoPr
} }
</Flex> </Flex>
) )
}).pipe( }) {}
Memoized.memoized
) {}