2023-03-23 20:04:53 -04:00
|
|
|
import cats.data.*
|
2023-03-18 20:42:41 -04:00
|
|
|
import cats.effect.*
|
2023-03-23 17:28:20 -04:00
|
|
|
import cats.effect.syntax.all.*
|
|
|
|
import cats.syntax.all.*
|
|
|
|
import fs2.concurrent.*
|
|
|
|
import fs2.dom.{Event => _, *}
|
|
|
|
import io.circe.parser.*
|
2023-03-23 21:36:00 -04:00
|
|
|
import io.circe.syntax.*
|
2023-03-18 19:41:54 -04:00
|
|
|
import calico.*
|
|
|
|
import calico.html.io.{*, given}
|
|
|
|
import calico.syntax.*
|
2023-03-23 21:36:00 -04:00
|
|
|
import scoin.*
|
2023-03-24 06:25:31 -04:00
|
|
|
import snow.*
|
|
|
|
|
|
|
|
import Utils.*
|
2023-03-18 17:07:07 -04:00
|
|
|
|
2023-03-23 20:04:53 -04:00
|
|
|
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])
|
|
|
|
|
2023-03-18 19:41:54 -04:00
|
|
|
object Main extends IOWebApp {
|
2023-03-23 20:04:53 -04:00
|
|
|
def render: Resource[IO, HtmlDivElement[IO]] = Store(window).flatMap {
|
|
|
|
store =>
|
2023-03-23 17:28:20 -04:00
|
|
|
div(
|
2023-03-23 20:04:53 -04:00
|
|
|
cls := "flex w-full h-full flex-col items-center justify-center",
|
|
|
|
div(
|
2023-03-23 21:36:00 -04:00
|
|
|
cls := "w-4/5",
|
|
|
|
h1(cls := "px-1 py-2 text-center text-xl", "nostr army knife"),
|
|
|
|
div(
|
2023-03-24 06:25:31 -04:00
|
|
|
cls := "flex my-3",
|
2023-03-23 21:36:00 -04:00
|
|
|
input(store),
|
2023-03-24 06:25:31 -04:00
|
|
|
actions(store)
|
2023-03-23 21:36:00 -04:00
|
|
|
),
|
|
|
|
result(store)
|
|
|
|
)
|
2023-03-23 20:04:53 -04:00
|
|
|
)
|
2023-03-23 17:28:20 -04:00
|
|
|
}
|
|
|
|
|
2023-03-24 06:25:31 -04:00
|
|
|
def actions(store: Store): Resource[IO, HtmlDivElement[IO]] =
|
|
|
|
div(
|
|
|
|
cls := "flex flex-col space-y-1 my-3",
|
|
|
|
button(
|
|
|
|
Styles.button,
|
|
|
|
"format",
|
|
|
|
onClick --> (_.foreach(_ =>
|
|
|
|
store.input.update(original =>
|
|
|
|
parse(original).toOption
|
|
|
|
.map(_.printWith(jsonPrinter))
|
|
|
|
.getOrElse(original)
|
|
|
|
)
|
|
|
|
))
|
|
|
|
),
|
|
|
|
button(
|
|
|
|
Styles.button,
|
|
|
|
"generate event",
|
|
|
|
onClick --> (_.foreach(_ =>
|
|
|
|
store.input.set(
|
|
|
|
Event(
|
|
|
|
kind = 1,
|
|
|
|
content = "hello world"
|
|
|
|
).sign(PrivateKey(randomBytes32()))
|
|
|
|
.asJson
|
|
|
|
.printWith(jsonPrinter)
|
|
|
|
)
|
|
|
|
))
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
2023-03-23 20:04:53 -04:00
|
|
|
def input(store: Store): Resource[IO, HtmlDivElement[IO]] =
|
|
|
|
div(
|
2023-03-23 21:36:00 -04:00
|
|
|
cls := "w-full grow",
|
2023-03-23 20:04:53 -04:00
|
|
|
div(
|
|
|
|
cls := "w-full flex justify-center",
|
|
|
|
textArea.withSelf { self =>
|
|
|
|
(
|
2023-03-23 21:36:00 -04:00
|
|
|
cls := "w-full max-h-96 p-3 rounded",
|
2023-03-24 06:25:31 -04:00
|
|
|
styleAttr := "min-height: 280px; font-family: monospace",
|
2023-03-23 20:04:53 -04:00
|
|
|
placeholder := "paste something nostric",
|
|
|
|
onInput --> (_.foreach(_ =>
|
|
|
|
self.value.get.flatMap(store.input.set)
|
|
|
|
)),
|
|
|
|
value <-- store.input
|
2023-03-23 17:28:20 -04:00
|
|
|
)
|
2023-03-23 20:04:53 -04:00
|
|
|
}
|
|
|
|
)
|
|
|
|
)
|
2023-03-23 17:28:20 -04:00
|
|
|
|
2023-03-23 20:04:53 -04:00
|
|
|
def result(store: Store): Resource[IO, HtmlDivElement[IO]] =
|
|
|
|
div(
|
2023-03-23 21:36:00 -04:00
|
|
|
cls := "w-full flex my-5",
|
2023-03-23 20:04:53 -04:00
|
|
|
store.input.map { input =>
|
2023-03-23 21:36:00 -04:00
|
|
|
if input.trim() == "" then div("")
|
2023-03-23 20:04:53 -04:00
|
|
|
else
|
|
|
|
decode[Event](input) match {
|
2023-03-23 21:36:00 -04:00
|
|
|
case Left(err: io.circe.ParsingFailure) =>
|
|
|
|
div("not valid JSON")
|
2023-03-23 20:04:53 -04:00
|
|
|
case Left(err: io.circe.DecodingFailure) =>
|
|
|
|
err.pathToRootString match {
|
2023-03-23 21:36:00 -04:00
|
|
|
case None => div(s"decoding ${err.pathToRootString}")
|
|
|
|
case Some(path) => div(s"field $path is missing or wrong")
|
2023-03-23 17:28:20 -04:00
|
|
|
}
|
2023-03-23 21:36:00 -04:00
|
|
|
case Right(event) =>
|
|
|
|
div(
|
|
|
|
cls := "text-md",
|
|
|
|
div(
|
|
|
|
span(cls := "font-bold", "serialized event "),
|
2023-03-24 06:25:31 -04:00
|
|
|
span(Styles.mono, event.serialized)
|
2023-03-23 21:36:00 -04:00
|
|
|
),
|
|
|
|
div(
|
|
|
|
span(cls := "font-bold", "implied event id "),
|
2023-03-24 06:25:31 -04:00
|
|
|
span(Styles.mono, event.hash.toHex)
|
2023-03-23 21:36:00 -04:00
|
|
|
),
|
|
|
|
div(
|
|
|
|
span(
|
|
|
|
cls := "font-bold",
|
|
|
|
"does the implied event id match the given event id? "
|
|
|
|
),
|
2023-03-24 06:25:31 -04:00
|
|
|
span(
|
|
|
|
Styles.mono,
|
|
|
|
event.id == event.hash.toHex match {
|
|
|
|
case true => "yes"; case false => "no"
|
|
|
|
}
|
|
|
|
)
|
2023-03-23 21:36:00 -04:00
|
|
|
),
|
|
|
|
div(
|
|
|
|
span(cls := "font-bold", "is signature valid? "),
|
2023-03-24 06:25:31 -04:00
|
|
|
span(
|
|
|
|
Styles.mono,
|
|
|
|
event.isValid match {
|
|
|
|
case true => "yes"; case false => "no"
|
|
|
|
}
|
|
|
|
)
|
2023-03-23 21:36:00 -04:00
|
|
|
)
|
|
|
|
)
|
2023-03-23 20:04:53 -04:00
|
|
|
}
|
2023-03-23 21:36:00 -04:00
|
|
|
|
2023-03-23 17:28:20 -04:00
|
|
|
}
|
2023-03-23 20:04:53 -04:00
|
|
|
)
|
2023-03-18 19:41:54 -04:00
|
|
|
}
|