diff --git a/src/main/scala/Main.scala b/src/main/scala/Main.scala index 544d900..36aebf1 100644 --- a/src/main/scala/Main.scala +++ b/src/main/scala/Main.scala @@ -1,3 +1,4 @@ +import cats.data.* import cats.effect.* import cats.effect.syntax.all.* import cats.syntax.all.* @@ -9,64 +10,92 @@ import calico.html.io.{*, given} import calico.syntax.* import snow.* +object Store { + def apply(window: Window[IO]): Resource[IO, Store] = { + val key = "nak-input" + + for { + inputRef <- SignallingRef[IO].of("").toResource + + _ <- Resource.eval { + OptionT(window.localStorage.getItem(key)) + .foreachF(inputRef.set(_)) + } + + _ <- window.localStorage + .events(window) + .foreach { + case Storage.Event.Updated(`key`, _, value, _) => + inputRef.set(value) + case _ => IO.unit + } + .compile + .drain + .background + + _ <- inputRef.discrete + .foreach(input => IO.cede *> window.localStorage.setItem(key, input)) + .compile + .drain + .background + } yield new Store(inputRef) + } +} + +case class Store(input: SignallingRef[IO, String]) + object Main extends IOWebApp { - def render: Resource[IO, HtmlDivElement[IO]] = { - div( - cls := "flex w-full h-full flex-col items-center justify-center", - h1(cls := "px-1 py-2 text-lg", "nostr army knife"), + def render: Resource[IO, HtmlDivElement[IO]] = Store(window).flatMap { + store => div( - cls := "flex justify-center my-3" - // button(cls := buttonCls, "generate event", onClick --> (_.foreach(_ => ))), - ), - input() - ) + cls := "flex w-full h-full flex-col items-center justify-center", + h1(cls := "px-1 py-2 text-lg", "nostr army knife"), + div( + cls := "flex justify-center my-3" + // button(cls := buttonCls, "generate event", onClick --> (_.foreach(_ => ))), + ), + input(store), + result(store) + ) } - def input(): Resource[IO, HtmlDivElement[IO]] = - SignallingRef[IO] - .of("") - .toResource - .flatMap { input => - div( - cls := "w-full", - div( - cls := "w-full flex justify-center", - textArea.withSelf { self => - ( - cls := "w-2/3 max-h-96 p-3", - styleAttr := "min-height: 200px", - placeholder := "paste something nostric", - onInput --> (_.foreach(_ => self.value.get.flatMap(input.set))), - value <-- input - ) - } + def input(store: Store): Resource[IO, HtmlDivElement[IO]] = + div( + cls := "w-full", + div( + cls := "w-full flex justify-center", + textArea.withSelf { self => + ( + cls := "w-2/3 max-h-96 p-3", + styleAttr := "min-height: 200px", + placeholder := "paste something nostric", + onInput --> (_.foreach(_ => + self.value.get.flatMap(store.input.set) + )), + value <-- store.input ) - ) - } + } + ) + ) - def result(): Resource[IO, HtmlDivElement[IO]] = - SignallingRef[IO] - .of("") - .toResource - .flatMap { input => - div( - cls := "w-full flex justify-center", - input.map(inp => - if inp.trim() == "" then "" - else - decode[Event](inp) match { - case Right(event) => event.toString - case Left(err: io.circe.DecodingFailure) => - err.pathToRootString match { - case Some(path) => s"field $path is missing or wrong" - case None => s"decoding ${err.pathToRootString}" - } - case Left(err: io.circe.ParsingFailure) => - "not valid JSON" + def result(store: Store): Resource[IO, HtmlDivElement[IO]] = + div( + cls := "w-full flex justify-center", + store.input.map { input => + if input.trim() == "" then "" + else + decode[Event](input) match { + case Right(event) => event.toString + case Left(err: io.circe.DecodingFailure) => + err.pathToRootString match { + case Some(path) => s"field $path is missing or wrong" + case None => s"decoding ${err.pathToRootString}" } - ) - ) + case Left(err: io.circe.ParsingFailure) => + "not valid JSON" + } } + ) val buttonCls = "bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 mx-2 px-4 rounded"