diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala deleted file mode 100644 index e7e6627..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.akka - -// Base class for all command-processing related exceptions from handlers -sealed abstract class CommandHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandNotAvailable[C, S](cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd není dostupný ve stavu $state" - ) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandRejected[C, S](reason: String, cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd byl ve stavu $state odmítnut s odůvodněním $reason" - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala deleted file mode 100644 index e7e6627..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.akka - -// Base class for all command-processing related exceptions from handlers -sealed abstract class CommandHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandNotAvailable[C, S](cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd není dostupný ve stavu $state" - ) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandRejected[C, S](reason: String, cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd byl ve stavu $state odmítnut s odůvodněním $reason" - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala deleted file mode 100644 index 72a5bf9..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.akka - -sealed abstract class EventHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -case class UnhandledEvent[Event, State](event: Event, state: State) - extends EventHandlerException( - s"Událost $event nastala ve stavu $state bez možnosti zpracování" - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala deleted file mode 100644 index e7e6627..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.akka - -// Base class for all command-processing related exceptions from handlers -sealed abstract class CommandHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandNotAvailable[C, S](cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd není dostupný ve stavu $state" - ) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandRejected[C, S](reason: String, cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd byl ve stavu $state odmítnut s odůvodněním $reason" - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala deleted file mode 100644 index 72a5bf9..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.akka - -sealed abstract class EventHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -case class UnhandledEvent[Event, State](event: Event, state: State) - extends EventHandlerException( - s"Událost $event nastala ve stavu $state bez možnosti zpracování" - ) diff --git a/iw/bom.sc b/iw/bom.sc deleted file mode 100644 index e9266b8..0000000 --- a/iw/bom.sc +++ /dev/null @@ -1,227 +0,0 @@ -import mill._, scalalib._ - -object IWMaterials { - - object Versions { - val akka = "2.6.16" - val akkaHttp = "10.2.4" - val cats = "2.7.0" - val elastic4s = "7.12.2" - val http4s = "0.23.10" - val http4sPac4J = "4.0.0" - val laminar = "0.14.2" - val laminext = laminar - val logbackClassic = "1.2.10" - val pac4j = "5.2.0" - val play = "2.8.8" - val playJson = "2.9.2" - val scalaTest = "3.2.9" - val slick = "3.3.3" - val sttpClient = "3.5.0" - val tapir = "0.20.1" - val urlDsl = "0.4.0" - val waypoint = "0.5.0" - val zio = "2.0.0-RC2" - val zioConfig = "3.0.0-RC2" - val zioInteropCats = "3.3.0-RC2" - val zioJson = "0.3.0-RC3" - val zioLogging = "2.0.0-RC5" - val zioPrelude = "1.0.0-RC10" - val zioZMX = "0.0.11" - } - - object Deps extends AkkaLibs with SlickLibs { - import IWMaterials.{Versions => V} - - val zioOrg = "dev.zio" - - def zioLib(name: String, version: String): Dep = - ivy"$zioOrg::zio-$name::$version" - - lazy val zio: Dep = ivy"$zioOrg::zio:${V.zio}" - - lazy val zioTest: Dep = zioLib("test", V.zio) - lazy val zioTestSbt: Dep = zioLib("test", V.zio) - - lazy val zioConfig: Dep = zioLib("config", V.zioConfig) - lazy val zioConfigTypesafe: Dep = - zioLib("config-typesafe", V.zioConfig) - lazy val zioConfigMagnolia: Dep = - zioLib("config-magnolia", V.zioConfig) - - lazy val zioJson: Dep = zioLib("json", V.zioJson) - lazy val zioLogging: Dep = zioLib("logging", V.zioLogging) - lazy val zioLoggingSlf4j: Dep = - zioLib("logging-slf4j", V.zioLogging) - lazy val zioPrelude: Dep = zioLib("prelude", V.zioPrelude) - lazy val zioStreams: Dep = zioLib("streams", V.zio) - lazy val zioZMX: Dep = zioLib("zmx", V.zioZMX) - lazy val zioInteropCats: Dep = - zioLib("interop-cats", V.zioInteropCats) - - /* What is the equivalent? ZIOModule with prepared test config? - def useZIO(testConf: Configuration*): Agg[Dep] = Agg( - zio, - zioTest, - zioTestSbt, - testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework") - ) - */ - - /* - def useZIOAll(testConf: Configuration*): Seq[Def.Setting[_]] = - useZIO(testConf: _*) ++ Seq( - zioStreams, - zioConfig, - zioConfigTypesafe, - zioConfigMagnolia, - zioJson, - zioZMX, - zioLogging, - zioPrelude - ) - */ - - private val tapirOrg = "com.softwaremill.sttp.tapir" - def tapirLib(name: String): Dep = - ivy"${tapirOrg}::tapir-$name::${V.tapir}" - - lazy val tapirCore: Dep = tapirLib("core") - lazy val tapirZIO: Dep = tapirLib("zio") - lazy val tapirZIOJson: Dep = tapirLib("json-zio") - lazy val tapirSttpClient: Dep = tapirLib("sttp-client") - lazy val tapirCats: Dep = tapirLib("cats") - lazy val tapirZIOHttp4sServer: Dep = tapirLib("zio-http4s-server") - - private val sttpClientOrg = "com.softwaremill.sttp.client3" - def sttpClientLib(name: String): Dep = - ivy"${sttpClientOrg}::${name}:${V.sttpClient}" - - lazy val sttpClientCore: Dep = sttpClientLib("core") - - lazy val http4sBlazeServer: Dep = - ivy"org.http4s::http4s-blaze-server:${V.http4s}" - - lazy val http4sPac4J: Dep = - ivy"org.pac4j::http4s-pac4j:${V.http4sPac4J}" - lazy val pac4jOIDC: Dep = - ivy"org.pac4j:pac4j-oidc:${V.pac4j}" - - lazy val scalaTest: Dep = - ivy"org.scalatest::scalatest:${V.scalaTest}" - lazy val scalaTestPlusScalacheck: Dep = - ivy"org.scalatestplus::scalacheck-1-15:3.2.9.0" - lazy val playScalaTest: Dep = - ivy"org.scalatestplus.play::scalatestplus-play:5.1.0" - - private val playOrg = "com.typesafe.play" - lazy val playMailer: Dep = ivy"${playOrg}::play-mailer:8.0.1" - lazy val playServer: Dep = ivy"${playOrg}::play-server:${V.play}" - lazy val playAkkaServer: Dep = - ivy"${playOrg}::play-akka-http-server:${V.play}" - lazy val play: Dep = ivy"${playOrg}::play:${V.play}" - lazy val playAhcWs: Dep = ivy"${playOrg}::play-ahc-ws:${V.play}" - lazy val playJson: Dep = ivy"${playOrg}::play-json:${V.playJson}" - - private val elastic4sOrg = "com.sksamuel.elastic4s" - lazy val useElastic4S: Agg[Dep] = Agg( - ivy"${elastic4sOrg}::elastic4s-core:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-client-akka:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-http-streams:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-json-play:${V.elastic4s}" - ) - - lazy val laminar: Dep = ivy"com.raquo::laminar::${V.laminar}" - - private def laminext(name: String): Dep = - ivy"io.laminext::$name::${V.laminar}" - - lazy val laminextCore: Dep = laminext("core") - lazy val laminextTailwind: Dep = laminext("tailwind") - lazy val laminextFetch: Dep = laminext("fetch") - lazy val laminextValidationCore: Dep = laminext("validation-core") - lazy val laminextUI: Dep = laminext("ui") - - lazy val waypoint: Dep = - ivy"com.raquo::waypoint::${V.waypoint}" - - lazy val urlDsl: Dep = - ivy"be.doeraene::url-dsl::${V.urlDsl}" - - lazy val scalaJavaTime: Dep = - ivy"io.github.cquiroz::scala-java-time::2.3.0" - - lazy val scalaJavaLocales: Dep = - ivy"io.github.cquiroz::scala-java-locales::1.2.1" - - lazy val logbackClassic: Dep = - ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" - } - - trait AkkaLibs { - - self: SlickLibs => - - object akka { - val V = Versions.akka - val tOrg = "com.typesafe.akka" - val lOrg = "com.lightbend.akka" - - def akkaMod(name: String): Dep = - ivy"$tOrg::akka-$name:$V" - - lazy val actor: Dep = akkaMod("actor") - lazy val actorTyped: Dep = akkaMod("actor-typed") - lazy val stream: Dep = akkaMod("stream") - lazy val persistence: Dep = akkaMod("persistence-typed") - lazy val persistenceQuery: Dep = akkaMod("persistence-query") - lazy val persistenceJdbc: Dep = - ivy"$lOrg::akka-persistence-jdbc:5.0.4" - val persistenceTestKit: Dep = ivy"$tOrg::akka-persistence-testkit:$V" - - object http { - val V = Versions.akkaHttp - - lazy val http: Dep = ivy"$tOrg::akka-http:$V" - lazy val sprayJson: Dep = ivy"$tOrg::akka-http-spray-json:$V" - - } - - object projection { - val V = "1.2.2" - - lazy val core: Dep = - ivy"$lOrg::akka-projection-core:$V" - lazy val eventsourced: Dep = - ivy"$lOrg::akka-projection-eventsourced:$V" - lazy val slick: Dep = - ivy"$lOrg::akka-projection-slick:$V" - lazy val jdbc: Dep = - ivy"$lOrg::akka-projection-jdbc:$V" - } - - object profiles { - // TODO: deal with cross-version for Scala3 (for3Use2_13) - lazy val eventsourcedJdbcProjection: Agg[Dep] = Agg( - persistenceQuery, - projection.core, - projection.eventsourced, - projection.slick, - persistenceJdbc - ) ++ slick.default - } - } - } - - trait SlickLibs { - object slick { - val V = IWMaterials.Versions.slick - val org = "com.typesafe.slick" - - lazy val slick: Dep = ivy"$org::slick:$V" - lazy val hikaricp: Dep = ivy"$org::slick-hikaricp:$V" - lazy val default: Agg[Dep] = Agg(slick, hikaricp) - } - } - -} diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala deleted file mode 100644 index e7e6627..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.akka - -// Base class for all command-processing related exceptions from handlers -sealed abstract class CommandHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandNotAvailable[C, S](cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd není dostupný ve stavu $state" - ) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandRejected[C, S](reason: String, cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd byl ve stavu $state odmítnut s odůvodněním $reason" - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala deleted file mode 100644 index 72a5bf9..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.akka - -sealed abstract class EventHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -case class UnhandledEvent[Event, State](event: Event, state: State) - extends EventHandlerException( - s"Událost $event nastala ve stavu $state bez možnosti zpracování" - ) diff --git a/iw/bom.sc b/iw/bom.sc deleted file mode 100644 index e9266b8..0000000 --- a/iw/bom.sc +++ /dev/null @@ -1,227 +0,0 @@ -import mill._, scalalib._ - -object IWMaterials { - - object Versions { - val akka = "2.6.16" - val akkaHttp = "10.2.4" - val cats = "2.7.0" - val elastic4s = "7.12.2" - val http4s = "0.23.10" - val http4sPac4J = "4.0.0" - val laminar = "0.14.2" - val laminext = laminar - val logbackClassic = "1.2.10" - val pac4j = "5.2.0" - val play = "2.8.8" - val playJson = "2.9.2" - val scalaTest = "3.2.9" - val slick = "3.3.3" - val sttpClient = "3.5.0" - val tapir = "0.20.1" - val urlDsl = "0.4.0" - val waypoint = "0.5.0" - val zio = "2.0.0-RC2" - val zioConfig = "3.0.0-RC2" - val zioInteropCats = "3.3.0-RC2" - val zioJson = "0.3.0-RC3" - val zioLogging = "2.0.0-RC5" - val zioPrelude = "1.0.0-RC10" - val zioZMX = "0.0.11" - } - - object Deps extends AkkaLibs with SlickLibs { - import IWMaterials.{Versions => V} - - val zioOrg = "dev.zio" - - def zioLib(name: String, version: String): Dep = - ivy"$zioOrg::zio-$name::$version" - - lazy val zio: Dep = ivy"$zioOrg::zio:${V.zio}" - - lazy val zioTest: Dep = zioLib("test", V.zio) - lazy val zioTestSbt: Dep = zioLib("test", V.zio) - - lazy val zioConfig: Dep = zioLib("config", V.zioConfig) - lazy val zioConfigTypesafe: Dep = - zioLib("config-typesafe", V.zioConfig) - lazy val zioConfigMagnolia: Dep = - zioLib("config-magnolia", V.zioConfig) - - lazy val zioJson: Dep = zioLib("json", V.zioJson) - lazy val zioLogging: Dep = zioLib("logging", V.zioLogging) - lazy val zioLoggingSlf4j: Dep = - zioLib("logging-slf4j", V.zioLogging) - lazy val zioPrelude: Dep = zioLib("prelude", V.zioPrelude) - lazy val zioStreams: Dep = zioLib("streams", V.zio) - lazy val zioZMX: Dep = zioLib("zmx", V.zioZMX) - lazy val zioInteropCats: Dep = - zioLib("interop-cats", V.zioInteropCats) - - /* What is the equivalent? ZIOModule with prepared test config? - def useZIO(testConf: Configuration*): Agg[Dep] = Agg( - zio, - zioTest, - zioTestSbt, - testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework") - ) - */ - - /* - def useZIOAll(testConf: Configuration*): Seq[Def.Setting[_]] = - useZIO(testConf: _*) ++ Seq( - zioStreams, - zioConfig, - zioConfigTypesafe, - zioConfigMagnolia, - zioJson, - zioZMX, - zioLogging, - zioPrelude - ) - */ - - private val tapirOrg = "com.softwaremill.sttp.tapir" - def tapirLib(name: String): Dep = - ivy"${tapirOrg}::tapir-$name::${V.tapir}" - - lazy val tapirCore: Dep = tapirLib("core") - lazy val tapirZIO: Dep = tapirLib("zio") - lazy val tapirZIOJson: Dep = tapirLib("json-zio") - lazy val tapirSttpClient: Dep = tapirLib("sttp-client") - lazy val tapirCats: Dep = tapirLib("cats") - lazy val tapirZIOHttp4sServer: Dep = tapirLib("zio-http4s-server") - - private val sttpClientOrg = "com.softwaremill.sttp.client3" - def sttpClientLib(name: String): Dep = - ivy"${sttpClientOrg}::${name}:${V.sttpClient}" - - lazy val sttpClientCore: Dep = sttpClientLib("core") - - lazy val http4sBlazeServer: Dep = - ivy"org.http4s::http4s-blaze-server:${V.http4s}" - - lazy val http4sPac4J: Dep = - ivy"org.pac4j::http4s-pac4j:${V.http4sPac4J}" - lazy val pac4jOIDC: Dep = - ivy"org.pac4j:pac4j-oidc:${V.pac4j}" - - lazy val scalaTest: Dep = - ivy"org.scalatest::scalatest:${V.scalaTest}" - lazy val scalaTestPlusScalacheck: Dep = - ivy"org.scalatestplus::scalacheck-1-15:3.2.9.0" - lazy val playScalaTest: Dep = - ivy"org.scalatestplus.play::scalatestplus-play:5.1.0" - - private val playOrg = "com.typesafe.play" - lazy val playMailer: Dep = ivy"${playOrg}::play-mailer:8.0.1" - lazy val playServer: Dep = ivy"${playOrg}::play-server:${V.play}" - lazy val playAkkaServer: Dep = - ivy"${playOrg}::play-akka-http-server:${V.play}" - lazy val play: Dep = ivy"${playOrg}::play:${V.play}" - lazy val playAhcWs: Dep = ivy"${playOrg}::play-ahc-ws:${V.play}" - lazy val playJson: Dep = ivy"${playOrg}::play-json:${V.playJson}" - - private val elastic4sOrg = "com.sksamuel.elastic4s" - lazy val useElastic4S: Agg[Dep] = Agg( - ivy"${elastic4sOrg}::elastic4s-core:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-client-akka:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-http-streams:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-json-play:${V.elastic4s}" - ) - - lazy val laminar: Dep = ivy"com.raquo::laminar::${V.laminar}" - - private def laminext(name: String): Dep = - ivy"io.laminext::$name::${V.laminar}" - - lazy val laminextCore: Dep = laminext("core") - lazy val laminextTailwind: Dep = laminext("tailwind") - lazy val laminextFetch: Dep = laminext("fetch") - lazy val laminextValidationCore: Dep = laminext("validation-core") - lazy val laminextUI: Dep = laminext("ui") - - lazy val waypoint: Dep = - ivy"com.raquo::waypoint::${V.waypoint}" - - lazy val urlDsl: Dep = - ivy"be.doeraene::url-dsl::${V.urlDsl}" - - lazy val scalaJavaTime: Dep = - ivy"io.github.cquiroz::scala-java-time::2.3.0" - - lazy val scalaJavaLocales: Dep = - ivy"io.github.cquiroz::scala-java-locales::1.2.1" - - lazy val logbackClassic: Dep = - ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" - } - - trait AkkaLibs { - - self: SlickLibs => - - object akka { - val V = Versions.akka - val tOrg = "com.typesafe.akka" - val lOrg = "com.lightbend.akka" - - def akkaMod(name: String): Dep = - ivy"$tOrg::akka-$name:$V" - - lazy val actor: Dep = akkaMod("actor") - lazy val actorTyped: Dep = akkaMod("actor-typed") - lazy val stream: Dep = akkaMod("stream") - lazy val persistence: Dep = akkaMod("persistence-typed") - lazy val persistenceQuery: Dep = akkaMod("persistence-query") - lazy val persistenceJdbc: Dep = - ivy"$lOrg::akka-persistence-jdbc:5.0.4" - val persistenceTestKit: Dep = ivy"$tOrg::akka-persistence-testkit:$V" - - object http { - val V = Versions.akkaHttp - - lazy val http: Dep = ivy"$tOrg::akka-http:$V" - lazy val sprayJson: Dep = ivy"$tOrg::akka-http-spray-json:$V" - - } - - object projection { - val V = "1.2.2" - - lazy val core: Dep = - ivy"$lOrg::akka-projection-core:$V" - lazy val eventsourced: Dep = - ivy"$lOrg::akka-projection-eventsourced:$V" - lazy val slick: Dep = - ivy"$lOrg::akka-projection-slick:$V" - lazy val jdbc: Dep = - ivy"$lOrg::akka-projection-jdbc:$V" - } - - object profiles { - // TODO: deal with cross-version for Scala3 (for3Use2_13) - lazy val eventsourcedJdbcProjection: Agg[Dep] = Agg( - persistenceQuery, - projection.core, - projection.eventsourced, - projection.slick, - persistenceJdbc - ) ++ slick.default - } - } - } - - trait SlickLibs { - object slick { - val V = IWMaterials.Versions.slick - val org = "com.typesafe.slick" - - lazy val slick: Dep = ivy"$org::slick:$V" - lazy val hikaricp: Dep = ivy"$org::slick-hikaricp:$V" - lazy val default: Agg[Dep] = Agg(slick, hikaricp) - } - } - -} diff --git a/iw/build.sc b/iw/build.sc deleted file mode 100644 index fbbc3b8..0000000 --- a/iw/build.sc +++ /dev/null @@ -1,144 +0,0 @@ -import mill._, scalalib._, scalajslib._ - -import $file.bom - -object support { - val Deps = bom.IWMaterials.Deps - val Versions = bom.IWMaterials.Versions - - trait CommonModule extends ScalaModule { - def scalaVersion = "3.1.1" - def scalacOptions = Seq( - "-encoding", - "utf8", - "-deprecation", - "-explain-types", - "-explain", - "-feature", - "-language:experimental.macros", - "-language:higherKinds", - "-language:implicitConversions", - "-unchecked", - "-Xfatal-warnings", - "-Ykind-projector" - ) - } - - trait CommonJSModule extends CommonModule with ScalaJSModule { - def scalaJSVersion = "1.8.0" - } - - trait CrossPlatformModule extends Module { outer => - def ivyDeps: T[Agg[Dep]] = Agg[Dep]() - def jsDeps: T[Agg[Dep]] = Agg[Dep]() - def jvmDeps: T[Agg[Dep]] = Agg[Dep]() - def moduleDeps: Seq[Module] = Seq[Module]() - - trait PlatformModule extends CommonModule { - def platform: String - override def millSourcePath = outer.millSourcePath - override def ivyDeps = outer.ivyDeps() ++ (platform match { - case "js" => jsDeps() - case _ => jvmDeps() - }) - override def moduleDeps = outer.moduleDeps.collect { - case m: CrossPlatformModule => - platform match { - case "js" => m.js - case _ => m.jvm - } - case m: JavaModule => m - } - } - - trait JsModule extends PlatformModule with CommonJSModule { - def platform = "js" - } - - trait JvmModule extends PlatformModule { - def platform = "jvm" - } - - val js: JsModule - val jvm: JvmModule - } - - trait PureCrossModule extends CrossPlatformModule { - override object js extends JsModule - override object jvm extends JvmModule - } - - trait PureCrossSbtModule extends CrossPlatformModule { - override object js extends JsModule with SbtModule - override object jvm extends JvmModule with SbtModule - } - - trait FullCrossSbtModule extends CrossPlatformModule { - trait FullSources extends JavaModule { self: PlatformModule => - override def sources = T.sources( - millSourcePath / platform / "src" / "main" / "scala", - millSourcePath / "shared" / "src" / "main" / "scala" - ) - - override def resources = T.sources( - millSourcePath / platform / "src" / "main" / "resources", - millSourcePath / "shared" / "src" / "main" / "resources" - ) - } - - override object js extends JsModule with FullSources - override object jvm extends JvmModule with FullSources - } - -} - -import support._ - -object mongo extends CommonModule { - def ivyDeps = Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - ivy"org.mongodb.scala::mongo-scala-driver:4.2.3".withDottyCompat( - scalaVersion() - ) - ) -} - -object tapir extends FullCrossSbtModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson, Deps.zioJson) - def jvmDeps = Agg(Deps.tapirZIO, Deps.tapirZIOHttp4sServer) - def jsDeps = Agg(Deps.tapirSttpClient) -} - -object akkaPersistence extends CommonModule { - def millSourcePath = build.millSourcePath / "akka-persistence" - def ivyDeps = (Agg( - Deps.akka.persistenceQuery, - Deps.akka.persistenceJdbc, - Deps.akka.projection.core, - Deps.akka.projection.eventsourced, - Deps.akka.projection.slick - ) ++ Deps.slick.default).map(_.withDottyCompat(scalaVersion())) ++ Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - Deps.akka.persistence.withDottyCompat(scalaVersion()), - ivy"com.typesafe.akka::akka-cluster-sharding-typed:${Deps.akka.V}" - .withDottyCompat(scalaVersion()) - ) -} - -object ui extends CommonJSModule { - def ivyDeps = Agg( - Deps.zio, - Deps.laminar, - Deps.zioJson, - Deps.waypoint, - Deps.urlDsl, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) -} diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala deleted file mode 100644 index e7e6627..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.akka - -// Base class for all command-processing related exceptions from handlers -sealed abstract class CommandHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandNotAvailable[C, S](cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd není dostupný ve stavu $state" - ) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandRejected[C, S](reason: String, cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd byl ve stavu $state odmítnut s odůvodněním $reason" - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala deleted file mode 100644 index 72a5bf9..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.akka - -sealed abstract class EventHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -case class UnhandledEvent[Event, State](event: Event, state: State) - extends EventHandlerException( - s"Událost $event nastala ve stavu $state bez možnosti zpracování" - ) diff --git a/iw/bom.sc b/iw/bom.sc deleted file mode 100644 index e9266b8..0000000 --- a/iw/bom.sc +++ /dev/null @@ -1,227 +0,0 @@ -import mill._, scalalib._ - -object IWMaterials { - - object Versions { - val akka = "2.6.16" - val akkaHttp = "10.2.4" - val cats = "2.7.0" - val elastic4s = "7.12.2" - val http4s = "0.23.10" - val http4sPac4J = "4.0.0" - val laminar = "0.14.2" - val laminext = laminar - val logbackClassic = "1.2.10" - val pac4j = "5.2.0" - val play = "2.8.8" - val playJson = "2.9.2" - val scalaTest = "3.2.9" - val slick = "3.3.3" - val sttpClient = "3.5.0" - val tapir = "0.20.1" - val urlDsl = "0.4.0" - val waypoint = "0.5.0" - val zio = "2.0.0-RC2" - val zioConfig = "3.0.0-RC2" - val zioInteropCats = "3.3.0-RC2" - val zioJson = "0.3.0-RC3" - val zioLogging = "2.0.0-RC5" - val zioPrelude = "1.0.0-RC10" - val zioZMX = "0.0.11" - } - - object Deps extends AkkaLibs with SlickLibs { - import IWMaterials.{Versions => V} - - val zioOrg = "dev.zio" - - def zioLib(name: String, version: String): Dep = - ivy"$zioOrg::zio-$name::$version" - - lazy val zio: Dep = ivy"$zioOrg::zio:${V.zio}" - - lazy val zioTest: Dep = zioLib("test", V.zio) - lazy val zioTestSbt: Dep = zioLib("test", V.zio) - - lazy val zioConfig: Dep = zioLib("config", V.zioConfig) - lazy val zioConfigTypesafe: Dep = - zioLib("config-typesafe", V.zioConfig) - lazy val zioConfigMagnolia: Dep = - zioLib("config-magnolia", V.zioConfig) - - lazy val zioJson: Dep = zioLib("json", V.zioJson) - lazy val zioLogging: Dep = zioLib("logging", V.zioLogging) - lazy val zioLoggingSlf4j: Dep = - zioLib("logging-slf4j", V.zioLogging) - lazy val zioPrelude: Dep = zioLib("prelude", V.zioPrelude) - lazy val zioStreams: Dep = zioLib("streams", V.zio) - lazy val zioZMX: Dep = zioLib("zmx", V.zioZMX) - lazy val zioInteropCats: Dep = - zioLib("interop-cats", V.zioInteropCats) - - /* What is the equivalent? ZIOModule with prepared test config? - def useZIO(testConf: Configuration*): Agg[Dep] = Agg( - zio, - zioTest, - zioTestSbt, - testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework") - ) - */ - - /* - def useZIOAll(testConf: Configuration*): Seq[Def.Setting[_]] = - useZIO(testConf: _*) ++ Seq( - zioStreams, - zioConfig, - zioConfigTypesafe, - zioConfigMagnolia, - zioJson, - zioZMX, - zioLogging, - zioPrelude - ) - */ - - private val tapirOrg = "com.softwaremill.sttp.tapir" - def tapirLib(name: String): Dep = - ivy"${tapirOrg}::tapir-$name::${V.tapir}" - - lazy val tapirCore: Dep = tapirLib("core") - lazy val tapirZIO: Dep = tapirLib("zio") - lazy val tapirZIOJson: Dep = tapirLib("json-zio") - lazy val tapirSttpClient: Dep = tapirLib("sttp-client") - lazy val tapirCats: Dep = tapirLib("cats") - lazy val tapirZIOHttp4sServer: Dep = tapirLib("zio-http4s-server") - - private val sttpClientOrg = "com.softwaremill.sttp.client3" - def sttpClientLib(name: String): Dep = - ivy"${sttpClientOrg}::${name}:${V.sttpClient}" - - lazy val sttpClientCore: Dep = sttpClientLib("core") - - lazy val http4sBlazeServer: Dep = - ivy"org.http4s::http4s-blaze-server:${V.http4s}" - - lazy val http4sPac4J: Dep = - ivy"org.pac4j::http4s-pac4j:${V.http4sPac4J}" - lazy val pac4jOIDC: Dep = - ivy"org.pac4j:pac4j-oidc:${V.pac4j}" - - lazy val scalaTest: Dep = - ivy"org.scalatest::scalatest:${V.scalaTest}" - lazy val scalaTestPlusScalacheck: Dep = - ivy"org.scalatestplus::scalacheck-1-15:3.2.9.0" - lazy val playScalaTest: Dep = - ivy"org.scalatestplus.play::scalatestplus-play:5.1.0" - - private val playOrg = "com.typesafe.play" - lazy val playMailer: Dep = ivy"${playOrg}::play-mailer:8.0.1" - lazy val playServer: Dep = ivy"${playOrg}::play-server:${V.play}" - lazy val playAkkaServer: Dep = - ivy"${playOrg}::play-akka-http-server:${V.play}" - lazy val play: Dep = ivy"${playOrg}::play:${V.play}" - lazy val playAhcWs: Dep = ivy"${playOrg}::play-ahc-ws:${V.play}" - lazy val playJson: Dep = ivy"${playOrg}::play-json:${V.playJson}" - - private val elastic4sOrg = "com.sksamuel.elastic4s" - lazy val useElastic4S: Agg[Dep] = Agg( - ivy"${elastic4sOrg}::elastic4s-core:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-client-akka:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-http-streams:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-json-play:${V.elastic4s}" - ) - - lazy val laminar: Dep = ivy"com.raquo::laminar::${V.laminar}" - - private def laminext(name: String): Dep = - ivy"io.laminext::$name::${V.laminar}" - - lazy val laminextCore: Dep = laminext("core") - lazy val laminextTailwind: Dep = laminext("tailwind") - lazy val laminextFetch: Dep = laminext("fetch") - lazy val laminextValidationCore: Dep = laminext("validation-core") - lazy val laminextUI: Dep = laminext("ui") - - lazy val waypoint: Dep = - ivy"com.raquo::waypoint::${V.waypoint}" - - lazy val urlDsl: Dep = - ivy"be.doeraene::url-dsl::${V.urlDsl}" - - lazy val scalaJavaTime: Dep = - ivy"io.github.cquiroz::scala-java-time::2.3.0" - - lazy val scalaJavaLocales: Dep = - ivy"io.github.cquiroz::scala-java-locales::1.2.1" - - lazy val logbackClassic: Dep = - ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" - } - - trait AkkaLibs { - - self: SlickLibs => - - object akka { - val V = Versions.akka - val tOrg = "com.typesafe.akka" - val lOrg = "com.lightbend.akka" - - def akkaMod(name: String): Dep = - ivy"$tOrg::akka-$name:$V" - - lazy val actor: Dep = akkaMod("actor") - lazy val actorTyped: Dep = akkaMod("actor-typed") - lazy val stream: Dep = akkaMod("stream") - lazy val persistence: Dep = akkaMod("persistence-typed") - lazy val persistenceQuery: Dep = akkaMod("persistence-query") - lazy val persistenceJdbc: Dep = - ivy"$lOrg::akka-persistence-jdbc:5.0.4" - val persistenceTestKit: Dep = ivy"$tOrg::akka-persistence-testkit:$V" - - object http { - val V = Versions.akkaHttp - - lazy val http: Dep = ivy"$tOrg::akka-http:$V" - lazy val sprayJson: Dep = ivy"$tOrg::akka-http-spray-json:$V" - - } - - object projection { - val V = "1.2.2" - - lazy val core: Dep = - ivy"$lOrg::akka-projection-core:$V" - lazy val eventsourced: Dep = - ivy"$lOrg::akka-projection-eventsourced:$V" - lazy val slick: Dep = - ivy"$lOrg::akka-projection-slick:$V" - lazy val jdbc: Dep = - ivy"$lOrg::akka-projection-jdbc:$V" - } - - object profiles { - // TODO: deal with cross-version for Scala3 (for3Use2_13) - lazy val eventsourcedJdbcProjection: Agg[Dep] = Agg( - persistenceQuery, - projection.core, - projection.eventsourced, - projection.slick, - persistenceJdbc - ) ++ slick.default - } - } - } - - trait SlickLibs { - object slick { - val V = IWMaterials.Versions.slick - val org = "com.typesafe.slick" - - lazy val slick: Dep = ivy"$org::slick:$V" - lazy val hikaricp: Dep = ivy"$org::slick-hikaricp:$V" - lazy val default: Agg[Dep] = Agg(slick, hikaricp) - } - } - -} diff --git a/iw/build.sc b/iw/build.sc deleted file mode 100644 index fbbc3b8..0000000 --- a/iw/build.sc +++ /dev/null @@ -1,144 +0,0 @@ -import mill._, scalalib._, scalajslib._ - -import $file.bom - -object support { - val Deps = bom.IWMaterials.Deps - val Versions = bom.IWMaterials.Versions - - trait CommonModule extends ScalaModule { - def scalaVersion = "3.1.1" - def scalacOptions = Seq( - "-encoding", - "utf8", - "-deprecation", - "-explain-types", - "-explain", - "-feature", - "-language:experimental.macros", - "-language:higherKinds", - "-language:implicitConversions", - "-unchecked", - "-Xfatal-warnings", - "-Ykind-projector" - ) - } - - trait CommonJSModule extends CommonModule with ScalaJSModule { - def scalaJSVersion = "1.8.0" - } - - trait CrossPlatformModule extends Module { outer => - def ivyDeps: T[Agg[Dep]] = Agg[Dep]() - def jsDeps: T[Agg[Dep]] = Agg[Dep]() - def jvmDeps: T[Agg[Dep]] = Agg[Dep]() - def moduleDeps: Seq[Module] = Seq[Module]() - - trait PlatformModule extends CommonModule { - def platform: String - override def millSourcePath = outer.millSourcePath - override def ivyDeps = outer.ivyDeps() ++ (platform match { - case "js" => jsDeps() - case _ => jvmDeps() - }) - override def moduleDeps = outer.moduleDeps.collect { - case m: CrossPlatformModule => - platform match { - case "js" => m.js - case _ => m.jvm - } - case m: JavaModule => m - } - } - - trait JsModule extends PlatformModule with CommonJSModule { - def platform = "js" - } - - trait JvmModule extends PlatformModule { - def platform = "jvm" - } - - val js: JsModule - val jvm: JvmModule - } - - trait PureCrossModule extends CrossPlatformModule { - override object js extends JsModule - override object jvm extends JvmModule - } - - trait PureCrossSbtModule extends CrossPlatformModule { - override object js extends JsModule with SbtModule - override object jvm extends JvmModule with SbtModule - } - - trait FullCrossSbtModule extends CrossPlatformModule { - trait FullSources extends JavaModule { self: PlatformModule => - override def sources = T.sources( - millSourcePath / platform / "src" / "main" / "scala", - millSourcePath / "shared" / "src" / "main" / "scala" - ) - - override def resources = T.sources( - millSourcePath / platform / "src" / "main" / "resources", - millSourcePath / "shared" / "src" / "main" / "resources" - ) - } - - override object js extends JsModule with FullSources - override object jvm extends JvmModule with FullSources - } - -} - -import support._ - -object mongo extends CommonModule { - def ivyDeps = Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - ivy"org.mongodb.scala::mongo-scala-driver:4.2.3".withDottyCompat( - scalaVersion() - ) - ) -} - -object tapir extends FullCrossSbtModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson, Deps.zioJson) - def jvmDeps = Agg(Deps.tapirZIO, Deps.tapirZIOHttp4sServer) - def jsDeps = Agg(Deps.tapirSttpClient) -} - -object akkaPersistence extends CommonModule { - def millSourcePath = build.millSourcePath / "akka-persistence" - def ivyDeps = (Agg( - Deps.akka.persistenceQuery, - Deps.akka.persistenceJdbc, - Deps.akka.projection.core, - Deps.akka.projection.eventsourced, - Deps.akka.projection.slick - ) ++ Deps.slick.default).map(_.withDottyCompat(scalaVersion())) ++ Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - Deps.akka.persistence.withDottyCompat(scalaVersion()), - ivy"com.typesafe.akka::akka-cluster-sharding-typed:${Deps.akka.V}" - .withDottyCompat(scalaVersion()) - ) -} - -object ui extends CommonJSModule { - def ivyDeps = Agg( - Deps.zio, - Deps.laminar, - Deps.zioJson, - Deps.waypoint, - Deps.urlDsl, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) -} diff --git a/iw/domain.sc b/iw/domain.sc deleted file mode 100644 index b09c56b..0000000 --- a/iw/domain.sc +++ /dev/null @@ -1,78 +0,0 @@ -import mill._, scalalib._ - -import $file.{build => ff}, ff.support._ - -trait DomainModule extends Module { - // General extra model deps - def modelModules: Seq[Module] = Seq.empty - // General extra codecs deps - def codecsModules: Seq[Module] = Seq.empty - // General extra endpoints deps - def endpointsModules: Seq[Module] = Seq.empty - - // Implementation deps for repo - def repoModules: Seq[JavaModule] = Seq.empty - - object shared extends Module { - object model extends PureCrossModule { - def moduleDeps = modelModules - def ivyDeps = Agg(Deps.zioPrelude) - } - object codecs extends PureCrossModule { - def moduleDeps = Seq(model) ++ codecsModules - def ivyDeps = Agg(Deps.zioJson) - } - } - - trait CommonProjects extends Module { - object model extends PureCrossModule { - def ivyDeps = Agg(Deps.zioPrelude) - def moduleDeps = Seq(shared.model) - } - object codecs extends PureCrossModule { - def moduleDeps = - Seq(model, shared.model, shared.codecs, ff.tapir) - } - object endpoints extends PureCrossModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson) - def moduleDeps = Seq(model, codecs) ++ endpointsModules - } - object client extends CommonJSModule { - def moduleDeps = Seq(endpoints.js) - } - object components extends CommonJSModule { - def ivyDeps = Agg( - Deps.laminar, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) - def moduleDeps = Seq(model.js, codecs.js, client, ff.ui) - } - } - - object query extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(repo, query.endpoints.jvm) - } - object repo extends CommonModule { - def ivyDeps = Agg(Deps.zio) - def moduleDeps = Seq(model.jvm, codecs.jvm) ++ repoModules - } - object projection extends CommonModule { - def moduleDeps = Seq(repo, ff.akkaPersistence) - } - } - - object command extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(entity, command.endpoints.jvm) - } - object entity extends CommonModule { - def moduleDeps = Seq(model.jvm, codecs.jvm, ff.akkaPersistence) - } - } -} diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala deleted file mode 100644 index e7e6627..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.akka - -// Base class for all command-processing related exceptions from handlers -sealed abstract class CommandHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandNotAvailable[C, S](cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd není dostupný ve stavu $state" - ) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandRejected[C, S](reason: String, cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd byl ve stavu $state odmítnut s odůvodněním $reason" - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala deleted file mode 100644 index 72a5bf9..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.akka - -sealed abstract class EventHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -case class UnhandledEvent[Event, State](event: Event, state: State) - extends EventHandlerException( - s"Událost $event nastala ve stavu $state bez možnosti zpracování" - ) diff --git a/iw/bom.sc b/iw/bom.sc deleted file mode 100644 index e9266b8..0000000 --- a/iw/bom.sc +++ /dev/null @@ -1,227 +0,0 @@ -import mill._, scalalib._ - -object IWMaterials { - - object Versions { - val akka = "2.6.16" - val akkaHttp = "10.2.4" - val cats = "2.7.0" - val elastic4s = "7.12.2" - val http4s = "0.23.10" - val http4sPac4J = "4.0.0" - val laminar = "0.14.2" - val laminext = laminar - val logbackClassic = "1.2.10" - val pac4j = "5.2.0" - val play = "2.8.8" - val playJson = "2.9.2" - val scalaTest = "3.2.9" - val slick = "3.3.3" - val sttpClient = "3.5.0" - val tapir = "0.20.1" - val urlDsl = "0.4.0" - val waypoint = "0.5.0" - val zio = "2.0.0-RC2" - val zioConfig = "3.0.0-RC2" - val zioInteropCats = "3.3.0-RC2" - val zioJson = "0.3.0-RC3" - val zioLogging = "2.0.0-RC5" - val zioPrelude = "1.0.0-RC10" - val zioZMX = "0.0.11" - } - - object Deps extends AkkaLibs with SlickLibs { - import IWMaterials.{Versions => V} - - val zioOrg = "dev.zio" - - def zioLib(name: String, version: String): Dep = - ivy"$zioOrg::zio-$name::$version" - - lazy val zio: Dep = ivy"$zioOrg::zio:${V.zio}" - - lazy val zioTest: Dep = zioLib("test", V.zio) - lazy val zioTestSbt: Dep = zioLib("test", V.zio) - - lazy val zioConfig: Dep = zioLib("config", V.zioConfig) - lazy val zioConfigTypesafe: Dep = - zioLib("config-typesafe", V.zioConfig) - lazy val zioConfigMagnolia: Dep = - zioLib("config-magnolia", V.zioConfig) - - lazy val zioJson: Dep = zioLib("json", V.zioJson) - lazy val zioLogging: Dep = zioLib("logging", V.zioLogging) - lazy val zioLoggingSlf4j: Dep = - zioLib("logging-slf4j", V.zioLogging) - lazy val zioPrelude: Dep = zioLib("prelude", V.zioPrelude) - lazy val zioStreams: Dep = zioLib("streams", V.zio) - lazy val zioZMX: Dep = zioLib("zmx", V.zioZMX) - lazy val zioInteropCats: Dep = - zioLib("interop-cats", V.zioInteropCats) - - /* What is the equivalent? ZIOModule with prepared test config? - def useZIO(testConf: Configuration*): Agg[Dep] = Agg( - zio, - zioTest, - zioTestSbt, - testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework") - ) - */ - - /* - def useZIOAll(testConf: Configuration*): Seq[Def.Setting[_]] = - useZIO(testConf: _*) ++ Seq( - zioStreams, - zioConfig, - zioConfigTypesafe, - zioConfigMagnolia, - zioJson, - zioZMX, - zioLogging, - zioPrelude - ) - */ - - private val tapirOrg = "com.softwaremill.sttp.tapir" - def tapirLib(name: String): Dep = - ivy"${tapirOrg}::tapir-$name::${V.tapir}" - - lazy val tapirCore: Dep = tapirLib("core") - lazy val tapirZIO: Dep = tapirLib("zio") - lazy val tapirZIOJson: Dep = tapirLib("json-zio") - lazy val tapirSttpClient: Dep = tapirLib("sttp-client") - lazy val tapirCats: Dep = tapirLib("cats") - lazy val tapirZIOHttp4sServer: Dep = tapirLib("zio-http4s-server") - - private val sttpClientOrg = "com.softwaremill.sttp.client3" - def sttpClientLib(name: String): Dep = - ivy"${sttpClientOrg}::${name}:${V.sttpClient}" - - lazy val sttpClientCore: Dep = sttpClientLib("core") - - lazy val http4sBlazeServer: Dep = - ivy"org.http4s::http4s-blaze-server:${V.http4s}" - - lazy val http4sPac4J: Dep = - ivy"org.pac4j::http4s-pac4j:${V.http4sPac4J}" - lazy val pac4jOIDC: Dep = - ivy"org.pac4j:pac4j-oidc:${V.pac4j}" - - lazy val scalaTest: Dep = - ivy"org.scalatest::scalatest:${V.scalaTest}" - lazy val scalaTestPlusScalacheck: Dep = - ivy"org.scalatestplus::scalacheck-1-15:3.2.9.0" - lazy val playScalaTest: Dep = - ivy"org.scalatestplus.play::scalatestplus-play:5.1.0" - - private val playOrg = "com.typesafe.play" - lazy val playMailer: Dep = ivy"${playOrg}::play-mailer:8.0.1" - lazy val playServer: Dep = ivy"${playOrg}::play-server:${V.play}" - lazy val playAkkaServer: Dep = - ivy"${playOrg}::play-akka-http-server:${V.play}" - lazy val play: Dep = ivy"${playOrg}::play:${V.play}" - lazy val playAhcWs: Dep = ivy"${playOrg}::play-ahc-ws:${V.play}" - lazy val playJson: Dep = ivy"${playOrg}::play-json:${V.playJson}" - - private val elastic4sOrg = "com.sksamuel.elastic4s" - lazy val useElastic4S: Agg[Dep] = Agg( - ivy"${elastic4sOrg}::elastic4s-core:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-client-akka:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-http-streams:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-json-play:${V.elastic4s}" - ) - - lazy val laminar: Dep = ivy"com.raquo::laminar::${V.laminar}" - - private def laminext(name: String): Dep = - ivy"io.laminext::$name::${V.laminar}" - - lazy val laminextCore: Dep = laminext("core") - lazy val laminextTailwind: Dep = laminext("tailwind") - lazy val laminextFetch: Dep = laminext("fetch") - lazy val laminextValidationCore: Dep = laminext("validation-core") - lazy val laminextUI: Dep = laminext("ui") - - lazy val waypoint: Dep = - ivy"com.raquo::waypoint::${V.waypoint}" - - lazy val urlDsl: Dep = - ivy"be.doeraene::url-dsl::${V.urlDsl}" - - lazy val scalaJavaTime: Dep = - ivy"io.github.cquiroz::scala-java-time::2.3.0" - - lazy val scalaJavaLocales: Dep = - ivy"io.github.cquiroz::scala-java-locales::1.2.1" - - lazy val logbackClassic: Dep = - ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" - } - - trait AkkaLibs { - - self: SlickLibs => - - object akka { - val V = Versions.akka - val tOrg = "com.typesafe.akka" - val lOrg = "com.lightbend.akka" - - def akkaMod(name: String): Dep = - ivy"$tOrg::akka-$name:$V" - - lazy val actor: Dep = akkaMod("actor") - lazy val actorTyped: Dep = akkaMod("actor-typed") - lazy val stream: Dep = akkaMod("stream") - lazy val persistence: Dep = akkaMod("persistence-typed") - lazy val persistenceQuery: Dep = akkaMod("persistence-query") - lazy val persistenceJdbc: Dep = - ivy"$lOrg::akka-persistence-jdbc:5.0.4" - val persistenceTestKit: Dep = ivy"$tOrg::akka-persistence-testkit:$V" - - object http { - val V = Versions.akkaHttp - - lazy val http: Dep = ivy"$tOrg::akka-http:$V" - lazy val sprayJson: Dep = ivy"$tOrg::akka-http-spray-json:$V" - - } - - object projection { - val V = "1.2.2" - - lazy val core: Dep = - ivy"$lOrg::akka-projection-core:$V" - lazy val eventsourced: Dep = - ivy"$lOrg::akka-projection-eventsourced:$V" - lazy val slick: Dep = - ivy"$lOrg::akka-projection-slick:$V" - lazy val jdbc: Dep = - ivy"$lOrg::akka-projection-jdbc:$V" - } - - object profiles { - // TODO: deal with cross-version for Scala3 (for3Use2_13) - lazy val eventsourcedJdbcProjection: Agg[Dep] = Agg( - persistenceQuery, - projection.core, - projection.eventsourced, - projection.slick, - persistenceJdbc - ) ++ slick.default - } - } - } - - trait SlickLibs { - object slick { - val V = IWMaterials.Versions.slick - val org = "com.typesafe.slick" - - lazy val slick: Dep = ivy"$org::slick:$V" - lazy val hikaricp: Dep = ivy"$org::slick-hikaricp:$V" - lazy val default: Agg[Dep] = Agg(slick, hikaricp) - } - } - -} diff --git a/iw/build.sc b/iw/build.sc deleted file mode 100644 index fbbc3b8..0000000 --- a/iw/build.sc +++ /dev/null @@ -1,144 +0,0 @@ -import mill._, scalalib._, scalajslib._ - -import $file.bom - -object support { - val Deps = bom.IWMaterials.Deps - val Versions = bom.IWMaterials.Versions - - trait CommonModule extends ScalaModule { - def scalaVersion = "3.1.1" - def scalacOptions = Seq( - "-encoding", - "utf8", - "-deprecation", - "-explain-types", - "-explain", - "-feature", - "-language:experimental.macros", - "-language:higherKinds", - "-language:implicitConversions", - "-unchecked", - "-Xfatal-warnings", - "-Ykind-projector" - ) - } - - trait CommonJSModule extends CommonModule with ScalaJSModule { - def scalaJSVersion = "1.8.0" - } - - trait CrossPlatformModule extends Module { outer => - def ivyDeps: T[Agg[Dep]] = Agg[Dep]() - def jsDeps: T[Agg[Dep]] = Agg[Dep]() - def jvmDeps: T[Agg[Dep]] = Agg[Dep]() - def moduleDeps: Seq[Module] = Seq[Module]() - - trait PlatformModule extends CommonModule { - def platform: String - override def millSourcePath = outer.millSourcePath - override def ivyDeps = outer.ivyDeps() ++ (platform match { - case "js" => jsDeps() - case _ => jvmDeps() - }) - override def moduleDeps = outer.moduleDeps.collect { - case m: CrossPlatformModule => - platform match { - case "js" => m.js - case _ => m.jvm - } - case m: JavaModule => m - } - } - - trait JsModule extends PlatformModule with CommonJSModule { - def platform = "js" - } - - trait JvmModule extends PlatformModule { - def platform = "jvm" - } - - val js: JsModule - val jvm: JvmModule - } - - trait PureCrossModule extends CrossPlatformModule { - override object js extends JsModule - override object jvm extends JvmModule - } - - trait PureCrossSbtModule extends CrossPlatformModule { - override object js extends JsModule with SbtModule - override object jvm extends JvmModule with SbtModule - } - - trait FullCrossSbtModule extends CrossPlatformModule { - trait FullSources extends JavaModule { self: PlatformModule => - override def sources = T.sources( - millSourcePath / platform / "src" / "main" / "scala", - millSourcePath / "shared" / "src" / "main" / "scala" - ) - - override def resources = T.sources( - millSourcePath / platform / "src" / "main" / "resources", - millSourcePath / "shared" / "src" / "main" / "resources" - ) - } - - override object js extends JsModule with FullSources - override object jvm extends JvmModule with FullSources - } - -} - -import support._ - -object mongo extends CommonModule { - def ivyDeps = Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - ivy"org.mongodb.scala::mongo-scala-driver:4.2.3".withDottyCompat( - scalaVersion() - ) - ) -} - -object tapir extends FullCrossSbtModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson, Deps.zioJson) - def jvmDeps = Agg(Deps.tapirZIO, Deps.tapirZIOHttp4sServer) - def jsDeps = Agg(Deps.tapirSttpClient) -} - -object akkaPersistence extends CommonModule { - def millSourcePath = build.millSourcePath / "akka-persistence" - def ivyDeps = (Agg( - Deps.akka.persistenceQuery, - Deps.akka.persistenceJdbc, - Deps.akka.projection.core, - Deps.akka.projection.eventsourced, - Deps.akka.projection.slick - ) ++ Deps.slick.default).map(_.withDottyCompat(scalaVersion())) ++ Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - Deps.akka.persistence.withDottyCompat(scalaVersion()), - ivy"com.typesafe.akka::akka-cluster-sharding-typed:${Deps.akka.V}" - .withDottyCompat(scalaVersion()) - ) -} - -object ui extends CommonJSModule { - def ivyDeps = Agg( - Deps.zio, - Deps.laminar, - Deps.zioJson, - Deps.waypoint, - Deps.urlDsl, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) -} diff --git a/iw/domain.sc b/iw/domain.sc deleted file mode 100644 index b09c56b..0000000 --- a/iw/domain.sc +++ /dev/null @@ -1,78 +0,0 @@ -import mill._, scalalib._ - -import $file.{build => ff}, ff.support._ - -trait DomainModule extends Module { - // General extra model deps - def modelModules: Seq[Module] = Seq.empty - // General extra codecs deps - def codecsModules: Seq[Module] = Seq.empty - // General extra endpoints deps - def endpointsModules: Seq[Module] = Seq.empty - - // Implementation deps for repo - def repoModules: Seq[JavaModule] = Seq.empty - - object shared extends Module { - object model extends PureCrossModule { - def moduleDeps = modelModules - def ivyDeps = Agg(Deps.zioPrelude) - } - object codecs extends PureCrossModule { - def moduleDeps = Seq(model) ++ codecsModules - def ivyDeps = Agg(Deps.zioJson) - } - } - - trait CommonProjects extends Module { - object model extends PureCrossModule { - def ivyDeps = Agg(Deps.zioPrelude) - def moduleDeps = Seq(shared.model) - } - object codecs extends PureCrossModule { - def moduleDeps = - Seq(model, shared.model, shared.codecs, ff.tapir) - } - object endpoints extends PureCrossModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson) - def moduleDeps = Seq(model, codecs) ++ endpointsModules - } - object client extends CommonJSModule { - def moduleDeps = Seq(endpoints.js) - } - object components extends CommonJSModule { - def ivyDeps = Agg( - Deps.laminar, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) - def moduleDeps = Seq(model.js, codecs.js, client, ff.ui) - } - } - - object query extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(repo, query.endpoints.jvm) - } - object repo extends CommonModule { - def ivyDeps = Agg(Deps.zio) - def moduleDeps = Seq(model.jvm, codecs.jvm) ++ repoModules - } - object projection extends CommonModule { - def moduleDeps = Seq(repo, ff.akkaPersistence) - } - } - - object command extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(entity, command.endpoints.jvm) - } - object entity extends CommonModule { - def moduleDeps = Seq(model.jvm, codecs.jvm, ff.akkaPersistence) - } - } -} diff --git a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala b/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala deleted file mode 100644 index 0fa3493..0000000 --- a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala +++ /dev/null @@ -1,53 +0,0 @@ -package works.iterative.mongo - -import zio.* -import zio.json.* -import zio.config.* -import org.mongodb.scala.* -import org.mongodb.scala.model.Filters.* -import org.bson.json.JsonObject -import org.mongodb.scala.model.ReplaceOptions -import org.mongodb.scala.bson.conversions.Bson - -case class MongoConfig(uri: String) - -object MongoConfig: - val configDesc = - import ConfigDescriptor.* - nested("MONGO")(string("URI").default("mongodb://localhost:27017")) - .to[MongoConfig] - val fromEnv = ZConfig.fromSystemEnv(configDesc) - -extension (m: MongoClient.type) - def layer: RLayer[MongoConfig, MongoClient] = - ZIO - .serviceWithZIO[MongoConfig](c => Task.attempt(MongoClient(c.uri))) - .toLayer - -class MongoJsonRepository[Elem, Key, Criteria]( - collection: MongoCollection[JsonObject], - toFilter: Criteria => Bson, - idFilter: Elem => (String, Key) -)(using JsonCodec[Elem]) { - def matching(criteria: Criteria): Task[Seq[Elem]] = - val filter = toFilter(criteria) - val query = collection.find(filter) - - for - result <- ZIO.fromFuture(_ => query.toFuture) - proof <- ZIO.collect(result)(j => - ZIO.fromOption(j.getJson.fromJson[Elem].toOption) - ) - yield proof - - def put(elem: Elem): Task[Unit] = - Task.async(cb => - collection - .replaceOne( - equal.tupled(idFilter(elem)), - JsonObject(elem.toJson), - ReplaceOptions().upsert(true) - ) - .subscribe(_ => cb(Task.unit), t => cb(Task.fail(t))) - ) -} diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala deleted file mode 100644 index e7e6627..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.akka - -// Base class for all command-processing related exceptions from handlers -sealed abstract class CommandHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandNotAvailable[C, S](cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd není dostupný ve stavu $state" - ) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandRejected[C, S](reason: String, cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd byl ve stavu $state odmítnut s odůvodněním $reason" - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala deleted file mode 100644 index 72a5bf9..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.akka - -sealed abstract class EventHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -case class UnhandledEvent[Event, State](event: Event, state: State) - extends EventHandlerException( - s"Událost $event nastala ve stavu $state bez možnosti zpracování" - ) diff --git a/iw/bom.sc b/iw/bom.sc deleted file mode 100644 index e9266b8..0000000 --- a/iw/bom.sc +++ /dev/null @@ -1,227 +0,0 @@ -import mill._, scalalib._ - -object IWMaterials { - - object Versions { - val akka = "2.6.16" - val akkaHttp = "10.2.4" - val cats = "2.7.0" - val elastic4s = "7.12.2" - val http4s = "0.23.10" - val http4sPac4J = "4.0.0" - val laminar = "0.14.2" - val laminext = laminar - val logbackClassic = "1.2.10" - val pac4j = "5.2.0" - val play = "2.8.8" - val playJson = "2.9.2" - val scalaTest = "3.2.9" - val slick = "3.3.3" - val sttpClient = "3.5.0" - val tapir = "0.20.1" - val urlDsl = "0.4.0" - val waypoint = "0.5.0" - val zio = "2.0.0-RC2" - val zioConfig = "3.0.0-RC2" - val zioInteropCats = "3.3.0-RC2" - val zioJson = "0.3.0-RC3" - val zioLogging = "2.0.0-RC5" - val zioPrelude = "1.0.0-RC10" - val zioZMX = "0.0.11" - } - - object Deps extends AkkaLibs with SlickLibs { - import IWMaterials.{Versions => V} - - val zioOrg = "dev.zio" - - def zioLib(name: String, version: String): Dep = - ivy"$zioOrg::zio-$name::$version" - - lazy val zio: Dep = ivy"$zioOrg::zio:${V.zio}" - - lazy val zioTest: Dep = zioLib("test", V.zio) - lazy val zioTestSbt: Dep = zioLib("test", V.zio) - - lazy val zioConfig: Dep = zioLib("config", V.zioConfig) - lazy val zioConfigTypesafe: Dep = - zioLib("config-typesafe", V.zioConfig) - lazy val zioConfigMagnolia: Dep = - zioLib("config-magnolia", V.zioConfig) - - lazy val zioJson: Dep = zioLib("json", V.zioJson) - lazy val zioLogging: Dep = zioLib("logging", V.zioLogging) - lazy val zioLoggingSlf4j: Dep = - zioLib("logging-slf4j", V.zioLogging) - lazy val zioPrelude: Dep = zioLib("prelude", V.zioPrelude) - lazy val zioStreams: Dep = zioLib("streams", V.zio) - lazy val zioZMX: Dep = zioLib("zmx", V.zioZMX) - lazy val zioInteropCats: Dep = - zioLib("interop-cats", V.zioInteropCats) - - /* What is the equivalent? ZIOModule with prepared test config? - def useZIO(testConf: Configuration*): Agg[Dep] = Agg( - zio, - zioTest, - zioTestSbt, - testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework") - ) - */ - - /* - def useZIOAll(testConf: Configuration*): Seq[Def.Setting[_]] = - useZIO(testConf: _*) ++ Seq( - zioStreams, - zioConfig, - zioConfigTypesafe, - zioConfigMagnolia, - zioJson, - zioZMX, - zioLogging, - zioPrelude - ) - */ - - private val tapirOrg = "com.softwaremill.sttp.tapir" - def tapirLib(name: String): Dep = - ivy"${tapirOrg}::tapir-$name::${V.tapir}" - - lazy val tapirCore: Dep = tapirLib("core") - lazy val tapirZIO: Dep = tapirLib("zio") - lazy val tapirZIOJson: Dep = tapirLib("json-zio") - lazy val tapirSttpClient: Dep = tapirLib("sttp-client") - lazy val tapirCats: Dep = tapirLib("cats") - lazy val tapirZIOHttp4sServer: Dep = tapirLib("zio-http4s-server") - - private val sttpClientOrg = "com.softwaremill.sttp.client3" - def sttpClientLib(name: String): Dep = - ivy"${sttpClientOrg}::${name}:${V.sttpClient}" - - lazy val sttpClientCore: Dep = sttpClientLib("core") - - lazy val http4sBlazeServer: Dep = - ivy"org.http4s::http4s-blaze-server:${V.http4s}" - - lazy val http4sPac4J: Dep = - ivy"org.pac4j::http4s-pac4j:${V.http4sPac4J}" - lazy val pac4jOIDC: Dep = - ivy"org.pac4j:pac4j-oidc:${V.pac4j}" - - lazy val scalaTest: Dep = - ivy"org.scalatest::scalatest:${V.scalaTest}" - lazy val scalaTestPlusScalacheck: Dep = - ivy"org.scalatestplus::scalacheck-1-15:3.2.9.0" - lazy val playScalaTest: Dep = - ivy"org.scalatestplus.play::scalatestplus-play:5.1.0" - - private val playOrg = "com.typesafe.play" - lazy val playMailer: Dep = ivy"${playOrg}::play-mailer:8.0.1" - lazy val playServer: Dep = ivy"${playOrg}::play-server:${V.play}" - lazy val playAkkaServer: Dep = - ivy"${playOrg}::play-akka-http-server:${V.play}" - lazy val play: Dep = ivy"${playOrg}::play:${V.play}" - lazy val playAhcWs: Dep = ivy"${playOrg}::play-ahc-ws:${V.play}" - lazy val playJson: Dep = ivy"${playOrg}::play-json:${V.playJson}" - - private val elastic4sOrg = "com.sksamuel.elastic4s" - lazy val useElastic4S: Agg[Dep] = Agg( - ivy"${elastic4sOrg}::elastic4s-core:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-client-akka:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-http-streams:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-json-play:${V.elastic4s}" - ) - - lazy val laminar: Dep = ivy"com.raquo::laminar::${V.laminar}" - - private def laminext(name: String): Dep = - ivy"io.laminext::$name::${V.laminar}" - - lazy val laminextCore: Dep = laminext("core") - lazy val laminextTailwind: Dep = laminext("tailwind") - lazy val laminextFetch: Dep = laminext("fetch") - lazy val laminextValidationCore: Dep = laminext("validation-core") - lazy val laminextUI: Dep = laminext("ui") - - lazy val waypoint: Dep = - ivy"com.raquo::waypoint::${V.waypoint}" - - lazy val urlDsl: Dep = - ivy"be.doeraene::url-dsl::${V.urlDsl}" - - lazy val scalaJavaTime: Dep = - ivy"io.github.cquiroz::scala-java-time::2.3.0" - - lazy val scalaJavaLocales: Dep = - ivy"io.github.cquiroz::scala-java-locales::1.2.1" - - lazy val logbackClassic: Dep = - ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" - } - - trait AkkaLibs { - - self: SlickLibs => - - object akka { - val V = Versions.akka - val tOrg = "com.typesafe.akka" - val lOrg = "com.lightbend.akka" - - def akkaMod(name: String): Dep = - ivy"$tOrg::akka-$name:$V" - - lazy val actor: Dep = akkaMod("actor") - lazy val actorTyped: Dep = akkaMod("actor-typed") - lazy val stream: Dep = akkaMod("stream") - lazy val persistence: Dep = akkaMod("persistence-typed") - lazy val persistenceQuery: Dep = akkaMod("persistence-query") - lazy val persistenceJdbc: Dep = - ivy"$lOrg::akka-persistence-jdbc:5.0.4" - val persistenceTestKit: Dep = ivy"$tOrg::akka-persistence-testkit:$V" - - object http { - val V = Versions.akkaHttp - - lazy val http: Dep = ivy"$tOrg::akka-http:$V" - lazy val sprayJson: Dep = ivy"$tOrg::akka-http-spray-json:$V" - - } - - object projection { - val V = "1.2.2" - - lazy val core: Dep = - ivy"$lOrg::akka-projection-core:$V" - lazy val eventsourced: Dep = - ivy"$lOrg::akka-projection-eventsourced:$V" - lazy val slick: Dep = - ivy"$lOrg::akka-projection-slick:$V" - lazy val jdbc: Dep = - ivy"$lOrg::akka-projection-jdbc:$V" - } - - object profiles { - // TODO: deal with cross-version for Scala3 (for3Use2_13) - lazy val eventsourcedJdbcProjection: Agg[Dep] = Agg( - persistenceQuery, - projection.core, - projection.eventsourced, - projection.slick, - persistenceJdbc - ) ++ slick.default - } - } - } - - trait SlickLibs { - object slick { - val V = IWMaterials.Versions.slick - val org = "com.typesafe.slick" - - lazy val slick: Dep = ivy"$org::slick:$V" - lazy val hikaricp: Dep = ivy"$org::slick-hikaricp:$V" - lazy val default: Agg[Dep] = Agg(slick, hikaricp) - } - } - -} diff --git a/iw/build.sc b/iw/build.sc deleted file mode 100644 index fbbc3b8..0000000 --- a/iw/build.sc +++ /dev/null @@ -1,144 +0,0 @@ -import mill._, scalalib._, scalajslib._ - -import $file.bom - -object support { - val Deps = bom.IWMaterials.Deps - val Versions = bom.IWMaterials.Versions - - trait CommonModule extends ScalaModule { - def scalaVersion = "3.1.1" - def scalacOptions = Seq( - "-encoding", - "utf8", - "-deprecation", - "-explain-types", - "-explain", - "-feature", - "-language:experimental.macros", - "-language:higherKinds", - "-language:implicitConversions", - "-unchecked", - "-Xfatal-warnings", - "-Ykind-projector" - ) - } - - trait CommonJSModule extends CommonModule with ScalaJSModule { - def scalaJSVersion = "1.8.0" - } - - trait CrossPlatformModule extends Module { outer => - def ivyDeps: T[Agg[Dep]] = Agg[Dep]() - def jsDeps: T[Agg[Dep]] = Agg[Dep]() - def jvmDeps: T[Agg[Dep]] = Agg[Dep]() - def moduleDeps: Seq[Module] = Seq[Module]() - - trait PlatformModule extends CommonModule { - def platform: String - override def millSourcePath = outer.millSourcePath - override def ivyDeps = outer.ivyDeps() ++ (platform match { - case "js" => jsDeps() - case _ => jvmDeps() - }) - override def moduleDeps = outer.moduleDeps.collect { - case m: CrossPlatformModule => - platform match { - case "js" => m.js - case _ => m.jvm - } - case m: JavaModule => m - } - } - - trait JsModule extends PlatformModule with CommonJSModule { - def platform = "js" - } - - trait JvmModule extends PlatformModule { - def platform = "jvm" - } - - val js: JsModule - val jvm: JvmModule - } - - trait PureCrossModule extends CrossPlatformModule { - override object js extends JsModule - override object jvm extends JvmModule - } - - trait PureCrossSbtModule extends CrossPlatformModule { - override object js extends JsModule with SbtModule - override object jvm extends JvmModule with SbtModule - } - - trait FullCrossSbtModule extends CrossPlatformModule { - trait FullSources extends JavaModule { self: PlatformModule => - override def sources = T.sources( - millSourcePath / platform / "src" / "main" / "scala", - millSourcePath / "shared" / "src" / "main" / "scala" - ) - - override def resources = T.sources( - millSourcePath / platform / "src" / "main" / "resources", - millSourcePath / "shared" / "src" / "main" / "resources" - ) - } - - override object js extends JsModule with FullSources - override object jvm extends JvmModule with FullSources - } - -} - -import support._ - -object mongo extends CommonModule { - def ivyDeps = Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - ivy"org.mongodb.scala::mongo-scala-driver:4.2.3".withDottyCompat( - scalaVersion() - ) - ) -} - -object tapir extends FullCrossSbtModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson, Deps.zioJson) - def jvmDeps = Agg(Deps.tapirZIO, Deps.tapirZIOHttp4sServer) - def jsDeps = Agg(Deps.tapirSttpClient) -} - -object akkaPersistence extends CommonModule { - def millSourcePath = build.millSourcePath / "akka-persistence" - def ivyDeps = (Agg( - Deps.akka.persistenceQuery, - Deps.akka.persistenceJdbc, - Deps.akka.projection.core, - Deps.akka.projection.eventsourced, - Deps.akka.projection.slick - ) ++ Deps.slick.default).map(_.withDottyCompat(scalaVersion())) ++ Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - Deps.akka.persistence.withDottyCompat(scalaVersion()), - ivy"com.typesafe.akka::akka-cluster-sharding-typed:${Deps.akka.V}" - .withDottyCompat(scalaVersion()) - ) -} - -object ui extends CommonJSModule { - def ivyDeps = Agg( - Deps.zio, - Deps.laminar, - Deps.zioJson, - Deps.waypoint, - Deps.urlDsl, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) -} diff --git a/iw/domain.sc b/iw/domain.sc deleted file mode 100644 index b09c56b..0000000 --- a/iw/domain.sc +++ /dev/null @@ -1,78 +0,0 @@ -import mill._, scalalib._ - -import $file.{build => ff}, ff.support._ - -trait DomainModule extends Module { - // General extra model deps - def modelModules: Seq[Module] = Seq.empty - // General extra codecs deps - def codecsModules: Seq[Module] = Seq.empty - // General extra endpoints deps - def endpointsModules: Seq[Module] = Seq.empty - - // Implementation deps for repo - def repoModules: Seq[JavaModule] = Seq.empty - - object shared extends Module { - object model extends PureCrossModule { - def moduleDeps = modelModules - def ivyDeps = Agg(Deps.zioPrelude) - } - object codecs extends PureCrossModule { - def moduleDeps = Seq(model) ++ codecsModules - def ivyDeps = Agg(Deps.zioJson) - } - } - - trait CommonProjects extends Module { - object model extends PureCrossModule { - def ivyDeps = Agg(Deps.zioPrelude) - def moduleDeps = Seq(shared.model) - } - object codecs extends PureCrossModule { - def moduleDeps = - Seq(model, shared.model, shared.codecs, ff.tapir) - } - object endpoints extends PureCrossModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson) - def moduleDeps = Seq(model, codecs) ++ endpointsModules - } - object client extends CommonJSModule { - def moduleDeps = Seq(endpoints.js) - } - object components extends CommonJSModule { - def ivyDeps = Agg( - Deps.laminar, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) - def moduleDeps = Seq(model.js, codecs.js, client, ff.ui) - } - } - - object query extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(repo, query.endpoints.jvm) - } - object repo extends CommonModule { - def ivyDeps = Agg(Deps.zio) - def moduleDeps = Seq(model.jvm, codecs.jvm) ++ repoModules - } - object projection extends CommonModule { - def moduleDeps = Seq(repo, ff.akkaPersistence) - } - } - - object command extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(entity, command.endpoints.jvm) - } - object entity extends CommonModule { - def moduleDeps = Seq(model.jvm, codecs.jvm, ff.akkaPersistence) - } - } -} diff --git a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala b/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala deleted file mode 100644 index 0fa3493..0000000 --- a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala +++ /dev/null @@ -1,53 +0,0 @@ -package works.iterative.mongo - -import zio.* -import zio.json.* -import zio.config.* -import org.mongodb.scala.* -import org.mongodb.scala.model.Filters.* -import org.bson.json.JsonObject -import org.mongodb.scala.model.ReplaceOptions -import org.mongodb.scala.bson.conversions.Bson - -case class MongoConfig(uri: String) - -object MongoConfig: - val configDesc = - import ConfigDescriptor.* - nested("MONGO")(string("URI").default("mongodb://localhost:27017")) - .to[MongoConfig] - val fromEnv = ZConfig.fromSystemEnv(configDesc) - -extension (m: MongoClient.type) - def layer: RLayer[MongoConfig, MongoClient] = - ZIO - .serviceWithZIO[MongoConfig](c => Task.attempt(MongoClient(c.uri))) - .toLayer - -class MongoJsonRepository[Elem, Key, Criteria]( - collection: MongoCollection[JsonObject], - toFilter: Criteria => Bson, - idFilter: Elem => (String, Key) -)(using JsonCodec[Elem]) { - def matching(criteria: Criteria): Task[Seq[Elem]] = - val filter = toFilter(criteria) - val query = collection.find(filter) - - for - result <- ZIO.fromFuture(_ => query.toFuture) - proof <- ZIO.collect(result)(j => - ZIO.fromOption(j.getJson.fromJson[Elem].toOption) - ) - yield proof - - def put(elem: Elem): Task[Unit] = - Task.async(cb => - collection - .replaceOne( - equal.tupled(idFilter(elem)), - JsonObject(elem.toJson), - ReplaceOptions().upsert(true) - ) - .subscribe(_ => cb(Task.unit), t => cb(Task.fail(t))) - ) -} diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala deleted file mode 100644 index 68d7196..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala +++ /dev/null @@ -1,12 +0,0 @@ -package works.iterative.tapir - -import sttp.model.Uri - -opaque type BaseUri = Option[Uri] - -object BaseUri: - - def apply(optU: Option[Uri]): BaseUri = optU - def apply(u: Uri): BaseUri = Some(u) - - extension (v: BaseUri) def toUri: Option[Uri] = v diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala deleted file mode 100644 index e7e6627..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.akka - -// Base class for all command-processing related exceptions from handlers -sealed abstract class CommandHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandNotAvailable[C, S](cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd není dostupný ve stavu $state" - ) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandRejected[C, S](reason: String, cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd byl ve stavu $state odmítnut s odůvodněním $reason" - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala deleted file mode 100644 index 72a5bf9..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.akka - -sealed abstract class EventHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -case class UnhandledEvent[Event, State](event: Event, state: State) - extends EventHandlerException( - s"Událost $event nastala ve stavu $state bez možnosti zpracování" - ) diff --git a/iw/bom.sc b/iw/bom.sc deleted file mode 100644 index e9266b8..0000000 --- a/iw/bom.sc +++ /dev/null @@ -1,227 +0,0 @@ -import mill._, scalalib._ - -object IWMaterials { - - object Versions { - val akka = "2.6.16" - val akkaHttp = "10.2.4" - val cats = "2.7.0" - val elastic4s = "7.12.2" - val http4s = "0.23.10" - val http4sPac4J = "4.0.0" - val laminar = "0.14.2" - val laminext = laminar - val logbackClassic = "1.2.10" - val pac4j = "5.2.0" - val play = "2.8.8" - val playJson = "2.9.2" - val scalaTest = "3.2.9" - val slick = "3.3.3" - val sttpClient = "3.5.0" - val tapir = "0.20.1" - val urlDsl = "0.4.0" - val waypoint = "0.5.0" - val zio = "2.0.0-RC2" - val zioConfig = "3.0.0-RC2" - val zioInteropCats = "3.3.0-RC2" - val zioJson = "0.3.0-RC3" - val zioLogging = "2.0.0-RC5" - val zioPrelude = "1.0.0-RC10" - val zioZMX = "0.0.11" - } - - object Deps extends AkkaLibs with SlickLibs { - import IWMaterials.{Versions => V} - - val zioOrg = "dev.zio" - - def zioLib(name: String, version: String): Dep = - ivy"$zioOrg::zio-$name::$version" - - lazy val zio: Dep = ivy"$zioOrg::zio:${V.zio}" - - lazy val zioTest: Dep = zioLib("test", V.zio) - lazy val zioTestSbt: Dep = zioLib("test", V.zio) - - lazy val zioConfig: Dep = zioLib("config", V.zioConfig) - lazy val zioConfigTypesafe: Dep = - zioLib("config-typesafe", V.zioConfig) - lazy val zioConfigMagnolia: Dep = - zioLib("config-magnolia", V.zioConfig) - - lazy val zioJson: Dep = zioLib("json", V.zioJson) - lazy val zioLogging: Dep = zioLib("logging", V.zioLogging) - lazy val zioLoggingSlf4j: Dep = - zioLib("logging-slf4j", V.zioLogging) - lazy val zioPrelude: Dep = zioLib("prelude", V.zioPrelude) - lazy val zioStreams: Dep = zioLib("streams", V.zio) - lazy val zioZMX: Dep = zioLib("zmx", V.zioZMX) - lazy val zioInteropCats: Dep = - zioLib("interop-cats", V.zioInteropCats) - - /* What is the equivalent? ZIOModule with prepared test config? - def useZIO(testConf: Configuration*): Agg[Dep] = Agg( - zio, - zioTest, - zioTestSbt, - testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework") - ) - */ - - /* - def useZIOAll(testConf: Configuration*): Seq[Def.Setting[_]] = - useZIO(testConf: _*) ++ Seq( - zioStreams, - zioConfig, - zioConfigTypesafe, - zioConfigMagnolia, - zioJson, - zioZMX, - zioLogging, - zioPrelude - ) - */ - - private val tapirOrg = "com.softwaremill.sttp.tapir" - def tapirLib(name: String): Dep = - ivy"${tapirOrg}::tapir-$name::${V.tapir}" - - lazy val tapirCore: Dep = tapirLib("core") - lazy val tapirZIO: Dep = tapirLib("zio") - lazy val tapirZIOJson: Dep = tapirLib("json-zio") - lazy val tapirSttpClient: Dep = tapirLib("sttp-client") - lazy val tapirCats: Dep = tapirLib("cats") - lazy val tapirZIOHttp4sServer: Dep = tapirLib("zio-http4s-server") - - private val sttpClientOrg = "com.softwaremill.sttp.client3" - def sttpClientLib(name: String): Dep = - ivy"${sttpClientOrg}::${name}:${V.sttpClient}" - - lazy val sttpClientCore: Dep = sttpClientLib("core") - - lazy val http4sBlazeServer: Dep = - ivy"org.http4s::http4s-blaze-server:${V.http4s}" - - lazy val http4sPac4J: Dep = - ivy"org.pac4j::http4s-pac4j:${V.http4sPac4J}" - lazy val pac4jOIDC: Dep = - ivy"org.pac4j:pac4j-oidc:${V.pac4j}" - - lazy val scalaTest: Dep = - ivy"org.scalatest::scalatest:${V.scalaTest}" - lazy val scalaTestPlusScalacheck: Dep = - ivy"org.scalatestplus::scalacheck-1-15:3.2.9.0" - lazy val playScalaTest: Dep = - ivy"org.scalatestplus.play::scalatestplus-play:5.1.0" - - private val playOrg = "com.typesafe.play" - lazy val playMailer: Dep = ivy"${playOrg}::play-mailer:8.0.1" - lazy val playServer: Dep = ivy"${playOrg}::play-server:${V.play}" - lazy val playAkkaServer: Dep = - ivy"${playOrg}::play-akka-http-server:${V.play}" - lazy val play: Dep = ivy"${playOrg}::play:${V.play}" - lazy val playAhcWs: Dep = ivy"${playOrg}::play-ahc-ws:${V.play}" - lazy val playJson: Dep = ivy"${playOrg}::play-json:${V.playJson}" - - private val elastic4sOrg = "com.sksamuel.elastic4s" - lazy val useElastic4S: Agg[Dep] = Agg( - ivy"${elastic4sOrg}::elastic4s-core:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-client-akka:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-http-streams:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-json-play:${V.elastic4s}" - ) - - lazy val laminar: Dep = ivy"com.raquo::laminar::${V.laminar}" - - private def laminext(name: String): Dep = - ivy"io.laminext::$name::${V.laminar}" - - lazy val laminextCore: Dep = laminext("core") - lazy val laminextTailwind: Dep = laminext("tailwind") - lazy val laminextFetch: Dep = laminext("fetch") - lazy val laminextValidationCore: Dep = laminext("validation-core") - lazy val laminextUI: Dep = laminext("ui") - - lazy val waypoint: Dep = - ivy"com.raquo::waypoint::${V.waypoint}" - - lazy val urlDsl: Dep = - ivy"be.doeraene::url-dsl::${V.urlDsl}" - - lazy val scalaJavaTime: Dep = - ivy"io.github.cquiroz::scala-java-time::2.3.0" - - lazy val scalaJavaLocales: Dep = - ivy"io.github.cquiroz::scala-java-locales::1.2.1" - - lazy val logbackClassic: Dep = - ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" - } - - trait AkkaLibs { - - self: SlickLibs => - - object akka { - val V = Versions.akka - val tOrg = "com.typesafe.akka" - val lOrg = "com.lightbend.akka" - - def akkaMod(name: String): Dep = - ivy"$tOrg::akka-$name:$V" - - lazy val actor: Dep = akkaMod("actor") - lazy val actorTyped: Dep = akkaMod("actor-typed") - lazy val stream: Dep = akkaMod("stream") - lazy val persistence: Dep = akkaMod("persistence-typed") - lazy val persistenceQuery: Dep = akkaMod("persistence-query") - lazy val persistenceJdbc: Dep = - ivy"$lOrg::akka-persistence-jdbc:5.0.4" - val persistenceTestKit: Dep = ivy"$tOrg::akka-persistence-testkit:$V" - - object http { - val V = Versions.akkaHttp - - lazy val http: Dep = ivy"$tOrg::akka-http:$V" - lazy val sprayJson: Dep = ivy"$tOrg::akka-http-spray-json:$V" - - } - - object projection { - val V = "1.2.2" - - lazy val core: Dep = - ivy"$lOrg::akka-projection-core:$V" - lazy val eventsourced: Dep = - ivy"$lOrg::akka-projection-eventsourced:$V" - lazy val slick: Dep = - ivy"$lOrg::akka-projection-slick:$V" - lazy val jdbc: Dep = - ivy"$lOrg::akka-projection-jdbc:$V" - } - - object profiles { - // TODO: deal with cross-version for Scala3 (for3Use2_13) - lazy val eventsourcedJdbcProjection: Agg[Dep] = Agg( - persistenceQuery, - projection.core, - projection.eventsourced, - projection.slick, - persistenceJdbc - ) ++ slick.default - } - } - } - - trait SlickLibs { - object slick { - val V = IWMaterials.Versions.slick - val org = "com.typesafe.slick" - - lazy val slick: Dep = ivy"$org::slick:$V" - lazy val hikaricp: Dep = ivy"$org::slick-hikaricp:$V" - lazy val default: Agg[Dep] = Agg(slick, hikaricp) - } - } - -} diff --git a/iw/build.sc b/iw/build.sc deleted file mode 100644 index fbbc3b8..0000000 --- a/iw/build.sc +++ /dev/null @@ -1,144 +0,0 @@ -import mill._, scalalib._, scalajslib._ - -import $file.bom - -object support { - val Deps = bom.IWMaterials.Deps - val Versions = bom.IWMaterials.Versions - - trait CommonModule extends ScalaModule { - def scalaVersion = "3.1.1" - def scalacOptions = Seq( - "-encoding", - "utf8", - "-deprecation", - "-explain-types", - "-explain", - "-feature", - "-language:experimental.macros", - "-language:higherKinds", - "-language:implicitConversions", - "-unchecked", - "-Xfatal-warnings", - "-Ykind-projector" - ) - } - - trait CommonJSModule extends CommonModule with ScalaJSModule { - def scalaJSVersion = "1.8.0" - } - - trait CrossPlatformModule extends Module { outer => - def ivyDeps: T[Agg[Dep]] = Agg[Dep]() - def jsDeps: T[Agg[Dep]] = Agg[Dep]() - def jvmDeps: T[Agg[Dep]] = Agg[Dep]() - def moduleDeps: Seq[Module] = Seq[Module]() - - trait PlatformModule extends CommonModule { - def platform: String - override def millSourcePath = outer.millSourcePath - override def ivyDeps = outer.ivyDeps() ++ (platform match { - case "js" => jsDeps() - case _ => jvmDeps() - }) - override def moduleDeps = outer.moduleDeps.collect { - case m: CrossPlatformModule => - platform match { - case "js" => m.js - case _ => m.jvm - } - case m: JavaModule => m - } - } - - trait JsModule extends PlatformModule with CommonJSModule { - def platform = "js" - } - - trait JvmModule extends PlatformModule { - def platform = "jvm" - } - - val js: JsModule - val jvm: JvmModule - } - - trait PureCrossModule extends CrossPlatformModule { - override object js extends JsModule - override object jvm extends JvmModule - } - - trait PureCrossSbtModule extends CrossPlatformModule { - override object js extends JsModule with SbtModule - override object jvm extends JvmModule with SbtModule - } - - trait FullCrossSbtModule extends CrossPlatformModule { - trait FullSources extends JavaModule { self: PlatformModule => - override def sources = T.sources( - millSourcePath / platform / "src" / "main" / "scala", - millSourcePath / "shared" / "src" / "main" / "scala" - ) - - override def resources = T.sources( - millSourcePath / platform / "src" / "main" / "resources", - millSourcePath / "shared" / "src" / "main" / "resources" - ) - } - - override object js extends JsModule with FullSources - override object jvm extends JvmModule with FullSources - } - -} - -import support._ - -object mongo extends CommonModule { - def ivyDeps = Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - ivy"org.mongodb.scala::mongo-scala-driver:4.2.3".withDottyCompat( - scalaVersion() - ) - ) -} - -object tapir extends FullCrossSbtModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson, Deps.zioJson) - def jvmDeps = Agg(Deps.tapirZIO, Deps.tapirZIOHttp4sServer) - def jsDeps = Agg(Deps.tapirSttpClient) -} - -object akkaPersistence extends CommonModule { - def millSourcePath = build.millSourcePath / "akka-persistence" - def ivyDeps = (Agg( - Deps.akka.persistenceQuery, - Deps.akka.persistenceJdbc, - Deps.akka.projection.core, - Deps.akka.projection.eventsourced, - Deps.akka.projection.slick - ) ++ Deps.slick.default).map(_.withDottyCompat(scalaVersion())) ++ Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - Deps.akka.persistence.withDottyCompat(scalaVersion()), - ivy"com.typesafe.akka::akka-cluster-sharding-typed:${Deps.akka.V}" - .withDottyCompat(scalaVersion()) - ) -} - -object ui extends CommonJSModule { - def ivyDeps = Agg( - Deps.zio, - Deps.laminar, - Deps.zioJson, - Deps.waypoint, - Deps.urlDsl, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) -} diff --git a/iw/domain.sc b/iw/domain.sc deleted file mode 100644 index b09c56b..0000000 --- a/iw/domain.sc +++ /dev/null @@ -1,78 +0,0 @@ -import mill._, scalalib._ - -import $file.{build => ff}, ff.support._ - -trait DomainModule extends Module { - // General extra model deps - def modelModules: Seq[Module] = Seq.empty - // General extra codecs deps - def codecsModules: Seq[Module] = Seq.empty - // General extra endpoints deps - def endpointsModules: Seq[Module] = Seq.empty - - // Implementation deps for repo - def repoModules: Seq[JavaModule] = Seq.empty - - object shared extends Module { - object model extends PureCrossModule { - def moduleDeps = modelModules - def ivyDeps = Agg(Deps.zioPrelude) - } - object codecs extends PureCrossModule { - def moduleDeps = Seq(model) ++ codecsModules - def ivyDeps = Agg(Deps.zioJson) - } - } - - trait CommonProjects extends Module { - object model extends PureCrossModule { - def ivyDeps = Agg(Deps.zioPrelude) - def moduleDeps = Seq(shared.model) - } - object codecs extends PureCrossModule { - def moduleDeps = - Seq(model, shared.model, shared.codecs, ff.tapir) - } - object endpoints extends PureCrossModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson) - def moduleDeps = Seq(model, codecs) ++ endpointsModules - } - object client extends CommonJSModule { - def moduleDeps = Seq(endpoints.js) - } - object components extends CommonJSModule { - def ivyDeps = Agg( - Deps.laminar, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) - def moduleDeps = Seq(model.js, codecs.js, client, ff.ui) - } - } - - object query extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(repo, query.endpoints.jvm) - } - object repo extends CommonModule { - def ivyDeps = Agg(Deps.zio) - def moduleDeps = Seq(model.jvm, codecs.jvm) ++ repoModules - } - object projection extends CommonModule { - def moduleDeps = Seq(repo, ff.akkaPersistence) - } - } - - object command extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(entity, command.endpoints.jvm) - } - object entity extends CommonModule { - def moduleDeps = Seq(model.jvm, codecs.jvm, ff.akkaPersistence) - } - } -} diff --git a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala b/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala deleted file mode 100644 index 0fa3493..0000000 --- a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala +++ /dev/null @@ -1,53 +0,0 @@ -package works.iterative.mongo - -import zio.* -import zio.json.* -import zio.config.* -import org.mongodb.scala.* -import org.mongodb.scala.model.Filters.* -import org.bson.json.JsonObject -import org.mongodb.scala.model.ReplaceOptions -import org.mongodb.scala.bson.conversions.Bson - -case class MongoConfig(uri: String) - -object MongoConfig: - val configDesc = - import ConfigDescriptor.* - nested("MONGO")(string("URI").default("mongodb://localhost:27017")) - .to[MongoConfig] - val fromEnv = ZConfig.fromSystemEnv(configDesc) - -extension (m: MongoClient.type) - def layer: RLayer[MongoConfig, MongoClient] = - ZIO - .serviceWithZIO[MongoConfig](c => Task.attempt(MongoClient(c.uri))) - .toLayer - -class MongoJsonRepository[Elem, Key, Criteria]( - collection: MongoCollection[JsonObject], - toFilter: Criteria => Bson, - idFilter: Elem => (String, Key) -)(using JsonCodec[Elem]) { - def matching(criteria: Criteria): Task[Seq[Elem]] = - val filter = toFilter(criteria) - val query = collection.find(filter) - - for - result <- ZIO.fromFuture(_ => query.toFuture) - proof <- ZIO.collect(result)(j => - ZIO.fromOption(j.getJson.fromJson[Elem].toOption) - ) - yield proof - - def put(elem: Elem): Task[Unit] = - Task.async(cb => - collection - .replaceOne( - equal.tupled(idFilter(elem)), - JsonObject(elem.toJson), - ReplaceOptions().upsert(true) - ) - .subscribe(_ => cb(Task.unit), t => cb(Task.fail(t))) - ) -} diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala deleted file mode 100644 index 68d7196..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala +++ /dev/null @@ -1,12 +0,0 @@ -package works.iterative.tapir - -import sttp.model.Uri - -opaque type BaseUri = Option[Uri] - -object BaseUri: - - def apply(optU: Option[Uri]): BaseUri = optU - def apply(u: Uri): BaseUri = Some(u) - - extension (v: BaseUri) def toUri: Option[Uri] = v diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 653bc51..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,22 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.client.sttp.SttpClientInterpreter -import sttp.tapir.PublicEndpoint -import sttp.tapir.client.sttp.WebSocketToPipe -import scala.concurrent.Future -import sttp.client3.SttpBackend -import sttp.capabilities.WebSockets - -trait CustomTapirPlatformSpecific extends SttpClientInterpreter: - self: CustomTapir => - - type Backend = SttpBackend[Future, WebSockets] - - def makeClient[I, E, O]( - endpoint: PublicEndpoint[I, E, O, Any] - )(using - baseUri: BaseUri, - backend: Backend, - wsToPipe: WebSocketToPipe[Any] - ): I => Future[O] = - toClientThrowErrors(endpoint, baseUri.toUri, backend) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala deleted file mode 100644 index e7e6627..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.akka - -// Base class for all command-processing related exceptions from handlers -sealed abstract class CommandHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandNotAvailable[C, S](cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd není dostupný ve stavu $state" - ) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandRejected[C, S](reason: String, cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd byl ve stavu $state odmítnut s odůvodněním $reason" - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala deleted file mode 100644 index 72a5bf9..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.akka - -sealed abstract class EventHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -case class UnhandledEvent[Event, State](event: Event, state: State) - extends EventHandlerException( - s"Událost $event nastala ve stavu $state bez možnosti zpracování" - ) diff --git a/iw/bom.sc b/iw/bom.sc deleted file mode 100644 index e9266b8..0000000 --- a/iw/bom.sc +++ /dev/null @@ -1,227 +0,0 @@ -import mill._, scalalib._ - -object IWMaterials { - - object Versions { - val akka = "2.6.16" - val akkaHttp = "10.2.4" - val cats = "2.7.0" - val elastic4s = "7.12.2" - val http4s = "0.23.10" - val http4sPac4J = "4.0.0" - val laminar = "0.14.2" - val laminext = laminar - val logbackClassic = "1.2.10" - val pac4j = "5.2.0" - val play = "2.8.8" - val playJson = "2.9.2" - val scalaTest = "3.2.9" - val slick = "3.3.3" - val sttpClient = "3.5.0" - val tapir = "0.20.1" - val urlDsl = "0.4.0" - val waypoint = "0.5.0" - val zio = "2.0.0-RC2" - val zioConfig = "3.0.0-RC2" - val zioInteropCats = "3.3.0-RC2" - val zioJson = "0.3.0-RC3" - val zioLogging = "2.0.0-RC5" - val zioPrelude = "1.0.0-RC10" - val zioZMX = "0.0.11" - } - - object Deps extends AkkaLibs with SlickLibs { - import IWMaterials.{Versions => V} - - val zioOrg = "dev.zio" - - def zioLib(name: String, version: String): Dep = - ivy"$zioOrg::zio-$name::$version" - - lazy val zio: Dep = ivy"$zioOrg::zio:${V.zio}" - - lazy val zioTest: Dep = zioLib("test", V.zio) - lazy val zioTestSbt: Dep = zioLib("test", V.zio) - - lazy val zioConfig: Dep = zioLib("config", V.zioConfig) - lazy val zioConfigTypesafe: Dep = - zioLib("config-typesafe", V.zioConfig) - lazy val zioConfigMagnolia: Dep = - zioLib("config-magnolia", V.zioConfig) - - lazy val zioJson: Dep = zioLib("json", V.zioJson) - lazy val zioLogging: Dep = zioLib("logging", V.zioLogging) - lazy val zioLoggingSlf4j: Dep = - zioLib("logging-slf4j", V.zioLogging) - lazy val zioPrelude: Dep = zioLib("prelude", V.zioPrelude) - lazy val zioStreams: Dep = zioLib("streams", V.zio) - lazy val zioZMX: Dep = zioLib("zmx", V.zioZMX) - lazy val zioInteropCats: Dep = - zioLib("interop-cats", V.zioInteropCats) - - /* What is the equivalent? ZIOModule with prepared test config? - def useZIO(testConf: Configuration*): Agg[Dep] = Agg( - zio, - zioTest, - zioTestSbt, - testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework") - ) - */ - - /* - def useZIOAll(testConf: Configuration*): Seq[Def.Setting[_]] = - useZIO(testConf: _*) ++ Seq( - zioStreams, - zioConfig, - zioConfigTypesafe, - zioConfigMagnolia, - zioJson, - zioZMX, - zioLogging, - zioPrelude - ) - */ - - private val tapirOrg = "com.softwaremill.sttp.tapir" - def tapirLib(name: String): Dep = - ivy"${tapirOrg}::tapir-$name::${V.tapir}" - - lazy val tapirCore: Dep = tapirLib("core") - lazy val tapirZIO: Dep = tapirLib("zio") - lazy val tapirZIOJson: Dep = tapirLib("json-zio") - lazy val tapirSttpClient: Dep = tapirLib("sttp-client") - lazy val tapirCats: Dep = tapirLib("cats") - lazy val tapirZIOHttp4sServer: Dep = tapirLib("zio-http4s-server") - - private val sttpClientOrg = "com.softwaremill.sttp.client3" - def sttpClientLib(name: String): Dep = - ivy"${sttpClientOrg}::${name}:${V.sttpClient}" - - lazy val sttpClientCore: Dep = sttpClientLib("core") - - lazy val http4sBlazeServer: Dep = - ivy"org.http4s::http4s-blaze-server:${V.http4s}" - - lazy val http4sPac4J: Dep = - ivy"org.pac4j::http4s-pac4j:${V.http4sPac4J}" - lazy val pac4jOIDC: Dep = - ivy"org.pac4j:pac4j-oidc:${V.pac4j}" - - lazy val scalaTest: Dep = - ivy"org.scalatest::scalatest:${V.scalaTest}" - lazy val scalaTestPlusScalacheck: Dep = - ivy"org.scalatestplus::scalacheck-1-15:3.2.9.0" - lazy val playScalaTest: Dep = - ivy"org.scalatestplus.play::scalatestplus-play:5.1.0" - - private val playOrg = "com.typesafe.play" - lazy val playMailer: Dep = ivy"${playOrg}::play-mailer:8.0.1" - lazy val playServer: Dep = ivy"${playOrg}::play-server:${V.play}" - lazy val playAkkaServer: Dep = - ivy"${playOrg}::play-akka-http-server:${V.play}" - lazy val play: Dep = ivy"${playOrg}::play:${V.play}" - lazy val playAhcWs: Dep = ivy"${playOrg}::play-ahc-ws:${V.play}" - lazy val playJson: Dep = ivy"${playOrg}::play-json:${V.playJson}" - - private val elastic4sOrg = "com.sksamuel.elastic4s" - lazy val useElastic4S: Agg[Dep] = Agg( - ivy"${elastic4sOrg}::elastic4s-core:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-client-akka:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-http-streams:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-json-play:${V.elastic4s}" - ) - - lazy val laminar: Dep = ivy"com.raquo::laminar::${V.laminar}" - - private def laminext(name: String): Dep = - ivy"io.laminext::$name::${V.laminar}" - - lazy val laminextCore: Dep = laminext("core") - lazy val laminextTailwind: Dep = laminext("tailwind") - lazy val laminextFetch: Dep = laminext("fetch") - lazy val laminextValidationCore: Dep = laminext("validation-core") - lazy val laminextUI: Dep = laminext("ui") - - lazy val waypoint: Dep = - ivy"com.raquo::waypoint::${V.waypoint}" - - lazy val urlDsl: Dep = - ivy"be.doeraene::url-dsl::${V.urlDsl}" - - lazy val scalaJavaTime: Dep = - ivy"io.github.cquiroz::scala-java-time::2.3.0" - - lazy val scalaJavaLocales: Dep = - ivy"io.github.cquiroz::scala-java-locales::1.2.1" - - lazy val logbackClassic: Dep = - ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" - } - - trait AkkaLibs { - - self: SlickLibs => - - object akka { - val V = Versions.akka - val tOrg = "com.typesafe.akka" - val lOrg = "com.lightbend.akka" - - def akkaMod(name: String): Dep = - ivy"$tOrg::akka-$name:$V" - - lazy val actor: Dep = akkaMod("actor") - lazy val actorTyped: Dep = akkaMod("actor-typed") - lazy val stream: Dep = akkaMod("stream") - lazy val persistence: Dep = akkaMod("persistence-typed") - lazy val persistenceQuery: Dep = akkaMod("persistence-query") - lazy val persistenceJdbc: Dep = - ivy"$lOrg::akka-persistence-jdbc:5.0.4" - val persistenceTestKit: Dep = ivy"$tOrg::akka-persistence-testkit:$V" - - object http { - val V = Versions.akkaHttp - - lazy val http: Dep = ivy"$tOrg::akka-http:$V" - lazy val sprayJson: Dep = ivy"$tOrg::akka-http-spray-json:$V" - - } - - object projection { - val V = "1.2.2" - - lazy val core: Dep = - ivy"$lOrg::akka-projection-core:$V" - lazy val eventsourced: Dep = - ivy"$lOrg::akka-projection-eventsourced:$V" - lazy val slick: Dep = - ivy"$lOrg::akka-projection-slick:$V" - lazy val jdbc: Dep = - ivy"$lOrg::akka-projection-jdbc:$V" - } - - object profiles { - // TODO: deal with cross-version for Scala3 (for3Use2_13) - lazy val eventsourcedJdbcProjection: Agg[Dep] = Agg( - persistenceQuery, - projection.core, - projection.eventsourced, - projection.slick, - persistenceJdbc - ) ++ slick.default - } - } - } - - trait SlickLibs { - object slick { - val V = IWMaterials.Versions.slick - val org = "com.typesafe.slick" - - lazy val slick: Dep = ivy"$org::slick:$V" - lazy val hikaricp: Dep = ivy"$org::slick-hikaricp:$V" - lazy val default: Agg[Dep] = Agg(slick, hikaricp) - } - } - -} diff --git a/iw/build.sc b/iw/build.sc deleted file mode 100644 index fbbc3b8..0000000 --- a/iw/build.sc +++ /dev/null @@ -1,144 +0,0 @@ -import mill._, scalalib._, scalajslib._ - -import $file.bom - -object support { - val Deps = bom.IWMaterials.Deps - val Versions = bom.IWMaterials.Versions - - trait CommonModule extends ScalaModule { - def scalaVersion = "3.1.1" - def scalacOptions = Seq( - "-encoding", - "utf8", - "-deprecation", - "-explain-types", - "-explain", - "-feature", - "-language:experimental.macros", - "-language:higherKinds", - "-language:implicitConversions", - "-unchecked", - "-Xfatal-warnings", - "-Ykind-projector" - ) - } - - trait CommonJSModule extends CommonModule with ScalaJSModule { - def scalaJSVersion = "1.8.0" - } - - trait CrossPlatformModule extends Module { outer => - def ivyDeps: T[Agg[Dep]] = Agg[Dep]() - def jsDeps: T[Agg[Dep]] = Agg[Dep]() - def jvmDeps: T[Agg[Dep]] = Agg[Dep]() - def moduleDeps: Seq[Module] = Seq[Module]() - - trait PlatformModule extends CommonModule { - def platform: String - override def millSourcePath = outer.millSourcePath - override def ivyDeps = outer.ivyDeps() ++ (platform match { - case "js" => jsDeps() - case _ => jvmDeps() - }) - override def moduleDeps = outer.moduleDeps.collect { - case m: CrossPlatformModule => - platform match { - case "js" => m.js - case _ => m.jvm - } - case m: JavaModule => m - } - } - - trait JsModule extends PlatformModule with CommonJSModule { - def platform = "js" - } - - trait JvmModule extends PlatformModule { - def platform = "jvm" - } - - val js: JsModule - val jvm: JvmModule - } - - trait PureCrossModule extends CrossPlatformModule { - override object js extends JsModule - override object jvm extends JvmModule - } - - trait PureCrossSbtModule extends CrossPlatformModule { - override object js extends JsModule with SbtModule - override object jvm extends JvmModule with SbtModule - } - - trait FullCrossSbtModule extends CrossPlatformModule { - trait FullSources extends JavaModule { self: PlatformModule => - override def sources = T.sources( - millSourcePath / platform / "src" / "main" / "scala", - millSourcePath / "shared" / "src" / "main" / "scala" - ) - - override def resources = T.sources( - millSourcePath / platform / "src" / "main" / "resources", - millSourcePath / "shared" / "src" / "main" / "resources" - ) - } - - override object js extends JsModule with FullSources - override object jvm extends JvmModule with FullSources - } - -} - -import support._ - -object mongo extends CommonModule { - def ivyDeps = Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - ivy"org.mongodb.scala::mongo-scala-driver:4.2.3".withDottyCompat( - scalaVersion() - ) - ) -} - -object tapir extends FullCrossSbtModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson, Deps.zioJson) - def jvmDeps = Agg(Deps.tapirZIO, Deps.tapirZIOHttp4sServer) - def jsDeps = Agg(Deps.tapirSttpClient) -} - -object akkaPersistence extends CommonModule { - def millSourcePath = build.millSourcePath / "akka-persistence" - def ivyDeps = (Agg( - Deps.akka.persistenceQuery, - Deps.akka.persistenceJdbc, - Deps.akka.projection.core, - Deps.akka.projection.eventsourced, - Deps.akka.projection.slick - ) ++ Deps.slick.default).map(_.withDottyCompat(scalaVersion())) ++ Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - Deps.akka.persistence.withDottyCompat(scalaVersion()), - ivy"com.typesafe.akka::akka-cluster-sharding-typed:${Deps.akka.V}" - .withDottyCompat(scalaVersion()) - ) -} - -object ui extends CommonJSModule { - def ivyDeps = Agg( - Deps.zio, - Deps.laminar, - Deps.zioJson, - Deps.waypoint, - Deps.urlDsl, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) -} diff --git a/iw/domain.sc b/iw/domain.sc deleted file mode 100644 index b09c56b..0000000 --- a/iw/domain.sc +++ /dev/null @@ -1,78 +0,0 @@ -import mill._, scalalib._ - -import $file.{build => ff}, ff.support._ - -trait DomainModule extends Module { - // General extra model deps - def modelModules: Seq[Module] = Seq.empty - // General extra codecs deps - def codecsModules: Seq[Module] = Seq.empty - // General extra endpoints deps - def endpointsModules: Seq[Module] = Seq.empty - - // Implementation deps for repo - def repoModules: Seq[JavaModule] = Seq.empty - - object shared extends Module { - object model extends PureCrossModule { - def moduleDeps = modelModules - def ivyDeps = Agg(Deps.zioPrelude) - } - object codecs extends PureCrossModule { - def moduleDeps = Seq(model) ++ codecsModules - def ivyDeps = Agg(Deps.zioJson) - } - } - - trait CommonProjects extends Module { - object model extends PureCrossModule { - def ivyDeps = Agg(Deps.zioPrelude) - def moduleDeps = Seq(shared.model) - } - object codecs extends PureCrossModule { - def moduleDeps = - Seq(model, shared.model, shared.codecs, ff.tapir) - } - object endpoints extends PureCrossModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson) - def moduleDeps = Seq(model, codecs) ++ endpointsModules - } - object client extends CommonJSModule { - def moduleDeps = Seq(endpoints.js) - } - object components extends CommonJSModule { - def ivyDeps = Agg( - Deps.laminar, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) - def moduleDeps = Seq(model.js, codecs.js, client, ff.ui) - } - } - - object query extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(repo, query.endpoints.jvm) - } - object repo extends CommonModule { - def ivyDeps = Agg(Deps.zio) - def moduleDeps = Seq(model.jvm, codecs.jvm) ++ repoModules - } - object projection extends CommonModule { - def moduleDeps = Seq(repo, ff.akkaPersistence) - } - } - - object command extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(entity, command.endpoints.jvm) - } - object entity extends CommonModule { - def moduleDeps = Seq(model.jvm, codecs.jvm, ff.akkaPersistence) - } - } -} diff --git a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala b/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala deleted file mode 100644 index 0fa3493..0000000 --- a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala +++ /dev/null @@ -1,53 +0,0 @@ -package works.iterative.mongo - -import zio.* -import zio.json.* -import zio.config.* -import org.mongodb.scala.* -import org.mongodb.scala.model.Filters.* -import org.bson.json.JsonObject -import org.mongodb.scala.model.ReplaceOptions -import org.mongodb.scala.bson.conversions.Bson - -case class MongoConfig(uri: String) - -object MongoConfig: - val configDesc = - import ConfigDescriptor.* - nested("MONGO")(string("URI").default("mongodb://localhost:27017")) - .to[MongoConfig] - val fromEnv = ZConfig.fromSystemEnv(configDesc) - -extension (m: MongoClient.type) - def layer: RLayer[MongoConfig, MongoClient] = - ZIO - .serviceWithZIO[MongoConfig](c => Task.attempt(MongoClient(c.uri))) - .toLayer - -class MongoJsonRepository[Elem, Key, Criteria]( - collection: MongoCollection[JsonObject], - toFilter: Criteria => Bson, - idFilter: Elem => (String, Key) -)(using JsonCodec[Elem]) { - def matching(criteria: Criteria): Task[Seq[Elem]] = - val filter = toFilter(criteria) - val query = collection.find(filter) - - for - result <- ZIO.fromFuture(_ => query.toFuture) - proof <- ZIO.collect(result)(j => - ZIO.fromOption(j.getJson.fromJson[Elem].toOption) - ) - yield proof - - def put(elem: Elem): Task[Unit] = - Task.async(cb => - collection - .replaceOne( - equal.tupled(idFilter(elem)), - JsonObject(elem.toJson), - ReplaceOptions().upsert(true) - ) - .subscribe(_ => cb(Task.unit), t => cb(Task.fail(t))) - ) -} diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala deleted file mode 100644 index 68d7196..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala +++ /dev/null @@ -1,12 +0,0 @@ -package works.iterative.tapir - -import sttp.model.Uri - -opaque type BaseUri = Option[Uri] - -object BaseUri: - - def apply(optU: Option[Uri]): BaseUri = optU - def apply(u: Uri): BaseUri = Some(u) - - extension (v: BaseUri) def toUri: Option[Uri] = v diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 653bc51..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,22 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.client.sttp.SttpClientInterpreter -import sttp.tapir.PublicEndpoint -import sttp.tapir.client.sttp.WebSocketToPipe -import scala.concurrent.Future -import sttp.client3.SttpBackend -import sttp.capabilities.WebSockets - -trait CustomTapirPlatformSpecific extends SttpClientInterpreter: - self: CustomTapir => - - type Backend = SttpBackend[Future, WebSockets] - - def makeClient[I, E, O]( - endpoint: PublicEndpoint[I, E, O, Any] - )(using - baseUri: BaseUri, - backend: Backend, - wsToPipe: WebSocketToPipe[Any] - ): I => Future[O] = - toClientThrowErrors(endpoint, baseUri.toUri, backend) diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 2545fbd..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,5 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.ztapir.ZTapir - -trait CustomTapirPlatformSpecific extends ZTapir diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala deleted file mode 100644 index e7e6627..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.akka - -// Base class for all command-processing related exceptions from handlers -sealed abstract class CommandHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandNotAvailable[C, S](cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd není dostupný ve stavu $state" - ) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandRejected[C, S](reason: String, cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd byl ve stavu $state odmítnut s odůvodněním $reason" - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala deleted file mode 100644 index 72a5bf9..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.akka - -sealed abstract class EventHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -case class UnhandledEvent[Event, State](event: Event, state: State) - extends EventHandlerException( - s"Událost $event nastala ve stavu $state bez možnosti zpracování" - ) diff --git a/iw/bom.sc b/iw/bom.sc deleted file mode 100644 index e9266b8..0000000 --- a/iw/bom.sc +++ /dev/null @@ -1,227 +0,0 @@ -import mill._, scalalib._ - -object IWMaterials { - - object Versions { - val akka = "2.6.16" - val akkaHttp = "10.2.4" - val cats = "2.7.0" - val elastic4s = "7.12.2" - val http4s = "0.23.10" - val http4sPac4J = "4.0.0" - val laminar = "0.14.2" - val laminext = laminar - val logbackClassic = "1.2.10" - val pac4j = "5.2.0" - val play = "2.8.8" - val playJson = "2.9.2" - val scalaTest = "3.2.9" - val slick = "3.3.3" - val sttpClient = "3.5.0" - val tapir = "0.20.1" - val urlDsl = "0.4.0" - val waypoint = "0.5.0" - val zio = "2.0.0-RC2" - val zioConfig = "3.0.0-RC2" - val zioInteropCats = "3.3.0-RC2" - val zioJson = "0.3.0-RC3" - val zioLogging = "2.0.0-RC5" - val zioPrelude = "1.0.0-RC10" - val zioZMX = "0.0.11" - } - - object Deps extends AkkaLibs with SlickLibs { - import IWMaterials.{Versions => V} - - val zioOrg = "dev.zio" - - def zioLib(name: String, version: String): Dep = - ivy"$zioOrg::zio-$name::$version" - - lazy val zio: Dep = ivy"$zioOrg::zio:${V.zio}" - - lazy val zioTest: Dep = zioLib("test", V.zio) - lazy val zioTestSbt: Dep = zioLib("test", V.zio) - - lazy val zioConfig: Dep = zioLib("config", V.zioConfig) - lazy val zioConfigTypesafe: Dep = - zioLib("config-typesafe", V.zioConfig) - lazy val zioConfigMagnolia: Dep = - zioLib("config-magnolia", V.zioConfig) - - lazy val zioJson: Dep = zioLib("json", V.zioJson) - lazy val zioLogging: Dep = zioLib("logging", V.zioLogging) - lazy val zioLoggingSlf4j: Dep = - zioLib("logging-slf4j", V.zioLogging) - lazy val zioPrelude: Dep = zioLib("prelude", V.zioPrelude) - lazy val zioStreams: Dep = zioLib("streams", V.zio) - lazy val zioZMX: Dep = zioLib("zmx", V.zioZMX) - lazy val zioInteropCats: Dep = - zioLib("interop-cats", V.zioInteropCats) - - /* What is the equivalent? ZIOModule with prepared test config? - def useZIO(testConf: Configuration*): Agg[Dep] = Agg( - zio, - zioTest, - zioTestSbt, - testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework") - ) - */ - - /* - def useZIOAll(testConf: Configuration*): Seq[Def.Setting[_]] = - useZIO(testConf: _*) ++ Seq( - zioStreams, - zioConfig, - zioConfigTypesafe, - zioConfigMagnolia, - zioJson, - zioZMX, - zioLogging, - zioPrelude - ) - */ - - private val tapirOrg = "com.softwaremill.sttp.tapir" - def tapirLib(name: String): Dep = - ivy"${tapirOrg}::tapir-$name::${V.tapir}" - - lazy val tapirCore: Dep = tapirLib("core") - lazy val tapirZIO: Dep = tapirLib("zio") - lazy val tapirZIOJson: Dep = tapirLib("json-zio") - lazy val tapirSttpClient: Dep = tapirLib("sttp-client") - lazy val tapirCats: Dep = tapirLib("cats") - lazy val tapirZIOHttp4sServer: Dep = tapirLib("zio-http4s-server") - - private val sttpClientOrg = "com.softwaremill.sttp.client3" - def sttpClientLib(name: String): Dep = - ivy"${sttpClientOrg}::${name}:${V.sttpClient}" - - lazy val sttpClientCore: Dep = sttpClientLib("core") - - lazy val http4sBlazeServer: Dep = - ivy"org.http4s::http4s-blaze-server:${V.http4s}" - - lazy val http4sPac4J: Dep = - ivy"org.pac4j::http4s-pac4j:${V.http4sPac4J}" - lazy val pac4jOIDC: Dep = - ivy"org.pac4j:pac4j-oidc:${V.pac4j}" - - lazy val scalaTest: Dep = - ivy"org.scalatest::scalatest:${V.scalaTest}" - lazy val scalaTestPlusScalacheck: Dep = - ivy"org.scalatestplus::scalacheck-1-15:3.2.9.0" - lazy val playScalaTest: Dep = - ivy"org.scalatestplus.play::scalatestplus-play:5.1.0" - - private val playOrg = "com.typesafe.play" - lazy val playMailer: Dep = ivy"${playOrg}::play-mailer:8.0.1" - lazy val playServer: Dep = ivy"${playOrg}::play-server:${V.play}" - lazy val playAkkaServer: Dep = - ivy"${playOrg}::play-akka-http-server:${V.play}" - lazy val play: Dep = ivy"${playOrg}::play:${V.play}" - lazy val playAhcWs: Dep = ivy"${playOrg}::play-ahc-ws:${V.play}" - lazy val playJson: Dep = ivy"${playOrg}::play-json:${V.playJson}" - - private val elastic4sOrg = "com.sksamuel.elastic4s" - lazy val useElastic4S: Agg[Dep] = Agg( - ivy"${elastic4sOrg}::elastic4s-core:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-client-akka:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-http-streams:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-json-play:${V.elastic4s}" - ) - - lazy val laminar: Dep = ivy"com.raquo::laminar::${V.laminar}" - - private def laminext(name: String): Dep = - ivy"io.laminext::$name::${V.laminar}" - - lazy val laminextCore: Dep = laminext("core") - lazy val laminextTailwind: Dep = laminext("tailwind") - lazy val laminextFetch: Dep = laminext("fetch") - lazy val laminextValidationCore: Dep = laminext("validation-core") - lazy val laminextUI: Dep = laminext("ui") - - lazy val waypoint: Dep = - ivy"com.raquo::waypoint::${V.waypoint}" - - lazy val urlDsl: Dep = - ivy"be.doeraene::url-dsl::${V.urlDsl}" - - lazy val scalaJavaTime: Dep = - ivy"io.github.cquiroz::scala-java-time::2.3.0" - - lazy val scalaJavaLocales: Dep = - ivy"io.github.cquiroz::scala-java-locales::1.2.1" - - lazy val logbackClassic: Dep = - ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" - } - - trait AkkaLibs { - - self: SlickLibs => - - object akka { - val V = Versions.akka - val tOrg = "com.typesafe.akka" - val lOrg = "com.lightbend.akka" - - def akkaMod(name: String): Dep = - ivy"$tOrg::akka-$name:$V" - - lazy val actor: Dep = akkaMod("actor") - lazy val actorTyped: Dep = akkaMod("actor-typed") - lazy val stream: Dep = akkaMod("stream") - lazy val persistence: Dep = akkaMod("persistence-typed") - lazy val persistenceQuery: Dep = akkaMod("persistence-query") - lazy val persistenceJdbc: Dep = - ivy"$lOrg::akka-persistence-jdbc:5.0.4" - val persistenceTestKit: Dep = ivy"$tOrg::akka-persistence-testkit:$V" - - object http { - val V = Versions.akkaHttp - - lazy val http: Dep = ivy"$tOrg::akka-http:$V" - lazy val sprayJson: Dep = ivy"$tOrg::akka-http-spray-json:$V" - - } - - object projection { - val V = "1.2.2" - - lazy val core: Dep = - ivy"$lOrg::akka-projection-core:$V" - lazy val eventsourced: Dep = - ivy"$lOrg::akka-projection-eventsourced:$V" - lazy val slick: Dep = - ivy"$lOrg::akka-projection-slick:$V" - lazy val jdbc: Dep = - ivy"$lOrg::akka-projection-jdbc:$V" - } - - object profiles { - // TODO: deal with cross-version for Scala3 (for3Use2_13) - lazy val eventsourcedJdbcProjection: Agg[Dep] = Agg( - persistenceQuery, - projection.core, - projection.eventsourced, - projection.slick, - persistenceJdbc - ) ++ slick.default - } - } - } - - trait SlickLibs { - object slick { - val V = IWMaterials.Versions.slick - val org = "com.typesafe.slick" - - lazy val slick: Dep = ivy"$org::slick:$V" - lazy val hikaricp: Dep = ivy"$org::slick-hikaricp:$V" - lazy val default: Agg[Dep] = Agg(slick, hikaricp) - } - } - -} diff --git a/iw/build.sc b/iw/build.sc deleted file mode 100644 index fbbc3b8..0000000 --- a/iw/build.sc +++ /dev/null @@ -1,144 +0,0 @@ -import mill._, scalalib._, scalajslib._ - -import $file.bom - -object support { - val Deps = bom.IWMaterials.Deps - val Versions = bom.IWMaterials.Versions - - trait CommonModule extends ScalaModule { - def scalaVersion = "3.1.1" - def scalacOptions = Seq( - "-encoding", - "utf8", - "-deprecation", - "-explain-types", - "-explain", - "-feature", - "-language:experimental.macros", - "-language:higherKinds", - "-language:implicitConversions", - "-unchecked", - "-Xfatal-warnings", - "-Ykind-projector" - ) - } - - trait CommonJSModule extends CommonModule with ScalaJSModule { - def scalaJSVersion = "1.8.0" - } - - trait CrossPlatformModule extends Module { outer => - def ivyDeps: T[Agg[Dep]] = Agg[Dep]() - def jsDeps: T[Agg[Dep]] = Agg[Dep]() - def jvmDeps: T[Agg[Dep]] = Agg[Dep]() - def moduleDeps: Seq[Module] = Seq[Module]() - - trait PlatformModule extends CommonModule { - def platform: String - override def millSourcePath = outer.millSourcePath - override def ivyDeps = outer.ivyDeps() ++ (platform match { - case "js" => jsDeps() - case _ => jvmDeps() - }) - override def moduleDeps = outer.moduleDeps.collect { - case m: CrossPlatformModule => - platform match { - case "js" => m.js - case _ => m.jvm - } - case m: JavaModule => m - } - } - - trait JsModule extends PlatformModule with CommonJSModule { - def platform = "js" - } - - trait JvmModule extends PlatformModule { - def platform = "jvm" - } - - val js: JsModule - val jvm: JvmModule - } - - trait PureCrossModule extends CrossPlatformModule { - override object js extends JsModule - override object jvm extends JvmModule - } - - trait PureCrossSbtModule extends CrossPlatformModule { - override object js extends JsModule with SbtModule - override object jvm extends JvmModule with SbtModule - } - - trait FullCrossSbtModule extends CrossPlatformModule { - trait FullSources extends JavaModule { self: PlatformModule => - override def sources = T.sources( - millSourcePath / platform / "src" / "main" / "scala", - millSourcePath / "shared" / "src" / "main" / "scala" - ) - - override def resources = T.sources( - millSourcePath / platform / "src" / "main" / "resources", - millSourcePath / "shared" / "src" / "main" / "resources" - ) - } - - override object js extends JsModule with FullSources - override object jvm extends JvmModule with FullSources - } - -} - -import support._ - -object mongo extends CommonModule { - def ivyDeps = Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - ivy"org.mongodb.scala::mongo-scala-driver:4.2.3".withDottyCompat( - scalaVersion() - ) - ) -} - -object tapir extends FullCrossSbtModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson, Deps.zioJson) - def jvmDeps = Agg(Deps.tapirZIO, Deps.tapirZIOHttp4sServer) - def jsDeps = Agg(Deps.tapirSttpClient) -} - -object akkaPersistence extends CommonModule { - def millSourcePath = build.millSourcePath / "akka-persistence" - def ivyDeps = (Agg( - Deps.akka.persistenceQuery, - Deps.akka.persistenceJdbc, - Deps.akka.projection.core, - Deps.akka.projection.eventsourced, - Deps.akka.projection.slick - ) ++ Deps.slick.default).map(_.withDottyCompat(scalaVersion())) ++ Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - Deps.akka.persistence.withDottyCompat(scalaVersion()), - ivy"com.typesafe.akka::akka-cluster-sharding-typed:${Deps.akka.V}" - .withDottyCompat(scalaVersion()) - ) -} - -object ui extends CommonJSModule { - def ivyDeps = Agg( - Deps.zio, - Deps.laminar, - Deps.zioJson, - Deps.waypoint, - Deps.urlDsl, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) -} diff --git a/iw/domain.sc b/iw/domain.sc deleted file mode 100644 index b09c56b..0000000 --- a/iw/domain.sc +++ /dev/null @@ -1,78 +0,0 @@ -import mill._, scalalib._ - -import $file.{build => ff}, ff.support._ - -trait DomainModule extends Module { - // General extra model deps - def modelModules: Seq[Module] = Seq.empty - // General extra codecs deps - def codecsModules: Seq[Module] = Seq.empty - // General extra endpoints deps - def endpointsModules: Seq[Module] = Seq.empty - - // Implementation deps for repo - def repoModules: Seq[JavaModule] = Seq.empty - - object shared extends Module { - object model extends PureCrossModule { - def moduleDeps = modelModules - def ivyDeps = Agg(Deps.zioPrelude) - } - object codecs extends PureCrossModule { - def moduleDeps = Seq(model) ++ codecsModules - def ivyDeps = Agg(Deps.zioJson) - } - } - - trait CommonProjects extends Module { - object model extends PureCrossModule { - def ivyDeps = Agg(Deps.zioPrelude) - def moduleDeps = Seq(shared.model) - } - object codecs extends PureCrossModule { - def moduleDeps = - Seq(model, shared.model, shared.codecs, ff.tapir) - } - object endpoints extends PureCrossModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson) - def moduleDeps = Seq(model, codecs) ++ endpointsModules - } - object client extends CommonJSModule { - def moduleDeps = Seq(endpoints.js) - } - object components extends CommonJSModule { - def ivyDeps = Agg( - Deps.laminar, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) - def moduleDeps = Seq(model.js, codecs.js, client, ff.ui) - } - } - - object query extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(repo, query.endpoints.jvm) - } - object repo extends CommonModule { - def ivyDeps = Agg(Deps.zio) - def moduleDeps = Seq(model.jvm, codecs.jvm) ++ repoModules - } - object projection extends CommonModule { - def moduleDeps = Seq(repo, ff.akkaPersistence) - } - } - - object command extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(entity, command.endpoints.jvm) - } - object entity extends CommonModule { - def moduleDeps = Seq(model.jvm, codecs.jvm, ff.akkaPersistence) - } - } -} diff --git a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala b/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala deleted file mode 100644 index 0fa3493..0000000 --- a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala +++ /dev/null @@ -1,53 +0,0 @@ -package works.iterative.mongo - -import zio.* -import zio.json.* -import zio.config.* -import org.mongodb.scala.* -import org.mongodb.scala.model.Filters.* -import org.bson.json.JsonObject -import org.mongodb.scala.model.ReplaceOptions -import org.mongodb.scala.bson.conversions.Bson - -case class MongoConfig(uri: String) - -object MongoConfig: - val configDesc = - import ConfigDescriptor.* - nested("MONGO")(string("URI").default("mongodb://localhost:27017")) - .to[MongoConfig] - val fromEnv = ZConfig.fromSystemEnv(configDesc) - -extension (m: MongoClient.type) - def layer: RLayer[MongoConfig, MongoClient] = - ZIO - .serviceWithZIO[MongoConfig](c => Task.attempt(MongoClient(c.uri))) - .toLayer - -class MongoJsonRepository[Elem, Key, Criteria]( - collection: MongoCollection[JsonObject], - toFilter: Criteria => Bson, - idFilter: Elem => (String, Key) -)(using JsonCodec[Elem]) { - def matching(criteria: Criteria): Task[Seq[Elem]] = - val filter = toFilter(criteria) - val query = collection.find(filter) - - for - result <- ZIO.fromFuture(_ => query.toFuture) - proof <- ZIO.collect(result)(j => - ZIO.fromOption(j.getJson.fromJson[Elem].toOption) - ) - yield proof - - def put(elem: Elem): Task[Unit] = - Task.async(cb => - collection - .replaceOne( - equal.tupled(idFilter(elem)), - JsonObject(elem.toJson), - ReplaceOptions().upsert(true) - ) - .subscribe(_ => cb(Task.unit), t => cb(Task.fail(t))) - ) -} diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala deleted file mode 100644 index 68d7196..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala +++ /dev/null @@ -1,12 +0,0 @@ -package works.iterative.tapir - -import sttp.model.Uri - -opaque type BaseUri = Option[Uri] - -object BaseUri: - - def apply(optU: Option[Uri]): BaseUri = optU - def apply(u: Uri): BaseUri = Some(u) - - extension (v: BaseUri) def toUri: Option[Uri] = v diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 653bc51..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,22 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.client.sttp.SttpClientInterpreter -import sttp.tapir.PublicEndpoint -import sttp.tapir.client.sttp.WebSocketToPipe -import scala.concurrent.Future -import sttp.client3.SttpBackend -import sttp.capabilities.WebSockets - -trait CustomTapirPlatformSpecific extends SttpClientInterpreter: - self: CustomTapir => - - type Backend = SttpBackend[Future, WebSockets] - - def makeClient[I, E, O]( - endpoint: PublicEndpoint[I, E, O, Any] - )(using - baseUri: BaseUri, - backend: Backend, - wsToPipe: WebSocketToPipe[Any] - ): I => Future[O] = - toClientThrowErrors(endpoint, baseUri.toUri, backend) diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 2545fbd..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,5 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.ztapir.ZTapir - -trait CustomTapirPlatformSpecific extends ZTapir diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala deleted file mode 100644 index 55e4ca1..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala +++ /dev/null @@ -1,7 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.server.http4s.ztapir.ZHttp4sServerInterpreter - -trait Http4sCustomTapir[Env] - extends CustomTapir - with ZHttp4sServerInterpreter[Env] diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala deleted file mode 100644 index e7e6627..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.akka - -// Base class for all command-processing related exceptions from handlers -sealed abstract class CommandHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandNotAvailable[C, S](cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd není dostupný ve stavu $state" - ) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandRejected[C, S](reason: String, cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd byl ve stavu $state odmítnut s odůvodněním $reason" - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala deleted file mode 100644 index 72a5bf9..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.akka - -sealed abstract class EventHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -case class UnhandledEvent[Event, State](event: Event, state: State) - extends EventHandlerException( - s"Událost $event nastala ve stavu $state bez možnosti zpracování" - ) diff --git a/iw/bom.sc b/iw/bom.sc deleted file mode 100644 index e9266b8..0000000 --- a/iw/bom.sc +++ /dev/null @@ -1,227 +0,0 @@ -import mill._, scalalib._ - -object IWMaterials { - - object Versions { - val akka = "2.6.16" - val akkaHttp = "10.2.4" - val cats = "2.7.0" - val elastic4s = "7.12.2" - val http4s = "0.23.10" - val http4sPac4J = "4.0.0" - val laminar = "0.14.2" - val laminext = laminar - val logbackClassic = "1.2.10" - val pac4j = "5.2.0" - val play = "2.8.8" - val playJson = "2.9.2" - val scalaTest = "3.2.9" - val slick = "3.3.3" - val sttpClient = "3.5.0" - val tapir = "0.20.1" - val urlDsl = "0.4.0" - val waypoint = "0.5.0" - val zio = "2.0.0-RC2" - val zioConfig = "3.0.0-RC2" - val zioInteropCats = "3.3.0-RC2" - val zioJson = "0.3.0-RC3" - val zioLogging = "2.0.0-RC5" - val zioPrelude = "1.0.0-RC10" - val zioZMX = "0.0.11" - } - - object Deps extends AkkaLibs with SlickLibs { - import IWMaterials.{Versions => V} - - val zioOrg = "dev.zio" - - def zioLib(name: String, version: String): Dep = - ivy"$zioOrg::zio-$name::$version" - - lazy val zio: Dep = ivy"$zioOrg::zio:${V.zio}" - - lazy val zioTest: Dep = zioLib("test", V.zio) - lazy val zioTestSbt: Dep = zioLib("test", V.zio) - - lazy val zioConfig: Dep = zioLib("config", V.zioConfig) - lazy val zioConfigTypesafe: Dep = - zioLib("config-typesafe", V.zioConfig) - lazy val zioConfigMagnolia: Dep = - zioLib("config-magnolia", V.zioConfig) - - lazy val zioJson: Dep = zioLib("json", V.zioJson) - lazy val zioLogging: Dep = zioLib("logging", V.zioLogging) - lazy val zioLoggingSlf4j: Dep = - zioLib("logging-slf4j", V.zioLogging) - lazy val zioPrelude: Dep = zioLib("prelude", V.zioPrelude) - lazy val zioStreams: Dep = zioLib("streams", V.zio) - lazy val zioZMX: Dep = zioLib("zmx", V.zioZMX) - lazy val zioInteropCats: Dep = - zioLib("interop-cats", V.zioInteropCats) - - /* What is the equivalent? ZIOModule with prepared test config? - def useZIO(testConf: Configuration*): Agg[Dep] = Agg( - zio, - zioTest, - zioTestSbt, - testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework") - ) - */ - - /* - def useZIOAll(testConf: Configuration*): Seq[Def.Setting[_]] = - useZIO(testConf: _*) ++ Seq( - zioStreams, - zioConfig, - zioConfigTypesafe, - zioConfigMagnolia, - zioJson, - zioZMX, - zioLogging, - zioPrelude - ) - */ - - private val tapirOrg = "com.softwaremill.sttp.tapir" - def tapirLib(name: String): Dep = - ivy"${tapirOrg}::tapir-$name::${V.tapir}" - - lazy val tapirCore: Dep = tapirLib("core") - lazy val tapirZIO: Dep = tapirLib("zio") - lazy val tapirZIOJson: Dep = tapirLib("json-zio") - lazy val tapirSttpClient: Dep = tapirLib("sttp-client") - lazy val tapirCats: Dep = tapirLib("cats") - lazy val tapirZIOHttp4sServer: Dep = tapirLib("zio-http4s-server") - - private val sttpClientOrg = "com.softwaremill.sttp.client3" - def sttpClientLib(name: String): Dep = - ivy"${sttpClientOrg}::${name}:${V.sttpClient}" - - lazy val sttpClientCore: Dep = sttpClientLib("core") - - lazy val http4sBlazeServer: Dep = - ivy"org.http4s::http4s-blaze-server:${V.http4s}" - - lazy val http4sPac4J: Dep = - ivy"org.pac4j::http4s-pac4j:${V.http4sPac4J}" - lazy val pac4jOIDC: Dep = - ivy"org.pac4j:pac4j-oidc:${V.pac4j}" - - lazy val scalaTest: Dep = - ivy"org.scalatest::scalatest:${V.scalaTest}" - lazy val scalaTestPlusScalacheck: Dep = - ivy"org.scalatestplus::scalacheck-1-15:3.2.9.0" - lazy val playScalaTest: Dep = - ivy"org.scalatestplus.play::scalatestplus-play:5.1.0" - - private val playOrg = "com.typesafe.play" - lazy val playMailer: Dep = ivy"${playOrg}::play-mailer:8.0.1" - lazy val playServer: Dep = ivy"${playOrg}::play-server:${V.play}" - lazy val playAkkaServer: Dep = - ivy"${playOrg}::play-akka-http-server:${V.play}" - lazy val play: Dep = ivy"${playOrg}::play:${V.play}" - lazy val playAhcWs: Dep = ivy"${playOrg}::play-ahc-ws:${V.play}" - lazy val playJson: Dep = ivy"${playOrg}::play-json:${V.playJson}" - - private val elastic4sOrg = "com.sksamuel.elastic4s" - lazy val useElastic4S: Agg[Dep] = Agg( - ivy"${elastic4sOrg}::elastic4s-core:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-client-akka:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-http-streams:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-json-play:${V.elastic4s}" - ) - - lazy val laminar: Dep = ivy"com.raquo::laminar::${V.laminar}" - - private def laminext(name: String): Dep = - ivy"io.laminext::$name::${V.laminar}" - - lazy val laminextCore: Dep = laminext("core") - lazy val laminextTailwind: Dep = laminext("tailwind") - lazy val laminextFetch: Dep = laminext("fetch") - lazy val laminextValidationCore: Dep = laminext("validation-core") - lazy val laminextUI: Dep = laminext("ui") - - lazy val waypoint: Dep = - ivy"com.raquo::waypoint::${V.waypoint}" - - lazy val urlDsl: Dep = - ivy"be.doeraene::url-dsl::${V.urlDsl}" - - lazy val scalaJavaTime: Dep = - ivy"io.github.cquiroz::scala-java-time::2.3.0" - - lazy val scalaJavaLocales: Dep = - ivy"io.github.cquiroz::scala-java-locales::1.2.1" - - lazy val logbackClassic: Dep = - ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" - } - - trait AkkaLibs { - - self: SlickLibs => - - object akka { - val V = Versions.akka - val tOrg = "com.typesafe.akka" - val lOrg = "com.lightbend.akka" - - def akkaMod(name: String): Dep = - ivy"$tOrg::akka-$name:$V" - - lazy val actor: Dep = akkaMod("actor") - lazy val actorTyped: Dep = akkaMod("actor-typed") - lazy val stream: Dep = akkaMod("stream") - lazy val persistence: Dep = akkaMod("persistence-typed") - lazy val persistenceQuery: Dep = akkaMod("persistence-query") - lazy val persistenceJdbc: Dep = - ivy"$lOrg::akka-persistence-jdbc:5.0.4" - val persistenceTestKit: Dep = ivy"$tOrg::akka-persistence-testkit:$V" - - object http { - val V = Versions.akkaHttp - - lazy val http: Dep = ivy"$tOrg::akka-http:$V" - lazy val sprayJson: Dep = ivy"$tOrg::akka-http-spray-json:$V" - - } - - object projection { - val V = "1.2.2" - - lazy val core: Dep = - ivy"$lOrg::akka-projection-core:$V" - lazy val eventsourced: Dep = - ivy"$lOrg::akka-projection-eventsourced:$V" - lazy val slick: Dep = - ivy"$lOrg::akka-projection-slick:$V" - lazy val jdbc: Dep = - ivy"$lOrg::akka-projection-jdbc:$V" - } - - object profiles { - // TODO: deal with cross-version for Scala3 (for3Use2_13) - lazy val eventsourcedJdbcProjection: Agg[Dep] = Agg( - persistenceQuery, - projection.core, - projection.eventsourced, - projection.slick, - persistenceJdbc - ) ++ slick.default - } - } - } - - trait SlickLibs { - object slick { - val V = IWMaterials.Versions.slick - val org = "com.typesafe.slick" - - lazy val slick: Dep = ivy"$org::slick:$V" - lazy val hikaricp: Dep = ivy"$org::slick-hikaricp:$V" - lazy val default: Agg[Dep] = Agg(slick, hikaricp) - } - } - -} diff --git a/iw/build.sc b/iw/build.sc deleted file mode 100644 index fbbc3b8..0000000 --- a/iw/build.sc +++ /dev/null @@ -1,144 +0,0 @@ -import mill._, scalalib._, scalajslib._ - -import $file.bom - -object support { - val Deps = bom.IWMaterials.Deps - val Versions = bom.IWMaterials.Versions - - trait CommonModule extends ScalaModule { - def scalaVersion = "3.1.1" - def scalacOptions = Seq( - "-encoding", - "utf8", - "-deprecation", - "-explain-types", - "-explain", - "-feature", - "-language:experimental.macros", - "-language:higherKinds", - "-language:implicitConversions", - "-unchecked", - "-Xfatal-warnings", - "-Ykind-projector" - ) - } - - trait CommonJSModule extends CommonModule with ScalaJSModule { - def scalaJSVersion = "1.8.0" - } - - trait CrossPlatformModule extends Module { outer => - def ivyDeps: T[Agg[Dep]] = Agg[Dep]() - def jsDeps: T[Agg[Dep]] = Agg[Dep]() - def jvmDeps: T[Agg[Dep]] = Agg[Dep]() - def moduleDeps: Seq[Module] = Seq[Module]() - - trait PlatformModule extends CommonModule { - def platform: String - override def millSourcePath = outer.millSourcePath - override def ivyDeps = outer.ivyDeps() ++ (platform match { - case "js" => jsDeps() - case _ => jvmDeps() - }) - override def moduleDeps = outer.moduleDeps.collect { - case m: CrossPlatformModule => - platform match { - case "js" => m.js - case _ => m.jvm - } - case m: JavaModule => m - } - } - - trait JsModule extends PlatformModule with CommonJSModule { - def platform = "js" - } - - trait JvmModule extends PlatformModule { - def platform = "jvm" - } - - val js: JsModule - val jvm: JvmModule - } - - trait PureCrossModule extends CrossPlatformModule { - override object js extends JsModule - override object jvm extends JvmModule - } - - trait PureCrossSbtModule extends CrossPlatformModule { - override object js extends JsModule with SbtModule - override object jvm extends JvmModule with SbtModule - } - - trait FullCrossSbtModule extends CrossPlatformModule { - trait FullSources extends JavaModule { self: PlatformModule => - override def sources = T.sources( - millSourcePath / platform / "src" / "main" / "scala", - millSourcePath / "shared" / "src" / "main" / "scala" - ) - - override def resources = T.sources( - millSourcePath / platform / "src" / "main" / "resources", - millSourcePath / "shared" / "src" / "main" / "resources" - ) - } - - override object js extends JsModule with FullSources - override object jvm extends JvmModule with FullSources - } - -} - -import support._ - -object mongo extends CommonModule { - def ivyDeps = Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - ivy"org.mongodb.scala::mongo-scala-driver:4.2.3".withDottyCompat( - scalaVersion() - ) - ) -} - -object tapir extends FullCrossSbtModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson, Deps.zioJson) - def jvmDeps = Agg(Deps.tapirZIO, Deps.tapirZIOHttp4sServer) - def jsDeps = Agg(Deps.tapirSttpClient) -} - -object akkaPersistence extends CommonModule { - def millSourcePath = build.millSourcePath / "akka-persistence" - def ivyDeps = (Agg( - Deps.akka.persistenceQuery, - Deps.akka.persistenceJdbc, - Deps.akka.projection.core, - Deps.akka.projection.eventsourced, - Deps.akka.projection.slick - ) ++ Deps.slick.default).map(_.withDottyCompat(scalaVersion())) ++ Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - Deps.akka.persistence.withDottyCompat(scalaVersion()), - ivy"com.typesafe.akka::akka-cluster-sharding-typed:${Deps.akka.V}" - .withDottyCompat(scalaVersion()) - ) -} - -object ui extends CommonJSModule { - def ivyDeps = Agg( - Deps.zio, - Deps.laminar, - Deps.zioJson, - Deps.waypoint, - Deps.urlDsl, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) -} diff --git a/iw/domain.sc b/iw/domain.sc deleted file mode 100644 index b09c56b..0000000 --- a/iw/domain.sc +++ /dev/null @@ -1,78 +0,0 @@ -import mill._, scalalib._ - -import $file.{build => ff}, ff.support._ - -trait DomainModule extends Module { - // General extra model deps - def modelModules: Seq[Module] = Seq.empty - // General extra codecs deps - def codecsModules: Seq[Module] = Seq.empty - // General extra endpoints deps - def endpointsModules: Seq[Module] = Seq.empty - - // Implementation deps for repo - def repoModules: Seq[JavaModule] = Seq.empty - - object shared extends Module { - object model extends PureCrossModule { - def moduleDeps = modelModules - def ivyDeps = Agg(Deps.zioPrelude) - } - object codecs extends PureCrossModule { - def moduleDeps = Seq(model) ++ codecsModules - def ivyDeps = Agg(Deps.zioJson) - } - } - - trait CommonProjects extends Module { - object model extends PureCrossModule { - def ivyDeps = Agg(Deps.zioPrelude) - def moduleDeps = Seq(shared.model) - } - object codecs extends PureCrossModule { - def moduleDeps = - Seq(model, shared.model, shared.codecs, ff.tapir) - } - object endpoints extends PureCrossModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson) - def moduleDeps = Seq(model, codecs) ++ endpointsModules - } - object client extends CommonJSModule { - def moduleDeps = Seq(endpoints.js) - } - object components extends CommonJSModule { - def ivyDeps = Agg( - Deps.laminar, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) - def moduleDeps = Seq(model.js, codecs.js, client, ff.ui) - } - } - - object query extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(repo, query.endpoints.jvm) - } - object repo extends CommonModule { - def ivyDeps = Agg(Deps.zio) - def moduleDeps = Seq(model.jvm, codecs.jvm) ++ repoModules - } - object projection extends CommonModule { - def moduleDeps = Seq(repo, ff.akkaPersistence) - } - } - - object command extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(entity, command.endpoints.jvm) - } - object entity extends CommonModule { - def moduleDeps = Seq(model.jvm, codecs.jvm, ff.akkaPersistence) - } - } -} diff --git a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala b/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala deleted file mode 100644 index 0fa3493..0000000 --- a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala +++ /dev/null @@ -1,53 +0,0 @@ -package works.iterative.mongo - -import zio.* -import zio.json.* -import zio.config.* -import org.mongodb.scala.* -import org.mongodb.scala.model.Filters.* -import org.bson.json.JsonObject -import org.mongodb.scala.model.ReplaceOptions -import org.mongodb.scala.bson.conversions.Bson - -case class MongoConfig(uri: String) - -object MongoConfig: - val configDesc = - import ConfigDescriptor.* - nested("MONGO")(string("URI").default("mongodb://localhost:27017")) - .to[MongoConfig] - val fromEnv = ZConfig.fromSystemEnv(configDesc) - -extension (m: MongoClient.type) - def layer: RLayer[MongoConfig, MongoClient] = - ZIO - .serviceWithZIO[MongoConfig](c => Task.attempt(MongoClient(c.uri))) - .toLayer - -class MongoJsonRepository[Elem, Key, Criteria]( - collection: MongoCollection[JsonObject], - toFilter: Criteria => Bson, - idFilter: Elem => (String, Key) -)(using JsonCodec[Elem]) { - def matching(criteria: Criteria): Task[Seq[Elem]] = - val filter = toFilter(criteria) - val query = collection.find(filter) - - for - result <- ZIO.fromFuture(_ => query.toFuture) - proof <- ZIO.collect(result)(j => - ZIO.fromOption(j.getJson.fromJson[Elem].toOption) - ) - yield proof - - def put(elem: Elem): Task[Unit] = - Task.async(cb => - collection - .replaceOne( - equal.tupled(idFilter(elem)), - JsonObject(elem.toJson), - ReplaceOptions().upsert(true) - ) - .subscribe(_ => cb(Task.unit), t => cb(Task.fail(t))) - ) -} diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala deleted file mode 100644 index 68d7196..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala +++ /dev/null @@ -1,12 +0,0 @@ -package works.iterative.tapir - -import sttp.model.Uri - -opaque type BaseUri = Option[Uri] - -object BaseUri: - - def apply(optU: Option[Uri]): BaseUri = optU - def apply(u: Uri): BaseUri = Some(u) - - extension (v: BaseUri) def toUri: Option[Uri] = v diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 653bc51..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,22 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.client.sttp.SttpClientInterpreter -import sttp.tapir.PublicEndpoint -import sttp.tapir.client.sttp.WebSocketToPipe -import scala.concurrent.Future -import sttp.client3.SttpBackend -import sttp.capabilities.WebSockets - -trait CustomTapirPlatformSpecific extends SttpClientInterpreter: - self: CustomTapir => - - type Backend = SttpBackend[Future, WebSockets] - - def makeClient[I, E, O]( - endpoint: PublicEndpoint[I, E, O, Any] - )(using - baseUri: BaseUri, - backend: Backend, - wsToPipe: WebSocketToPipe[Any] - ): I => Future[O] = - toClientThrowErrors(endpoint, baseUri.toUri, backend) diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 2545fbd..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,5 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.ztapir.ZTapir - -trait CustomTapirPlatformSpecific extends ZTapir diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala deleted file mode 100644 index 55e4ca1..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala +++ /dev/null @@ -1,7 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.server.http4s.ztapir.ZHttp4sServerInterpreter - -trait Http4sCustomTapir[Env] - extends CustomTapir - with ZHttp4sServerInterpreter[Env] diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala deleted file mode 100644 index dd52e9b..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala +++ /dev/null @@ -1,14 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.Tapir -import sttp.tapir.json.zio.TapirJsonZio -import sttp.tapir.TapirAliases - -trait CustomTapir - extends Tapir - with TapirJsonZio - with TapirAliases - with CustomTapirPlatformSpecific: - given Schema[ServerError] = Schema.derived - -object CustomTapir extends CustomTapir diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala deleted file mode 100644 index e7e6627..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.akka - -// Base class for all command-processing related exceptions from handlers -sealed abstract class CommandHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandNotAvailable[C, S](cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd není dostupný ve stavu $state" - ) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandRejected[C, S](reason: String, cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd byl ve stavu $state odmítnut s odůvodněním $reason" - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala deleted file mode 100644 index 72a5bf9..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.akka - -sealed abstract class EventHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -case class UnhandledEvent[Event, State](event: Event, state: State) - extends EventHandlerException( - s"Událost $event nastala ve stavu $state bez možnosti zpracování" - ) diff --git a/iw/bom.sc b/iw/bom.sc deleted file mode 100644 index e9266b8..0000000 --- a/iw/bom.sc +++ /dev/null @@ -1,227 +0,0 @@ -import mill._, scalalib._ - -object IWMaterials { - - object Versions { - val akka = "2.6.16" - val akkaHttp = "10.2.4" - val cats = "2.7.0" - val elastic4s = "7.12.2" - val http4s = "0.23.10" - val http4sPac4J = "4.0.0" - val laminar = "0.14.2" - val laminext = laminar - val logbackClassic = "1.2.10" - val pac4j = "5.2.0" - val play = "2.8.8" - val playJson = "2.9.2" - val scalaTest = "3.2.9" - val slick = "3.3.3" - val sttpClient = "3.5.0" - val tapir = "0.20.1" - val urlDsl = "0.4.0" - val waypoint = "0.5.0" - val zio = "2.0.0-RC2" - val zioConfig = "3.0.0-RC2" - val zioInteropCats = "3.3.0-RC2" - val zioJson = "0.3.0-RC3" - val zioLogging = "2.0.0-RC5" - val zioPrelude = "1.0.0-RC10" - val zioZMX = "0.0.11" - } - - object Deps extends AkkaLibs with SlickLibs { - import IWMaterials.{Versions => V} - - val zioOrg = "dev.zio" - - def zioLib(name: String, version: String): Dep = - ivy"$zioOrg::zio-$name::$version" - - lazy val zio: Dep = ivy"$zioOrg::zio:${V.zio}" - - lazy val zioTest: Dep = zioLib("test", V.zio) - lazy val zioTestSbt: Dep = zioLib("test", V.zio) - - lazy val zioConfig: Dep = zioLib("config", V.zioConfig) - lazy val zioConfigTypesafe: Dep = - zioLib("config-typesafe", V.zioConfig) - lazy val zioConfigMagnolia: Dep = - zioLib("config-magnolia", V.zioConfig) - - lazy val zioJson: Dep = zioLib("json", V.zioJson) - lazy val zioLogging: Dep = zioLib("logging", V.zioLogging) - lazy val zioLoggingSlf4j: Dep = - zioLib("logging-slf4j", V.zioLogging) - lazy val zioPrelude: Dep = zioLib("prelude", V.zioPrelude) - lazy val zioStreams: Dep = zioLib("streams", V.zio) - lazy val zioZMX: Dep = zioLib("zmx", V.zioZMX) - lazy val zioInteropCats: Dep = - zioLib("interop-cats", V.zioInteropCats) - - /* What is the equivalent? ZIOModule with prepared test config? - def useZIO(testConf: Configuration*): Agg[Dep] = Agg( - zio, - zioTest, - zioTestSbt, - testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework") - ) - */ - - /* - def useZIOAll(testConf: Configuration*): Seq[Def.Setting[_]] = - useZIO(testConf: _*) ++ Seq( - zioStreams, - zioConfig, - zioConfigTypesafe, - zioConfigMagnolia, - zioJson, - zioZMX, - zioLogging, - zioPrelude - ) - */ - - private val tapirOrg = "com.softwaremill.sttp.tapir" - def tapirLib(name: String): Dep = - ivy"${tapirOrg}::tapir-$name::${V.tapir}" - - lazy val tapirCore: Dep = tapirLib("core") - lazy val tapirZIO: Dep = tapirLib("zio") - lazy val tapirZIOJson: Dep = tapirLib("json-zio") - lazy val tapirSttpClient: Dep = tapirLib("sttp-client") - lazy val tapirCats: Dep = tapirLib("cats") - lazy val tapirZIOHttp4sServer: Dep = tapirLib("zio-http4s-server") - - private val sttpClientOrg = "com.softwaremill.sttp.client3" - def sttpClientLib(name: String): Dep = - ivy"${sttpClientOrg}::${name}:${V.sttpClient}" - - lazy val sttpClientCore: Dep = sttpClientLib("core") - - lazy val http4sBlazeServer: Dep = - ivy"org.http4s::http4s-blaze-server:${V.http4s}" - - lazy val http4sPac4J: Dep = - ivy"org.pac4j::http4s-pac4j:${V.http4sPac4J}" - lazy val pac4jOIDC: Dep = - ivy"org.pac4j:pac4j-oidc:${V.pac4j}" - - lazy val scalaTest: Dep = - ivy"org.scalatest::scalatest:${V.scalaTest}" - lazy val scalaTestPlusScalacheck: Dep = - ivy"org.scalatestplus::scalacheck-1-15:3.2.9.0" - lazy val playScalaTest: Dep = - ivy"org.scalatestplus.play::scalatestplus-play:5.1.0" - - private val playOrg = "com.typesafe.play" - lazy val playMailer: Dep = ivy"${playOrg}::play-mailer:8.0.1" - lazy val playServer: Dep = ivy"${playOrg}::play-server:${V.play}" - lazy val playAkkaServer: Dep = - ivy"${playOrg}::play-akka-http-server:${V.play}" - lazy val play: Dep = ivy"${playOrg}::play:${V.play}" - lazy val playAhcWs: Dep = ivy"${playOrg}::play-ahc-ws:${V.play}" - lazy val playJson: Dep = ivy"${playOrg}::play-json:${V.playJson}" - - private val elastic4sOrg = "com.sksamuel.elastic4s" - lazy val useElastic4S: Agg[Dep] = Agg( - ivy"${elastic4sOrg}::elastic4s-core:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-client-akka:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-http-streams:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-json-play:${V.elastic4s}" - ) - - lazy val laminar: Dep = ivy"com.raquo::laminar::${V.laminar}" - - private def laminext(name: String): Dep = - ivy"io.laminext::$name::${V.laminar}" - - lazy val laminextCore: Dep = laminext("core") - lazy val laminextTailwind: Dep = laminext("tailwind") - lazy val laminextFetch: Dep = laminext("fetch") - lazy val laminextValidationCore: Dep = laminext("validation-core") - lazy val laminextUI: Dep = laminext("ui") - - lazy val waypoint: Dep = - ivy"com.raquo::waypoint::${V.waypoint}" - - lazy val urlDsl: Dep = - ivy"be.doeraene::url-dsl::${V.urlDsl}" - - lazy val scalaJavaTime: Dep = - ivy"io.github.cquiroz::scala-java-time::2.3.0" - - lazy val scalaJavaLocales: Dep = - ivy"io.github.cquiroz::scala-java-locales::1.2.1" - - lazy val logbackClassic: Dep = - ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" - } - - trait AkkaLibs { - - self: SlickLibs => - - object akka { - val V = Versions.akka - val tOrg = "com.typesafe.akka" - val lOrg = "com.lightbend.akka" - - def akkaMod(name: String): Dep = - ivy"$tOrg::akka-$name:$V" - - lazy val actor: Dep = akkaMod("actor") - lazy val actorTyped: Dep = akkaMod("actor-typed") - lazy val stream: Dep = akkaMod("stream") - lazy val persistence: Dep = akkaMod("persistence-typed") - lazy val persistenceQuery: Dep = akkaMod("persistence-query") - lazy val persistenceJdbc: Dep = - ivy"$lOrg::akka-persistence-jdbc:5.0.4" - val persistenceTestKit: Dep = ivy"$tOrg::akka-persistence-testkit:$V" - - object http { - val V = Versions.akkaHttp - - lazy val http: Dep = ivy"$tOrg::akka-http:$V" - lazy val sprayJson: Dep = ivy"$tOrg::akka-http-spray-json:$V" - - } - - object projection { - val V = "1.2.2" - - lazy val core: Dep = - ivy"$lOrg::akka-projection-core:$V" - lazy val eventsourced: Dep = - ivy"$lOrg::akka-projection-eventsourced:$V" - lazy val slick: Dep = - ivy"$lOrg::akka-projection-slick:$V" - lazy val jdbc: Dep = - ivy"$lOrg::akka-projection-jdbc:$V" - } - - object profiles { - // TODO: deal with cross-version for Scala3 (for3Use2_13) - lazy val eventsourcedJdbcProjection: Agg[Dep] = Agg( - persistenceQuery, - projection.core, - projection.eventsourced, - projection.slick, - persistenceJdbc - ) ++ slick.default - } - } - } - - trait SlickLibs { - object slick { - val V = IWMaterials.Versions.slick - val org = "com.typesafe.slick" - - lazy val slick: Dep = ivy"$org::slick:$V" - lazy val hikaricp: Dep = ivy"$org::slick-hikaricp:$V" - lazy val default: Agg[Dep] = Agg(slick, hikaricp) - } - } - -} diff --git a/iw/build.sc b/iw/build.sc deleted file mode 100644 index fbbc3b8..0000000 --- a/iw/build.sc +++ /dev/null @@ -1,144 +0,0 @@ -import mill._, scalalib._, scalajslib._ - -import $file.bom - -object support { - val Deps = bom.IWMaterials.Deps - val Versions = bom.IWMaterials.Versions - - trait CommonModule extends ScalaModule { - def scalaVersion = "3.1.1" - def scalacOptions = Seq( - "-encoding", - "utf8", - "-deprecation", - "-explain-types", - "-explain", - "-feature", - "-language:experimental.macros", - "-language:higherKinds", - "-language:implicitConversions", - "-unchecked", - "-Xfatal-warnings", - "-Ykind-projector" - ) - } - - trait CommonJSModule extends CommonModule with ScalaJSModule { - def scalaJSVersion = "1.8.0" - } - - trait CrossPlatformModule extends Module { outer => - def ivyDeps: T[Agg[Dep]] = Agg[Dep]() - def jsDeps: T[Agg[Dep]] = Agg[Dep]() - def jvmDeps: T[Agg[Dep]] = Agg[Dep]() - def moduleDeps: Seq[Module] = Seq[Module]() - - trait PlatformModule extends CommonModule { - def platform: String - override def millSourcePath = outer.millSourcePath - override def ivyDeps = outer.ivyDeps() ++ (platform match { - case "js" => jsDeps() - case _ => jvmDeps() - }) - override def moduleDeps = outer.moduleDeps.collect { - case m: CrossPlatformModule => - platform match { - case "js" => m.js - case _ => m.jvm - } - case m: JavaModule => m - } - } - - trait JsModule extends PlatformModule with CommonJSModule { - def platform = "js" - } - - trait JvmModule extends PlatformModule { - def platform = "jvm" - } - - val js: JsModule - val jvm: JvmModule - } - - trait PureCrossModule extends CrossPlatformModule { - override object js extends JsModule - override object jvm extends JvmModule - } - - trait PureCrossSbtModule extends CrossPlatformModule { - override object js extends JsModule with SbtModule - override object jvm extends JvmModule with SbtModule - } - - trait FullCrossSbtModule extends CrossPlatformModule { - trait FullSources extends JavaModule { self: PlatformModule => - override def sources = T.sources( - millSourcePath / platform / "src" / "main" / "scala", - millSourcePath / "shared" / "src" / "main" / "scala" - ) - - override def resources = T.sources( - millSourcePath / platform / "src" / "main" / "resources", - millSourcePath / "shared" / "src" / "main" / "resources" - ) - } - - override object js extends JsModule with FullSources - override object jvm extends JvmModule with FullSources - } - -} - -import support._ - -object mongo extends CommonModule { - def ivyDeps = Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - ivy"org.mongodb.scala::mongo-scala-driver:4.2.3".withDottyCompat( - scalaVersion() - ) - ) -} - -object tapir extends FullCrossSbtModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson, Deps.zioJson) - def jvmDeps = Agg(Deps.tapirZIO, Deps.tapirZIOHttp4sServer) - def jsDeps = Agg(Deps.tapirSttpClient) -} - -object akkaPersistence extends CommonModule { - def millSourcePath = build.millSourcePath / "akka-persistence" - def ivyDeps = (Agg( - Deps.akka.persistenceQuery, - Deps.akka.persistenceJdbc, - Deps.akka.projection.core, - Deps.akka.projection.eventsourced, - Deps.akka.projection.slick - ) ++ Deps.slick.default).map(_.withDottyCompat(scalaVersion())) ++ Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - Deps.akka.persistence.withDottyCompat(scalaVersion()), - ivy"com.typesafe.akka::akka-cluster-sharding-typed:${Deps.akka.V}" - .withDottyCompat(scalaVersion()) - ) -} - -object ui extends CommonJSModule { - def ivyDeps = Agg( - Deps.zio, - Deps.laminar, - Deps.zioJson, - Deps.waypoint, - Deps.urlDsl, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) -} diff --git a/iw/domain.sc b/iw/domain.sc deleted file mode 100644 index b09c56b..0000000 --- a/iw/domain.sc +++ /dev/null @@ -1,78 +0,0 @@ -import mill._, scalalib._ - -import $file.{build => ff}, ff.support._ - -trait DomainModule extends Module { - // General extra model deps - def modelModules: Seq[Module] = Seq.empty - // General extra codecs deps - def codecsModules: Seq[Module] = Seq.empty - // General extra endpoints deps - def endpointsModules: Seq[Module] = Seq.empty - - // Implementation deps for repo - def repoModules: Seq[JavaModule] = Seq.empty - - object shared extends Module { - object model extends PureCrossModule { - def moduleDeps = modelModules - def ivyDeps = Agg(Deps.zioPrelude) - } - object codecs extends PureCrossModule { - def moduleDeps = Seq(model) ++ codecsModules - def ivyDeps = Agg(Deps.zioJson) - } - } - - trait CommonProjects extends Module { - object model extends PureCrossModule { - def ivyDeps = Agg(Deps.zioPrelude) - def moduleDeps = Seq(shared.model) - } - object codecs extends PureCrossModule { - def moduleDeps = - Seq(model, shared.model, shared.codecs, ff.tapir) - } - object endpoints extends PureCrossModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson) - def moduleDeps = Seq(model, codecs) ++ endpointsModules - } - object client extends CommonJSModule { - def moduleDeps = Seq(endpoints.js) - } - object components extends CommonJSModule { - def ivyDeps = Agg( - Deps.laminar, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) - def moduleDeps = Seq(model.js, codecs.js, client, ff.ui) - } - } - - object query extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(repo, query.endpoints.jvm) - } - object repo extends CommonModule { - def ivyDeps = Agg(Deps.zio) - def moduleDeps = Seq(model.jvm, codecs.jvm) ++ repoModules - } - object projection extends CommonModule { - def moduleDeps = Seq(repo, ff.akkaPersistence) - } - } - - object command extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(entity, command.endpoints.jvm) - } - object entity extends CommonModule { - def moduleDeps = Seq(model.jvm, codecs.jvm, ff.akkaPersistence) - } - } -} diff --git a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala b/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala deleted file mode 100644 index 0fa3493..0000000 --- a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala +++ /dev/null @@ -1,53 +0,0 @@ -package works.iterative.mongo - -import zio.* -import zio.json.* -import zio.config.* -import org.mongodb.scala.* -import org.mongodb.scala.model.Filters.* -import org.bson.json.JsonObject -import org.mongodb.scala.model.ReplaceOptions -import org.mongodb.scala.bson.conversions.Bson - -case class MongoConfig(uri: String) - -object MongoConfig: - val configDesc = - import ConfigDescriptor.* - nested("MONGO")(string("URI").default("mongodb://localhost:27017")) - .to[MongoConfig] - val fromEnv = ZConfig.fromSystemEnv(configDesc) - -extension (m: MongoClient.type) - def layer: RLayer[MongoConfig, MongoClient] = - ZIO - .serviceWithZIO[MongoConfig](c => Task.attempt(MongoClient(c.uri))) - .toLayer - -class MongoJsonRepository[Elem, Key, Criteria]( - collection: MongoCollection[JsonObject], - toFilter: Criteria => Bson, - idFilter: Elem => (String, Key) -)(using JsonCodec[Elem]) { - def matching(criteria: Criteria): Task[Seq[Elem]] = - val filter = toFilter(criteria) - val query = collection.find(filter) - - for - result <- ZIO.fromFuture(_ => query.toFuture) - proof <- ZIO.collect(result)(j => - ZIO.fromOption(j.getJson.fromJson[Elem].toOption) - ) - yield proof - - def put(elem: Elem): Task[Unit] = - Task.async(cb => - collection - .replaceOne( - equal.tupled(idFilter(elem)), - JsonObject(elem.toJson), - ReplaceOptions().upsert(true) - ) - .subscribe(_ => cb(Task.unit), t => cb(Task.fail(t))) - ) -} diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala deleted file mode 100644 index 68d7196..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala +++ /dev/null @@ -1,12 +0,0 @@ -package works.iterative.tapir - -import sttp.model.Uri - -opaque type BaseUri = Option[Uri] - -object BaseUri: - - def apply(optU: Option[Uri]): BaseUri = optU - def apply(u: Uri): BaseUri = Some(u) - - extension (v: BaseUri) def toUri: Option[Uri] = v diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 653bc51..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,22 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.client.sttp.SttpClientInterpreter -import sttp.tapir.PublicEndpoint -import sttp.tapir.client.sttp.WebSocketToPipe -import scala.concurrent.Future -import sttp.client3.SttpBackend -import sttp.capabilities.WebSockets - -trait CustomTapirPlatformSpecific extends SttpClientInterpreter: - self: CustomTapir => - - type Backend = SttpBackend[Future, WebSockets] - - def makeClient[I, E, O]( - endpoint: PublicEndpoint[I, E, O, Any] - )(using - baseUri: BaseUri, - backend: Backend, - wsToPipe: WebSocketToPipe[Any] - ): I => Future[O] = - toClientThrowErrors(endpoint, baseUri.toUri, backend) diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 2545fbd..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,5 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.ztapir.ZTapir - -trait CustomTapirPlatformSpecific extends ZTapir diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala deleted file mode 100644 index 55e4ca1..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala +++ /dev/null @@ -1,7 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.server.http4s.ztapir.ZHttp4sServerInterpreter - -trait Http4sCustomTapir[Env] - extends CustomTapir - with ZHttp4sServerInterpreter[Env] diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala deleted file mode 100644 index dd52e9b..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala +++ /dev/null @@ -1,14 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.Tapir -import sttp.tapir.json.zio.TapirJsonZio -import sttp.tapir.TapirAliases - -trait CustomTapir - extends Tapir - with TapirJsonZio - with TapirAliases - with CustomTapirPlatformSpecific: - given Schema[ServerError] = Schema.derived - -object CustomTapir extends CustomTapir diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala deleted file mode 100644 index 715591d..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.tapir - -import zio.json.* - -sealed trait ServerError -case class InternalServerError(msg: String) extends ServerError -object InternalServerError: - def fromThrowable(t: Throwable): ServerError = InternalServerError( - t.getMessage - ) - -object ServerError: - given JsonCodec[ServerError] = DeriveJsonCodec.gen diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala deleted file mode 100644 index e7e6627..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.akka - -// Base class for all command-processing related exceptions from handlers -sealed abstract class CommandHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandNotAvailable[C, S](cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd není dostupný ve stavu $state" - ) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandRejected[C, S](reason: String, cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd byl ve stavu $state odmítnut s odůvodněním $reason" - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala deleted file mode 100644 index 72a5bf9..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.akka - -sealed abstract class EventHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -case class UnhandledEvent[Event, State](event: Event, state: State) - extends EventHandlerException( - s"Událost $event nastala ve stavu $state bez možnosti zpracování" - ) diff --git a/iw/bom.sc b/iw/bom.sc deleted file mode 100644 index e9266b8..0000000 --- a/iw/bom.sc +++ /dev/null @@ -1,227 +0,0 @@ -import mill._, scalalib._ - -object IWMaterials { - - object Versions { - val akka = "2.6.16" - val akkaHttp = "10.2.4" - val cats = "2.7.0" - val elastic4s = "7.12.2" - val http4s = "0.23.10" - val http4sPac4J = "4.0.0" - val laminar = "0.14.2" - val laminext = laminar - val logbackClassic = "1.2.10" - val pac4j = "5.2.0" - val play = "2.8.8" - val playJson = "2.9.2" - val scalaTest = "3.2.9" - val slick = "3.3.3" - val sttpClient = "3.5.0" - val tapir = "0.20.1" - val urlDsl = "0.4.0" - val waypoint = "0.5.0" - val zio = "2.0.0-RC2" - val zioConfig = "3.0.0-RC2" - val zioInteropCats = "3.3.0-RC2" - val zioJson = "0.3.0-RC3" - val zioLogging = "2.0.0-RC5" - val zioPrelude = "1.0.0-RC10" - val zioZMX = "0.0.11" - } - - object Deps extends AkkaLibs with SlickLibs { - import IWMaterials.{Versions => V} - - val zioOrg = "dev.zio" - - def zioLib(name: String, version: String): Dep = - ivy"$zioOrg::zio-$name::$version" - - lazy val zio: Dep = ivy"$zioOrg::zio:${V.zio}" - - lazy val zioTest: Dep = zioLib("test", V.zio) - lazy val zioTestSbt: Dep = zioLib("test", V.zio) - - lazy val zioConfig: Dep = zioLib("config", V.zioConfig) - lazy val zioConfigTypesafe: Dep = - zioLib("config-typesafe", V.zioConfig) - lazy val zioConfigMagnolia: Dep = - zioLib("config-magnolia", V.zioConfig) - - lazy val zioJson: Dep = zioLib("json", V.zioJson) - lazy val zioLogging: Dep = zioLib("logging", V.zioLogging) - lazy val zioLoggingSlf4j: Dep = - zioLib("logging-slf4j", V.zioLogging) - lazy val zioPrelude: Dep = zioLib("prelude", V.zioPrelude) - lazy val zioStreams: Dep = zioLib("streams", V.zio) - lazy val zioZMX: Dep = zioLib("zmx", V.zioZMX) - lazy val zioInteropCats: Dep = - zioLib("interop-cats", V.zioInteropCats) - - /* What is the equivalent? ZIOModule with prepared test config? - def useZIO(testConf: Configuration*): Agg[Dep] = Agg( - zio, - zioTest, - zioTestSbt, - testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework") - ) - */ - - /* - def useZIOAll(testConf: Configuration*): Seq[Def.Setting[_]] = - useZIO(testConf: _*) ++ Seq( - zioStreams, - zioConfig, - zioConfigTypesafe, - zioConfigMagnolia, - zioJson, - zioZMX, - zioLogging, - zioPrelude - ) - */ - - private val tapirOrg = "com.softwaremill.sttp.tapir" - def tapirLib(name: String): Dep = - ivy"${tapirOrg}::tapir-$name::${V.tapir}" - - lazy val tapirCore: Dep = tapirLib("core") - lazy val tapirZIO: Dep = tapirLib("zio") - lazy val tapirZIOJson: Dep = tapirLib("json-zio") - lazy val tapirSttpClient: Dep = tapirLib("sttp-client") - lazy val tapirCats: Dep = tapirLib("cats") - lazy val tapirZIOHttp4sServer: Dep = tapirLib("zio-http4s-server") - - private val sttpClientOrg = "com.softwaremill.sttp.client3" - def sttpClientLib(name: String): Dep = - ivy"${sttpClientOrg}::${name}:${V.sttpClient}" - - lazy val sttpClientCore: Dep = sttpClientLib("core") - - lazy val http4sBlazeServer: Dep = - ivy"org.http4s::http4s-blaze-server:${V.http4s}" - - lazy val http4sPac4J: Dep = - ivy"org.pac4j::http4s-pac4j:${V.http4sPac4J}" - lazy val pac4jOIDC: Dep = - ivy"org.pac4j:pac4j-oidc:${V.pac4j}" - - lazy val scalaTest: Dep = - ivy"org.scalatest::scalatest:${V.scalaTest}" - lazy val scalaTestPlusScalacheck: Dep = - ivy"org.scalatestplus::scalacheck-1-15:3.2.9.0" - lazy val playScalaTest: Dep = - ivy"org.scalatestplus.play::scalatestplus-play:5.1.0" - - private val playOrg = "com.typesafe.play" - lazy val playMailer: Dep = ivy"${playOrg}::play-mailer:8.0.1" - lazy val playServer: Dep = ivy"${playOrg}::play-server:${V.play}" - lazy val playAkkaServer: Dep = - ivy"${playOrg}::play-akka-http-server:${V.play}" - lazy val play: Dep = ivy"${playOrg}::play:${V.play}" - lazy val playAhcWs: Dep = ivy"${playOrg}::play-ahc-ws:${V.play}" - lazy val playJson: Dep = ivy"${playOrg}::play-json:${V.playJson}" - - private val elastic4sOrg = "com.sksamuel.elastic4s" - lazy val useElastic4S: Agg[Dep] = Agg( - ivy"${elastic4sOrg}::elastic4s-core:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-client-akka:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-http-streams:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-json-play:${V.elastic4s}" - ) - - lazy val laminar: Dep = ivy"com.raquo::laminar::${V.laminar}" - - private def laminext(name: String): Dep = - ivy"io.laminext::$name::${V.laminar}" - - lazy val laminextCore: Dep = laminext("core") - lazy val laminextTailwind: Dep = laminext("tailwind") - lazy val laminextFetch: Dep = laminext("fetch") - lazy val laminextValidationCore: Dep = laminext("validation-core") - lazy val laminextUI: Dep = laminext("ui") - - lazy val waypoint: Dep = - ivy"com.raquo::waypoint::${V.waypoint}" - - lazy val urlDsl: Dep = - ivy"be.doeraene::url-dsl::${V.urlDsl}" - - lazy val scalaJavaTime: Dep = - ivy"io.github.cquiroz::scala-java-time::2.3.0" - - lazy val scalaJavaLocales: Dep = - ivy"io.github.cquiroz::scala-java-locales::1.2.1" - - lazy val logbackClassic: Dep = - ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" - } - - trait AkkaLibs { - - self: SlickLibs => - - object akka { - val V = Versions.akka - val tOrg = "com.typesafe.akka" - val lOrg = "com.lightbend.akka" - - def akkaMod(name: String): Dep = - ivy"$tOrg::akka-$name:$V" - - lazy val actor: Dep = akkaMod("actor") - lazy val actorTyped: Dep = akkaMod("actor-typed") - lazy val stream: Dep = akkaMod("stream") - lazy val persistence: Dep = akkaMod("persistence-typed") - lazy val persistenceQuery: Dep = akkaMod("persistence-query") - lazy val persistenceJdbc: Dep = - ivy"$lOrg::akka-persistence-jdbc:5.0.4" - val persistenceTestKit: Dep = ivy"$tOrg::akka-persistence-testkit:$V" - - object http { - val V = Versions.akkaHttp - - lazy val http: Dep = ivy"$tOrg::akka-http:$V" - lazy val sprayJson: Dep = ivy"$tOrg::akka-http-spray-json:$V" - - } - - object projection { - val V = "1.2.2" - - lazy val core: Dep = - ivy"$lOrg::akka-projection-core:$V" - lazy val eventsourced: Dep = - ivy"$lOrg::akka-projection-eventsourced:$V" - lazy val slick: Dep = - ivy"$lOrg::akka-projection-slick:$V" - lazy val jdbc: Dep = - ivy"$lOrg::akka-projection-jdbc:$V" - } - - object profiles { - // TODO: deal with cross-version for Scala3 (for3Use2_13) - lazy val eventsourcedJdbcProjection: Agg[Dep] = Agg( - persistenceQuery, - projection.core, - projection.eventsourced, - projection.slick, - persistenceJdbc - ) ++ slick.default - } - } - } - - trait SlickLibs { - object slick { - val V = IWMaterials.Versions.slick - val org = "com.typesafe.slick" - - lazy val slick: Dep = ivy"$org::slick:$V" - lazy val hikaricp: Dep = ivy"$org::slick-hikaricp:$V" - lazy val default: Agg[Dep] = Agg(slick, hikaricp) - } - } - -} diff --git a/iw/build.sc b/iw/build.sc deleted file mode 100644 index fbbc3b8..0000000 --- a/iw/build.sc +++ /dev/null @@ -1,144 +0,0 @@ -import mill._, scalalib._, scalajslib._ - -import $file.bom - -object support { - val Deps = bom.IWMaterials.Deps - val Versions = bom.IWMaterials.Versions - - trait CommonModule extends ScalaModule { - def scalaVersion = "3.1.1" - def scalacOptions = Seq( - "-encoding", - "utf8", - "-deprecation", - "-explain-types", - "-explain", - "-feature", - "-language:experimental.macros", - "-language:higherKinds", - "-language:implicitConversions", - "-unchecked", - "-Xfatal-warnings", - "-Ykind-projector" - ) - } - - trait CommonJSModule extends CommonModule with ScalaJSModule { - def scalaJSVersion = "1.8.0" - } - - trait CrossPlatformModule extends Module { outer => - def ivyDeps: T[Agg[Dep]] = Agg[Dep]() - def jsDeps: T[Agg[Dep]] = Agg[Dep]() - def jvmDeps: T[Agg[Dep]] = Agg[Dep]() - def moduleDeps: Seq[Module] = Seq[Module]() - - trait PlatformModule extends CommonModule { - def platform: String - override def millSourcePath = outer.millSourcePath - override def ivyDeps = outer.ivyDeps() ++ (platform match { - case "js" => jsDeps() - case _ => jvmDeps() - }) - override def moduleDeps = outer.moduleDeps.collect { - case m: CrossPlatformModule => - platform match { - case "js" => m.js - case _ => m.jvm - } - case m: JavaModule => m - } - } - - trait JsModule extends PlatformModule with CommonJSModule { - def platform = "js" - } - - trait JvmModule extends PlatformModule { - def platform = "jvm" - } - - val js: JsModule - val jvm: JvmModule - } - - trait PureCrossModule extends CrossPlatformModule { - override object js extends JsModule - override object jvm extends JvmModule - } - - trait PureCrossSbtModule extends CrossPlatformModule { - override object js extends JsModule with SbtModule - override object jvm extends JvmModule with SbtModule - } - - trait FullCrossSbtModule extends CrossPlatformModule { - trait FullSources extends JavaModule { self: PlatformModule => - override def sources = T.sources( - millSourcePath / platform / "src" / "main" / "scala", - millSourcePath / "shared" / "src" / "main" / "scala" - ) - - override def resources = T.sources( - millSourcePath / platform / "src" / "main" / "resources", - millSourcePath / "shared" / "src" / "main" / "resources" - ) - } - - override object js extends JsModule with FullSources - override object jvm extends JvmModule with FullSources - } - -} - -import support._ - -object mongo extends CommonModule { - def ivyDeps = Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - ivy"org.mongodb.scala::mongo-scala-driver:4.2.3".withDottyCompat( - scalaVersion() - ) - ) -} - -object tapir extends FullCrossSbtModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson, Deps.zioJson) - def jvmDeps = Agg(Deps.tapirZIO, Deps.tapirZIOHttp4sServer) - def jsDeps = Agg(Deps.tapirSttpClient) -} - -object akkaPersistence extends CommonModule { - def millSourcePath = build.millSourcePath / "akka-persistence" - def ivyDeps = (Agg( - Deps.akka.persistenceQuery, - Deps.akka.persistenceJdbc, - Deps.akka.projection.core, - Deps.akka.projection.eventsourced, - Deps.akka.projection.slick - ) ++ Deps.slick.default).map(_.withDottyCompat(scalaVersion())) ++ Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - Deps.akka.persistence.withDottyCompat(scalaVersion()), - ivy"com.typesafe.akka::akka-cluster-sharding-typed:${Deps.akka.V}" - .withDottyCompat(scalaVersion()) - ) -} - -object ui extends CommonJSModule { - def ivyDeps = Agg( - Deps.zio, - Deps.laminar, - Deps.zioJson, - Deps.waypoint, - Deps.urlDsl, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) -} diff --git a/iw/domain.sc b/iw/domain.sc deleted file mode 100644 index b09c56b..0000000 --- a/iw/domain.sc +++ /dev/null @@ -1,78 +0,0 @@ -import mill._, scalalib._ - -import $file.{build => ff}, ff.support._ - -trait DomainModule extends Module { - // General extra model deps - def modelModules: Seq[Module] = Seq.empty - // General extra codecs deps - def codecsModules: Seq[Module] = Seq.empty - // General extra endpoints deps - def endpointsModules: Seq[Module] = Seq.empty - - // Implementation deps for repo - def repoModules: Seq[JavaModule] = Seq.empty - - object shared extends Module { - object model extends PureCrossModule { - def moduleDeps = modelModules - def ivyDeps = Agg(Deps.zioPrelude) - } - object codecs extends PureCrossModule { - def moduleDeps = Seq(model) ++ codecsModules - def ivyDeps = Agg(Deps.zioJson) - } - } - - trait CommonProjects extends Module { - object model extends PureCrossModule { - def ivyDeps = Agg(Deps.zioPrelude) - def moduleDeps = Seq(shared.model) - } - object codecs extends PureCrossModule { - def moduleDeps = - Seq(model, shared.model, shared.codecs, ff.tapir) - } - object endpoints extends PureCrossModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson) - def moduleDeps = Seq(model, codecs) ++ endpointsModules - } - object client extends CommonJSModule { - def moduleDeps = Seq(endpoints.js) - } - object components extends CommonJSModule { - def ivyDeps = Agg( - Deps.laminar, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) - def moduleDeps = Seq(model.js, codecs.js, client, ff.ui) - } - } - - object query extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(repo, query.endpoints.jvm) - } - object repo extends CommonModule { - def ivyDeps = Agg(Deps.zio) - def moduleDeps = Seq(model.jvm, codecs.jvm) ++ repoModules - } - object projection extends CommonModule { - def moduleDeps = Seq(repo, ff.akkaPersistence) - } - } - - object command extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(entity, command.endpoints.jvm) - } - object entity extends CommonModule { - def moduleDeps = Seq(model.jvm, codecs.jvm, ff.akkaPersistence) - } - } -} diff --git a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala b/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala deleted file mode 100644 index 0fa3493..0000000 --- a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala +++ /dev/null @@ -1,53 +0,0 @@ -package works.iterative.mongo - -import zio.* -import zio.json.* -import zio.config.* -import org.mongodb.scala.* -import org.mongodb.scala.model.Filters.* -import org.bson.json.JsonObject -import org.mongodb.scala.model.ReplaceOptions -import org.mongodb.scala.bson.conversions.Bson - -case class MongoConfig(uri: String) - -object MongoConfig: - val configDesc = - import ConfigDescriptor.* - nested("MONGO")(string("URI").default("mongodb://localhost:27017")) - .to[MongoConfig] - val fromEnv = ZConfig.fromSystemEnv(configDesc) - -extension (m: MongoClient.type) - def layer: RLayer[MongoConfig, MongoClient] = - ZIO - .serviceWithZIO[MongoConfig](c => Task.attempt(MongoClient(c.uri))) - .toLayer - -class MongoJsonRepository[Elem, Key, Criteria]( - collection: MongoCollection[JsonObject], - toFilter: Criteria => Bson, - idFilter: Elem => (String, Key) -)(using JsonCodec[Elem]) { - def matching(criteria: Criteria): Task[Seq[Elem]] = - val filter = toFilter(criteria) - val query = collection.find(filter) - - for - result <- ZIO.fromFuture(_ => query.toFuture) - proof <- ZIO.collect(result)(j => - ZIO.fromOption(j.getJson.fromJson[Elem].toOption) - ) - yield proof - - def put(elem: Elem): Task[Unit] = - Task.async(cb => - collection - .replaceOne( - equal.tupled(idFilter(elem)), - JsonObject(elem.toJson), - ReplaceOptions().upsert(true) - ) - .subscribe(_ => cb(Task.unit), t => cb(Task.fail(t))) - ) -} diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala deleted file mode 100644 index 68d7196..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala +++ /dev/null @@ -1,12 +0,0 @@ -package works.iterative.tapir - -import sttp.model.Uri - -opaque type BaseUri = Option[Uri] - -object BaseUri: - - def apply(optU: Option[Uri]): BaseUri = optU - def apply(u: Uri): BaseUri = Some(u) - - extension (v: BaseUri) def toUri: Option[Uri] = v diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 653bc51..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,22 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.client.sttp.SttpClientInterpreter -import sttp.tapir.PublicEndpoint -import sttp.tapir.client.sttp.WebSocketToPipe -import scala.concurrent.Future -import sttp.client3.SttpBackend -import sttp.capabilities.WebSockets - -trait CustomTapirPlatformSpecific extends SttpClientInterpreter: - self: CustomTapir => - - type Backend = SttpBackend[Future, WebSockets] - - def makeClient[I, E, O]( - endpoint: PublicEndpoint[I, E, O, Any] - )(using - baseUri: BaseUri, - backend: Backend, - wsToPipe: WebSocketToPipe[Any] - ): I => Future[O] = - toClientThrowErrors(endpoint, baseUri.toUri, backend) diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 2545fbd..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,5 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.ztapir.ZTapir - -trait CustomTapirPlatformSpecific extends ZTapir diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala deleted file mode 100644 index 55e4ca1..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala +++ /dev/null @@ -1,7 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.server.http4s.ztapir.ZHttp4sServerInterpreter - -trait Http4sCustomTapir[Env] - extends CustomTapir - with ZHttp4sServerInterpreter[Env] diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala deleted file mode 100644 index dd52e9b..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala +++ /dev/null @@ -1,14 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.Tapir -import sttp.tapir.json.zio.TapirJsonZio -import sttp.tapir.TapirAliases - -trait CustomTapir - extends Tapir - with TapirJsonZio - with TapirAliases - with CustomTapirPlatformSpecific: - given Schema[ServerError] = Schema.derived - -object CustomTapir extends CustomTapir diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala deleted file mode 100644 index 715591d..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.tapir - -import zio.json.* - -sealed trait ServerError -case class InternalServerError(msg: String) extends ServerError -object InternalServerError: - def fromThrowable(t: Throwable): ServerError = InternalServerError( - t.getMessage - ) - -object ServerError: - given JsonCodec[ServerError] = DeriveJsonCodec.gen diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/File.scala deleted file mode 100644 index 09f6c07..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala +++ /dev/null @@ -1,3 +0,0 @@ -package works.iterative.services.files - -case class File(url: String, name: String) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala deleted file mode 100644 index e7e6627..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.akka - -// Base class for all command-processing related exceptions from handlers -sealed abstract class CommandHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandNotAvailable[C, S](cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd není dostupný ve stavu $state" - ) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandRejected[C, S](reason: String, cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd byl ve stavu $state odmítnut s odůvodněním $reason" - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala deleted file mode 100644 index 72a5bf9..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.akka - -sealed abstract class EventHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -case class UnhandledEvent[Event, State](event: Event, state: State) - extends EventHandlerException( - s"Událost $event nastala ve stavu $state bez možnosti zpracování" - ) diff --git a/iw/bom.sc b/iw/bom.sc deleted file mode 100644 index e9266b8..0000000 --- a/iw/bom.sc +++ /dev/null @@ -1,227 +0,0 @@ -import mill._, scalalib._ - -object IWMaterials { - - object Versions { - val akka = "2.6.16" - val akkaHttp = "10.2.4" - val cats = "2.7.0" - val elastic4s = "7.12.2" - val http4s = "0.23.10" - val http4sPac4J = "4.0.0" - val laminar = "0.14.2" - val laminext = laminar - val logbackClassic = "1.2.10" - val pac4j = "5.2.0" - val play = "2.8.8" - val playJson = "2.9.2" - val scalaTest = "3.2.9" - val slick = "3.3.3" - val sttpClient = "3.5.0" - val tapir = "0.20.1" - val urlDsl = "0.4.0" - val waypoint = "0.5.0" - val zio = "2.0.0-RC2" - val zioConfig = "3.0.0-RC2" - val zioInteropCats = "3.3.0-RC2" - val zioJson = "0.3.0-RC3" - val zioLogging = "2.0.0-RC5" - val zioPrelude = "1.0.0-RC10" - val zioZMX = "0.0.11" - } - - object Deps extends AkkaLibs with SlickLibs { - import IWMaterials.{Versions => V} - - val zioOrg = "dev.zio" - - def zioLib(name: String, version: String): Dep = - ivy"$zioOrg::zio-$name::$version" - - lazy val zio: Dep = ivy"$zioOrg::zio:${V.zio}" - - lazy val zioTest: Dep = zioLib("test", V.zio) - lazy val zioTestSbt: Dep = zioLib("test", V.zio) - - lazy val zioConfig: Dep = zioLib("config", V.zioConfig) - lazy val zioConfigTypesafe: Dep = - zioLib("config-typesafe", V.zioConfig) - lazy val zioConfigMagnolia: Dep = - zioLib("config-magnolia", V.zioConfig) - - lazy val zioJson: Dep = zioLib("json", V.zioJson) - lazy val zioLogging: Dep = zioLib("logging", V.zioLogging) - lazy val zioLoggingSlf4j: Dep = - zioLib("logging-slf4j", V.zioLogging) - lazy val zioPrelude: Dep = zioLib("prelude", V.zioPrelude) - lazy val zioStreams: Dep = zioLib("streams", V.zio) - lazy val zioZMX: Dep = zioLib("zmx", V.zioZMX) - lazy val zioInteropCats: Dep = - zioLib("interop-cats", V.zioInteropCats) - - /* What is the equivalent? ZIOModule with prepared test config? - def useZIO(testConf: Configuration*): Agg[Dep] = Agg( - zio, - zioTest, - zioTestSbt, - testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework") - ) - */ - - /* - def useZIOAll(testConf: Configuration*): Seq[Def.Setting[_]] = - useZIO(testConf: _*) ++ Seq( - zioStreams, - zioConfig, - zioConfigTypesafe, - zioConfigMagnolia, - zioJson, - zioZMX, - zioLogging, - zioPrelude - ) - */ - - private val tapirOrg = "com.softwaremill.sttp.tapir" - def tapirLib(name: String): Dep = - ivy"${tapirOrg}::tapir-$name::${V.tapir}" - - lazy val tapirCore: Dep = tapirLib("core") - lazy val tapirZIO: Dep = tapirLib("zio") - lazy val tapirZIOJson: Dep = tapirLib("json-zio") - lazy val tapirSttpClient: Dep = tapirLib("sttp-client") - lazy val tapirCats: Dep = tapirLib("cats") - lazy val tapirZIOHttp4sServer: Dep = tapirLib("zio-http4s-server") - - private val sttpClientOrg = "com.softwaremill.sttp.client3" - def sttpClientLib(name: String): Dep = - ivy"${sttpClientOrg}::${name}:${V.sttpClient}" - - lazy val sttpClientCore: Dep = sttpClientLib("core") - - lazy val http4sBlazeServer: Dep = - ivy"org.http4s::http4s-blaze-server:${V.http4s}" - - lazy val http4sPac4J: Dep = - ivy"org.pac4j::http4s-pac4j:${V.http4sPac4J}" - lazy val pac4jOIDC: Dep = - ivy"org.pac4j:pac4j-oidc:${V.pac4j}" - - lazy val scalaTest: Dep = - ivy"org.scalatest::scalatest:${V.scalaTest}" - lazy val scalaTestPlusScalacheck: Dep = - ivy"org.scalatestplus::scalacheck-1-15:3.2.9.0" - lazy val playScalaTest: Dep = - ivy"org.scalatestplus.play::scalatestplus-play:5.1.0" - - private val playOrg = "com.typesafe.play" - lazy val playMailer: Dep = ivy"${playOrg}::play-mailer:8.0.1" - lazy val playServer: Dep = ivy"${playOrg}::play-server:${V.play}" - lazy val playAkkaServer: Dep = - ivy"${playOrg}::play-akka-http-server:${V.play}" - lazy val play: Dep = ivy"${playOrg}::play:${V.play}" - lazy val playAhcWs: Dep = ivy"${playOrg}::play-ahc-ws:${V.play}" - lazy val playJson: Dep = ivy"${playOrg}::play-json:${V.playJson}" - - private val elastic4sOrg = "com.sksamuel.elastic4s" - lazy val useElastic4S: Agg[Dep] = Agg( - ivy"${elastic4sOrg}::elastic4s-core:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-client-akka:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-http-streams:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-json-play:${V.elastic4s}" - ) - - lazy val laminar: Dep = ivy"com.raquo::laminar::${V.laminar}" - - private def laminext(name: String): Dep = - ivy"io.laminext::$name::${V.laminar}" - - lazy val laminextCore: Dep = laminext("core") - lazy val laminextTailwind: Dep = laminext("tailwind") - lazy val laminextFetch: Dep = laminext("fetch") - lazy val laminextValidationCore: Dep = laminext("validation-core") - lazy val laminextUI: Dep = laminext("ui") - - lazy val waypoint: Dep = - ivy"com.raquo::waypoint::${V.waypoint}" - - lazy val urlDsl: Dep = - ivy"be.doeraene::url-dsl::${V.urlDsl}" - - lazy val scalaJavaTime: Dep = - ivy"io.github.cquiroz::scala-java-time::2.3.0" - - lazy val scalaJavaLocales: Dep = - ivy"io.github.cquiroz::scala-java-locales::1.2.1" - - lazy val logbackClassic: Dep = - ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" - } - - trait AkkaLibs { - - self: SlickLibs => - - object akka { - val V = Versions.akka - val tOrg = "com.typesafe.akka" - val lOrg = "com.lightbend.akka" - - def akkaMod(name: String): Dep = - ivy"$tOrg::akka-$name:$V" - - lazy val actor: Dep = akkaMod("actor") - lazy val actorTyped: Dep = akkaMod("actor-typed") - lazy val stream: Dep = akkaMod("stream") - lazy val persistence: Dep = akkaMod("persistence-typed") - lazy val persistenceQuery: Dep = akkaMod("persistence-query") - lazy val persistenceJdbc: Dep = - ivy"$lOrg::akka-persistence-jdbc:5.0.4" - val persistenceTestKit: Dep = ivy"$tOrg::akka-persistence-testkit:$V" - - object http { - val V = Versions.akkaHttp - - lazy val http: Dep = ivy"$tOrg::akka-http:$V" - lazy val sprayJson: Dep = ivy"$tOrg::akka-http-spray-json:$V" - - } - - object projection { - val V = "1.2.2" - - lazy val core: Dep = - ivy"$lOrg::akka-projection-core:$V" - lazy val eventsourced: Dep = - ivy"$lOrg::akka-projection-eventsourced:$V" - lazy val slick: Dep = - ivy"$lOrg::akka-projection-slick:$V" - lazy val jdbc: Dep = - ivy"$lOrg::akka-projection-jdbc:$V" - } - - object profiles { - // TODO: deal with cross-version for Scala3 (for3Use2_13) - lazy val eventsourcedJdbcProjection: Agg[Dep] = Agg( - persistenceQuery, - projection.core, - projection.eventsourced, - projection.slick, - persistenceJdbc - ) ++ slick.default - } - } - } - - trait SlickLibs { - object slick { - val V = IWMaterials.Versions.slick - val org = "com.typesafe.slick" - - lazy val slick: Dep = ivy"$org::slick:$V" - lazy val hikaricp: Dep = ivy"$org::slick-hikaricp:$V" - lazy val default: Agg[Dep] = Agg(slick, hikaricp) - } - } - -} diff --git a/iw/build.sc b/iw/build.sc deleted file mode 100644 index fbbc3b8..0000000 --- a/iw/build.sc +++ /dev/null @@ -1,144 +0,0 @@ -import mill._, scalalib._, scalajslib._ - -import $file.bom - -object support { - val Deps = bom.IWMaterials.Deps - val Versions = bom.IWMaterials.Versions - - trait CommonModule extends ScalaModule { - def scalaVersion = "3.1.1" - def scalacOptions = Seq( - "-encoding", - "utf8", - "-deprecation", - "-explain-types", - "-explain", - "-feature", - "-language:experimental.macros", - "-language:higherKinds", - "-language:implicitConversions", - "-unchecked", - "-Xfatal-warnings", - "-Ykind-projector" - ) - } - - trait CommonJSModule extends CommonModule with ScalaJSModule { - def scalaJSVersion = "1.8.0" - } - - trait CrossPlatformModule extends Module { outer => - def ivyDeps: T[Agg[Dep]] = Agg[Dep]() - def jsDeps: T[Agg[Dep]] = Agg[Dep]() - def jvmDeps: T[Agg[Dep]] = Agg[Dep]() - def moduleDeps: Seq[Module] = Seq[Module]() - - trait PlatformModule extends CommonModule { - def platform: String - override def millSourcePath = outer.millSourcePath - override def ivyDeps = outer.ivyDeps() ++ (platform match { - case "js" => jsDeps() - case _ => jvmDeps() - }) - override def moduleDeps = outer.moduleDeps.collect { - case m: CrossPlatformModule => - platform match { - case "js" => m.js - case _ => m.jvm - } - case m: JavaModule => m - } - } - - trait JsModule extends PlatformModule with CommonJSModule { - def platform = "js" - } - - trait JvmModule extends PlatformModule { - def platform = "jvm" - } - - val js: JsModule - val jvm: JvmModule - } - - trait PureCrossModule extends CrossPlatformModule { - override object js extends JsModule - override object jvm extends JvmModule - } - - trait PureCrossSbtModule extends CrossPlatformModule { - override object js extends JsModule with SbtModule - override object jvm extends JvmModule with SbtModule - } - - trait FullCrossSbtModule extends CrossPlatformModule { - trait FullSources extends JavaModule { self: PlatformModule => - override def sources = T.sources( - millSourcePath / platform / "src" / "main" / "scala", - millSourcePath / "shared" / "src" / "main" / "scala" - ) - - override def resources = T.sources( - millSourcePath / platform / "src" / "main" / "resources", - millSourcePath / "shared" / "src" / "main" / "resources" - ) - } - - override object js extends JsModule with FullSources - override object jvm extends JvmModule with FullSources - } - -} - -import support._ - -object mongo extends CommonModule { - def ivyDeps = Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - ivy"org.mongodb.scala::mongo-scala-driver:4.2.3".withDottyCompat( - scalaVersion() - ) - ) -} - -object tapir extends FullCrossSbtModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson, Deps.zioJson) - def jvmDeps = Agg(Deps.tapirZIO, Deps.tapirZIOHttp4sServer) - def jsDeps = Agg(Deps.tapirSttpClient) -} - -object akkaPersistence extends CommonModule { - def millSourcePath = build.millSourcePath / "akka-persistence" - def ivyDeps = (Agg( - Deps.akka.persistenceQuery, - Deps.akka.persistenceJdbc, - Deps.akka.projection.core, - Deps.akka.projection.eventsourced, - Deps.akka.projection.slick - ) ++ Deps.slick.default).map(_.withDottyCompat(scalaVersion())) ++ Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - Deps.akka.persistence.withDottyCompat(scalaVersion()), - ivy"com.typesafe.akka::akka-cluster-sharding-typed:${Deps.akka.V}" - .withDottyCompat(scalaVersion()) - ) -} - -object ui extends CommonJSModule { - def ivyDeps = Agg( - Deps.zio, - Deps.laminar, - Deps.zioJson, - Deps.waypoint, - Deps.urlDsl, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) -} diff --git a/iw/domain.sc b/iw/domain.sc deleted file mode 100644 index b09c56b..0000000 --- a/iw/domain.sc +++ /dev/null @@ -1,78 +0,0 @@ -import mill._, scalalib._ - -import $file.{build => ff}, ff.support._ - -trait DomainModule extends Module { - // General extra model deps - def modelModules: Seq[Module] = Seq.empty - // General extra codecs deps - def codecsModules: Seq[Module] = Seq.empty - // General extra endpoints deps - def endpointsModules: Seq[Module] = Seq.empty - - // Implementation deps for repo - def repoModules: Seq[JavaModule] = Seq.empty - - object shared extends Module { - object model extends PureCrossModule { - def moduleDeps = modelModules - def ivyDeps = Agg(Deps.zioPrelude) - } - object codecs extends PureCrossModule { - def moduleDeps = Seq(model) ++ codecsModules - def ivyDeps = Agg(Deps.zioJson) - } - } - - trait CommonProjects extends Module { - object model extends PureCrossModule { - def ivyDeps = Agg(Deps.zioPrelude) - def moduleDeps = Seq(shared.model) - } - object codecs extends PureCrossModule { - def moduleDeps = - Seq(model, shared.model, shared.codecs, ff.tapir) - } - object endpoints extends PureCrossModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson) - def moduleDeps = Seq(model, codecs) ++ endpointsModules - } - object client extends CommonJSModule { - def moduleDeps = Seq(endpoints.js) - } - object components extends CommonJSModule { - def ivyDeps = Agg( - Deps.laminar, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) - def moduleDeps = Seq(model.js, codecs.js, client, ff.ui) - } - } - - object query extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(repo, query.endpoints.jvm) - } - object repo extends CommonModule { - def ivyDeps = Agg(Deps.zio) - def moduleDeps = Seq(model.jvm, codecs.jvm) ++ repoModules - } - object projection extends CommonModule { - def moduleDeps = Seq(repo, ff.akkaPersistence) - } - } - - object command extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(entity, command.endpoints.jvm) - } - object entity extends CommonModule { - def moduleDeps = Seq(model.jvm, codecs.jvm, ff.akkaPersistence) - } - } -} diff --git a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala b/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala deleted file mode 100644 index 0fa3493..0000000 --- a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala +++ /dev/null @@ -1,53 +0,0 @@ -package works.iterative.mongo - -import zio.* -import zio.json.* -import zio.config.* -import org.mongodb.scala.* -import org.mongodb.scala.model.Filters.* -import org.bson.json.JsonObject -import org.mongodb.scala.model.ReplaceOptions -import org.mongodb.scala.bson.conversions.Bson - -case class MongoConfig(uri: String) - -object MongoConfig: - val configDesc = - import ConfigDescriptor.* - nested("MONGO")(string("URI").default("mongodb://localhost:27017")) - .to[MongoConfig] - val fromEnv = ZConfig.fromSystemEnv(configDesc) - -extension (m: MongoClient.type) - def layer: RLayer[MongoConfig, MongoClient] = - ZIO - .serviceWithZIO[MongoConfig](c => Task.attempt(MongoClient(c.uri))) - .toLayer - -class MongoJsonRepository[Elem, Key, Criteria]( - collection: MongoCollection[JsonObject], - toFilter: Criteria => Bson, - idFilter: Elem => (String, Key) -)(using JsonCodec[Elem]) { - def matching(criteria: Criteria): Task[Seq[Elem]] = - val filter = toFilter(criteria) - val query = collection.find(filter) - - for - result <- ZIO.fromFuture(_ => query.toFuture) - proof <- ZIO.collect(result)(j => - ZIO.fromOption(j.getJson.fromJson[Elem].toOption) - ) - yield proof - - def put(elem: Elem): Task[Unit] = - Task.async(cb => - collection - .replaceOne( - equal.tupled(idFilter(elem)), - JsonObject(elem.toJson), - ReplaceOptions().upsert(true) - ) - .subscribe(_ => cb(Task.unit), t => cb(Task.fail(t))) - ) -} diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala deleted file mode 100644 index 68d7196..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala +++ /dev/null @@ -1,12 +0,0 @@ -package works.iterative.tapir - -import sttp.model.Uri - -opaque type BaseUri = Option[Uri] - -object BaseUri: - - def apply(optU: Option[Uri]): BaseUri = optU - def apply(u: Uri): BaseUri = Some(u) - - extension (v: BaseUri) def toUri: Option[Uri] = v diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 653bc51..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,22 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.client.sttp.SttpClientInterpreter -import sttp.tapir.PublicEndpoint -import sttp.tapir.client.sttp.WebSocketToPipe -import scala.concurrent.Future -import sttp.client3.SttpBackend -import sttp.capabilities.WebSockets - -trait CustomTapirPlatformSpecific extends SttpClientInterpreter: - self: CustomTapir => - - type Backend = SttpBackend[Future, WebSockets] - - def makeClient[I, E, O]( - endpoint: PublicEndpoint[I, E, O, Any] - )(using - baseUri: BaseUri, - backend: Backend, - wsToPipe: WebSocketToPipe[Any] - ): I => Future[O] = - toClientThrowErrors(endpoint, baseUri.toUri, backend) diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 2545fbd..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,5 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.ztapir.ZTapir - -trait CustomTapirPlatformSpecific extends ZTapir diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala deleted file mode 100644 index 55e4ca1..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala +++ /dev/null @@ -1,7 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.server.http4s.ztapir.ZHttp4sServerInterpreter - -trait Http4sCustomTapir[Env] - extends CustomTapir - with ZHttp4sServerInterpreter[Env] diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala deleted file mode 100644 index dd52e9b..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala +++ /dev/null @@ -1,14 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.Tapir -import sttp.tapir.json.zio.TapirJsonZio -import sttp.tapir.TapirAliases - -trait CustomTapir - extends Tapir - with TapirJsonZio - with TapirAliases - with CustomTapirPlatformSpecific: - given Schema[ServerError] = Schema.derived - -object CustomTapir extends CustomTapir diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala deleted file mode 100644 index 715591d..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.tapir - -import zio.json.* - -sealed trait ServerError -case class InternalServerError(msg: String) extends ServerError -object InternalServerError: - def fromThrowable(t: Throwable): ServerError = InternalServerError( - t.getMessage - ) - -object ServerError: - given JsonCodec[ServerError] = DeriveJsonCodec.gen diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/File.scala deleted file mode 100644 index 09f6c07..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala +++ /dev/null @@ -1,3 +0,0 @@ -package works.iterative.services.files - -case class File(url: String, name: String) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala deleted file mode 100644 index 46633a5..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala +++ /dev/null @@ -1,25 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.Renderable - -given Renderable[File] with - extension (m: File) - def toHtml: HtmlElement = - li( - cls("pl-3 pr-4 py-3 flex items-center justify-between text-sm"), - div( - cls("w-0 flex-1 flex items-center"), - Icons.solid - .paperclip() - .amend(svg.cls := "flex-shrink-0 text-gray-400"), - span(cls("ml-2 flex-1 w-0 truncate"), m.name) - ), - a( - href(m.url), - cls("font-medium text-indigo-600 hover:text-indigo-500"), - "Otevřít" - ) - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala deleted file mode 100644 index e7e6627..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.akka - -// Base class for all command-processing related exceptions from handlers -sealed abstract class CommandHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandNotAvailable[C, S](cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd není dostupný ve stavu $state" - ) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandRejected[C, S](reason: String, cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd byl ve stavu $state odmítnut s odůvodněním $reason" - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala deleted file mode 100644 index 72a5bf9..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.akka - -sealed abstract class EventHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -case class UnhandledEvent[Event, State](event: Event, state: State) - extends EventHandlerException( - s"Událost $event nastala ve stavu $state bez možnosti zpracování" - ) diff --git a/iw/bom.sc b/iw/bom.sc deleted file mode 100644 index e9266b8..0000000 --- a/iw/bom.sc +++ /dev/null @@ -1,227 +0,0 @@ -import mill._, scalalib._ - -object IWMaterials { - - object Versions { - val akka = "2.6.16" - val akkaHttp = "10.2.4" - val cats = "2.7.0" - val elastic4s = "7.12.2" - val http4s = "0.23.10" - val http4sPac4J = "4.0.0" - val laminar = "0.14.2" - val laminext = laminar - val logbackClassic = "1.2.10" - val pac4j = "5.2.0" - val play = "2.8.8" - val playJson = "2.9.2" - val scalaTest = "3.2.9" - val slick = "3.3.3" - val sttpClient = "3.5.0" - val tapir = "0.20.1" - val urlDsl = "0.4.0" - val waypoint = "0.5.0" - val zio = "2.0.0-RC2" - val zioConfig = "3.0.0-RC2" - val zioInteropCats = "3.3.0-RC2" - val zioJson = "0.3.0-RC3" - val zioLogging = "2.0.0-RC5" - val zioPrelude = "1.0.0-RC10" - val zioZMX = "0.0.11" - } - - object Deps extends AkkaLibs with SlickLibs { - import IWMaterials.{Versions => V} - - val zioOrg = "dev.zio" - - def zioLib(name: String, version: String): Dep = - ivy"$zioOrg::zio-$name::$version" - - lazy val zio: Dep = ivy"$zioOrg::zio:${V.zio}" - - lazy val zioTest: Dep = zioLib("test", V.zio) - lazy val zioTestSbt: Dep = zioLib("test", V.zio) - - lazy val zioConfig: Dep = zioLib("config", V.zioConfig) - lazy val zioConfigTypesafe: Dep = - zioLib("config-typesafe", V.zioConfig) - lazy val zioConfigMagnolia: Dep = - zioLib("config-magnolia", V.zioConfig) - - lazy val zioJson: Dep = zioLib("json", V.zioJson) - lazy val zioLogging: Dep = zioLib("logging", V.zioLogging) - lazy val zioLoggingSlf4j: Dep = - zioLib("logging-slf4j", V.zioLogging) - lazy val zioPrelude: Dep = zioLib("prelude", V.zioPrelude) - lazy val zioStreams: Dep = zioLib("streams", V.zio) - lazy val zioZMX: Dep = zioLib("zmx", V.zioZMX) - lazy val zioInteropCats: Dep = - zioLib("interop-cats", V.zioInteropCats) - - /* What is the equivalent? ZIOModule with prepared test config? - def useZIO(testConf: Configuration*): Agg[Dep] = Agg( - zio, - zioTest, - zioTestSbt, - testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework") - ) - */ - - /* - def useZIOAll(testConf: Configuration*): Seq[Def.Setting[_]] = - useZIO(testConf: _*) ++ Seq( - zioStreams, - zioConfig, - zioConfigTypesafe, - zioConfigMagnolia, - zioJson, - zioZMX, - zioLogging, - zioPrelude - ) - */ - - private val tapirOrg = "com.softwaremill.sttp.tapir" - def tapirLib(name: String): Dep = - ivy"${tapirOrg}::tapir-$name::${V.tapir}" - - lazy val tapirCore: Dep = tapirLib("core") - lazy val tapirZIO: Dep = tapirLib("zio") - lazy val tapirZIOJson: Dep = tapirLib("json-zio") - lazy val tapirSttpClient: Dep = tapirLib("sttp-client") - lazy val tapirCats: Dep = tapirLib("cats") - lazy val tapirZIOHttp4sServer: Dep = tapirLib("zio-http4s-server") - - private val sttpClientOrg = "com.softwaremill.sttp.client3" - def sttpClientLib(name: String): Dep = - ivy"${sttpClientOrg}::${name}:${V.sttpClient}" - - lazy val sttpClientCore: Dep = sttpClientLib("core") - - lazy val http4sBlazeServer: Dep = - ivy"org.http4s::http4s-blaze-server:${V.http4s}" - - lazy val http4sPac4J: Dep = - ivy"org.pac4j::http4s-pac4j:${V.http4sPac4J}" - lazy val pac4jOIDC: Dep = - ivy"org.pac4j:pac4j-oidc:${V.pac4j}" - - lazy val scalaTest: Dep = - ivy"org.scalatest::scalatest:${V.scalaTest}" - lazy val scalaTestPlusScalacheck: Dep = - ivy"org.scalatestplus::scalacheck-1-15:3.2.9.0" - lazy val playScalaTest: Dep = - ivy"org.scalatestplus.play::scalatestplus-play:5.1.0" - - private val playOrg = "com.typesafe.play" - lazy val playMailer: Dep = ivy"${playOrg}::play-mailer:8.0.1" - lazy val playServer: Dep = ivy"${playOrg}::play-server:${V.play}" - lazy val playAkkaServer: Dep = - ivy"${playOrg}::play-akka-http-server:${V.play}" - lazy val play: Dep = ivy"${playOrg}::play:${V.play}" - lazy val playAhcWs: Dep = ivy"${playOrg}::play-ahc-ws:${V.play}" - lazy val playJson: Dep = ivy"${playOrg}::play-json:${V.playJson}" - - private val elastic4sOrg = "com.sksamuel.elastic4s" - lazy val useElastic4S: Agg[Dep] = Agg( - ivy"${elastic4sOrg}::elastic4s-core:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-client-akka:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-http-streams:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-json-play:${V.elastic4s}" - ) - - lazy val laminar: Dep = ivy"com.raquo::laminar::${V.laminar}" - - private def laminext(name: String): Dep = - ivy"io.laminext::$name::${V.laminar}" - - lazy val laminextCore: Dep = laminext("core") - lazy val laminextTailwind: Dep = laminext("tailwind") - lazy val laminextFetch: Dep = laminext("fetch") - lazy val laminextValidationCore: Dep = laminext("validation-core") - lazy val laminextUI: Dep = laminext("ui") - - lazy val waypoint: Dep = - ivy"com.raquo::waypoint::${V.waypoint}" - - lazy val urlDsl: Dep = - ivy"be.doeraene::url-dsl::${V.urlDsl}" - - lazy val scalaJavaTime: Dep = - ivy"io.github.cquiroz::scala-java-time::2.3.0" - - lazy val scalaJavaLocales: Dep = - ivy"io.github.cquiroz::scala-java-locales::1.2.1" - - lazy val logbackClassic: Dep = - ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" - } - - trait AkkaLibs { - - self: SlickLibs => - - object akka { - val V = Versions.akka - val tOrg = "com.typesafe.akka" - val lOrg = "com.lightbend.akka" - - def akkaMod(name: String): Dep = - ivy"$tOrg::akka-$name:$V" - - lazy val actor: Dep = akkaMod("actor") - lazy val actorTyped: Dep = akkaMod("actor-typed") - lazy val stream: Dep = akkaMod("stream") - lazy val persistence: Dep = akkaMod("persistence-typed") - lazy val persistenceQuery: Dep = akkaMod("persistence-query") - lazy val persistenceJdbc: Dep = - ivy"$lOrg::akka-persistence-jdbc:5.0.4" - val persistenceTestKit: Dep = ivy"$tOrg::akka-persistence-testkit:$V" - - object http { - val V = Versions.akkaHttp - - lazy val http: Dep = ivy"$tOrg::akka-http:$V" - lazy val sprayJson: Dep = ivy"$tOrg::akka-http-spray-json:$V" - - } - - object projection { - val V = "1.2.2" - - lazy val core: Dep = - ivy"$lOrg::akka-projection-core:$V" - lazy val eventsourced: Dep = - ivy"$lOrg::akka-projection-eventsourced:$V" - lazy val slick: Dep = - ivy"$lOrg::akka-projection-slick:$V" - lazy val jdbc: Dep = - ivy"$lOrg::akka-projection-jdbc:$V" - } - - object profiles { - // TODO: deal with cross-version for Scala3 (for3Use2_13) - lazy val eventsourcedJdbcProjection: Agg[Dep] = Agg( - persistenceQuery, - projection.core, - projection.eventsourced, - projection.slick, - persistenceJdbc - ) ++ slick.default - } - } - } - - trait SlickLibs { - object slick { - val V = IWMaterials.Versions.slick - val org = "com.typesafe.slick" - - lazy val slick: Dep = ivy"$org::slick:$V" - lazy val hikaricp: Dep = ivy"$org::slick-hikaricp:$V" - lazy val default: Agg[Dep] = Agg(slick, hikaricp) - } - } - -} diff --git a/iw/build.sc b/iw/build.sc deleted file mode 100644 index fbbc3b8..0000000 --- a/iw/build.sc +++ /dev/null @@ -1,144 +0,0 @@ -import mill._, scalalib._, scalajslib._ - -import $file.bom - -object support { - val Deps = bom.IWMaterials.Deps - val Versions = bom.IWMaterials.Versions - - trait CommonModule extends ScalaModule { - def scalaVersion = "3.1.1" - def scalacOptions = Seq( - "-encoding", - "utf8", - "-deprecation", - "-explain-types", - "-explain", - "-feature", - "-language:experimental.macros", - "-language:higherKinds", - "-language:implicitConversions", - "-unchecked", - "-Xfatal-warnings", - "-Ykind-projector" - ) - } - - trait CommonJSModule extends CommonModule with ScalaJSModule { - def scalaJSVersion = "1.8.0" - } - - trait CrossPlatformModule extends Module { outer => - def ivyDeps: T[Agg[Dep]] = Agg[Dep]() - def jsDeps: T[Agg[Dep]] = Agg[Dep]() - def jvmDeps: T[Agg[Dep]] = Agg[Dep]() - def moduleDeps: Seq[Module] = Seq[Module]() - - trait PlatformModule extends CommonModule { - def platform: String - override def millSourcePath = outer.millSourcePath - override def ivyDeps = outer.ivyDeps() ++ (platform match { - case "js" => jsDeps() - case _ => jvmDeps() - }) - override def moduleDeps = outer.moduleDeps.collect { - case m: CrossPlatformModule => - platform match { - case "js" => m.js - case _ => m.jvm - } - case m: JavaModule => m - } - } - - trait JsModule extends PlatformModule with CommonJSModule { - def platform = "js" - } - - trait JvmModule extends PlatformModule { - def platform = "jvm" - } - - val js: JsModule - val jvm: JvmModule - } - - trait PureCrossModule extends CrossPlatformModule { - override object js extends JsModule - override object jvm extends JvmModule - } - - trait PureCrossSbtModule extends CrossPlatformModule { - override object js extends JsModule with SbtModule - override object jvm extends JvmModule with SbtModule - } - - trait FullCrossSbtModule extends CrossPlatformModule { - trait FullSources extends JavaModule { self: PlatformModule => - override def sources = T.sources( - millSourcePath / platform / "src" / "main" / "scala", - millSourcePath / "shared" / "src" / "main" / "scala" - ) - - override def resources = T.sources( - millSourcePath / platform / "src" / "main" / "resources", - millSourcePath / "shared" / "src" / "main" / "resources" - ) - } - - override object js extends JsModule with FullSources - override object jvm extends JvmModule with FullSources - } - -} - -import support._ - -object mongo extends CommonModule { - def ivyDeps = Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - ivy"org.mongodb.scala::mongo-scala-driver:4.2.3".withDottyCompat( - scalaVersion() - ) - ) -} - -object tapir extends FullCrossSbtModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson, Deps.zioJson) - def jvmDeps = Agg(Deps.tapirZIO, Deps.tapirZIOHttp4sServer) - def jsDeps = Agg(Deps.tapirSttpClient) -} - -object akkaPersistence extends CommonModule { - def millSourcePath = build.millSourcePath / "akka-persistence" - def ivyDeps = (Agg( - Deps.akka.persistenceQuery, - Deps.akka.persistenceJdbc, - Deps.akka.projection.core, - Deps.akka.projection.eventsourced, - Deps.akka.projection.slick - ) ++ Deps.slick.default).map(_.withDottyCompat(scalaVersion())) ++ Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - Deps.akka.persistence.withDottyCompat(scalaVersion()), - ivy"com.typesafe.akka::akka-cluster-sharding-typed:${Deps.akka.V}" - .withDottyCompat(scalaVersion()) - ) -} - -object ui extends CommonJSModule { - def ivyDeps = Agg( - Deps.zio, - Deps.laminar, - Deps.zioJson, - Deps.waypoint, - Deps.urlDsl, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) -} diff --git a/iw/domain.sc b/iw/domain.sc deleted file mode 100644 index b09c56b..0000000 --- a/iw/domain.sc +++ /dev/null @@ -1,78 +0,0 @@ -import mill._, scalalib._ - -import $file.{build => ff}, ff.support._ - -trait DomainModule extends Module { - // General extra model deps - def modelModules: Seq[Module] = Seq.empty - // General extra codecs deps - def codecsModules: Seq[Module] = Seq.empty - // General extra endpoints deps - def endpointsModules: Seq[Module] = Seq.empty - - // Implementation deps for repo - def repoModules: Seq[JavaModule] = Seq.empty - - object shared extends Module { - object model extends PureCrossModule { - def moduleDeps = modelModules - def ivyDeps = Agg(Deps.zioPrelude) - } - object codecs extends PureCrossModule { - def moduleDeps = Seq(model) ++ codecsModules - def ivyDeps = Agg(Deps.zioJson) - } - } - - trait CommonProjects extends Module { - object model extends PureCrossModule { - def ivyDeps = Agg(Deps.zioPrelude) - def moduleDeps = Seq(shared.model) - } - object codecs extends PureCrossModule { - def moduleDeps = - Seq(model, shared.model, shared.codecs, ff.tapir) - } - object endpoints extends PureCrossModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson) - def moduleDeps = Seq(model, codecs) ++ endpointsModules - } - object client extends CommonJSModule { - def moduleDeps = Seq(endpoints.js) - } - object components extends CommonJSModule { - def ivyDeps = Agg( - Deps.laminar, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) - def moduleDeps = Seq(model.js, codecs.js, client, ff.ui) - } - } - - object query extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(repo, query.endpoints.jvm) - } - object repo extends CommonModule { - def ivyDeps = Agg(Deps.zio) - def moduleDeps = Seq(model.jvm, codecs.jvm) ++ repoModules - } - object projection extends CommonModule { - def moduleDeps = Seq(repo, ff.akkaPersistence) - } - } - - object command extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(entity, command.endpoints.jvm) - } - object entity extends CommonModule { - def moduleDeps = Seq(model.jvm, codecs.jvm, ff.akkaPersistence) - } - } -} diff --git a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala b/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala deleted file mode 100644 index 0fa3493..0000000 --- a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala +++ /dev/null @@ -1,53 +0,0 @@ -package works.iterative.mongo - -import zio.* -import zio.json.* -import zio.config.* -import org.mongodb.scala.* -import org.mongodb.scala.model.Filters.* -import org.bson.json.JsonObject -import org.mongodb.scala.model.ReplaceOptions -import org.mongodb.scala.bson.conversions.Bson - -case class MongoConfig(uri: String) - -object MongoConfig: - val configDesc = - import ConfigDescriptor.* - nested("MONGO")(string("URI").default("mongodb://localhost:27017")) - .to[MongoConfig] - val fromEnv = ZConfig.fromSystemEnv(configDesc) - -extension (m: MongoClient.type) - def layer: RLayer[MongoConfig, MongoClient] = - ZIO - .serviceWithZIO[MongoConfig](c => Task.attempt(MongoClient(c.uri))) - .toLayer - -class MongoJsonRepository[Elem, Key, Criteria]( - collection: MongoCollection[JsonObject], - toFilter: Criteria => Bson, - idFilter: Elem => (String, Key) -)(using JsonCodec[Elem]) { - def matching(criteria: Criteria): Task[Seq[Elem]] = - val filter = toFilter(criteria) - val query = collection.find(filter) - - for - result <- ZIO.fromFuture(_ => query.toFuture) - proof <- ZIO.collect(result)(j => - ZIO.fromOption(j.getJson.fromJson[Elem].toOption) - ) - yield proof - - def put(elem: Elem): Task[Unit] = - Task.async(cb => - collection - .replaceOne( - equal.tupled(idFilter(elem)), - JsonObject(elem.toJson), - ReplaceOptions().upsert(true) - ) - .subscribe(_ => cb(Task.unit), t => cb(Task.fail(t))) - ) -} diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala deleted file mode 100644 index 68d7196..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala +++ /dev/null @@ -1,12 +0,0 @@ -package works.iterative.tapir - -import sttp.model.Uri - -opaque type BaseUri = Option[Uri] - -object BaseUri: - - def apply(optU: Option[Uri]): BaseUri = optU - def apply(u: Uri): BaseUri = Some(u) - - extension (v: BaseUri) def toUri: Option[Uri] = v diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 653bc51..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,22 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.client.sttp.SttpClientInterpreter -import sttp.tapir.PublicEndpoint -import sttp.tapir.client.sttp.WebSocketToPipe -import scala.concurrent.Future -import sttp.client3.SttpBackend -import sttp.capabilities.WebSockets - -trait CustomTapirPlatformSpecific extends SttpClientInterpreter: - self: CustomTapir => - - type Backend = SttpBackend[Future, WebSockets] - - def makeClient[I, E, O]( - endpoint: PublicEndpoint[I, E, O, Any] - )(using - baseUri: BaseUri, - backend: Backend, - wsToPipe: WebSocketToPipe[Any] - ): I => Future[O] = - toClientThrowErrors(endpoint, baseUri.toUri, backend) diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 2545fbd..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,5 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.ztapir.ZTapir - -trait CustomTapirPlatformSpecific extends ZTapir diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala deleted file mode 100644 index 55e4ca1..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala +++ /dev/null @@ -1,7 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.server.http4s.ztapir.ZHttp4sServerInterpreter - -trait Http4sCustomTapir[Env] - extends CustomTapir - with ZHttp4sServerInterpreter[Env] diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala deleted file mode 100644 index dd52e9b..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala +++ /dev/null @@ -1,14 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.Tapir -import sttp.tapir.json.zio.TapirJsonZio -import sttp.tapir.TapirAliases - -trait CustomTapir - extends Tapir - with TapirJsonZio - with TapirAliases - with CustomTapirPlatformSpecific: - given Schema[ServerError] = Schema.derived - -object CustomTapir extends CustomTapir diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala deleted file mode 100644 index 715591d..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.tapir - -import zio.json.* - -sealed trait ServerError -case class InternalServerError(msg: String) extends ServerError -object InternalServerError: - def fromThrowable(t: Throwable): ServerError = InternalServerError( - t.getMessage - ) - -object ServerError: - given JsonCodec[ServerError] = DeriveJsonCodec.gen diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/File.scala deleted file mode 100644 index 09f6c07..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala +++ /dev/null @@ -1,3 +0,0 @@ -package works.iterative.services.files - -case class File(url: String, name: String) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala deleted file mode 100644 index 46633a5..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala +++ /dev/null @@ -1,25 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.Renderable - -given Renderable[File] with - extension (m: File) - def toHtml: HtmlElement = - li( - cls("pl-3 pr-4 py-3 flex items-center justify-between text-sm"), - div( - cls("w-0 flex-1 flex items-center"), - Icons.solid - .paperclip() - .amend(svg.cls := "flex-shrink-0 text-gray-400"), - span(cls("ml-2 flex-1 w-0 truncate"), m.name) - ), - a( - href(m.url), - cls("font-medium text-indigo-600 hover:text-indigo-500"), - "Otevřít" - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala deleted file mode 100644 index ac59db8..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import io.laminext.syntax.core.{*, given} - -def FileList(files: List[File]): HtmlElement = - ul( - role("list"), - cls("border border-gray-200 rounded-md divide-y divide-gray-200"), - files.map(_.toHtml) - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala deleted file mode 100644 index e7e6627..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.akka - -// Base class for all command-processing related exceptions from handlers -sealed abstract class CommandHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandNotAvailable[C, S](cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd není dostupný ve stavu $state" - ) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandRejected[C, S](reason: String, cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd byl ve stavu $state odmítnut s odůvodněním $reason" - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala deleted file mode 100644 index 72a5bf9..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.akka - -sealed abstract class EventHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -case class UnhandledEvent[Event, State](event: Event, state: State) - extends EventHandlerException( - s"Událost $event nastala ve stavu $state bez možnosti zpracování" - ) diff --git a/iw/bom.sc b/iw/bom.sc deleted file mode 100644 index e9266b8..0000000 --- a/iw/bom.sc +++ /dev/null @@ -1,227 +0,0 @@ -import mill._, scalalib._ - -object IWMaterials { - - object Versions { - val akka = "2.6.16" - val akkaHttp = "10.2.4" - val cats = "2.7.0" - val elastic4s = "7.12.2" - val http4s = "0.23.10" - val http4sPac4J = "4.0.0" - val laminar = "0.14.2" - val laminext = laminar - val logbackClassic = "1.2.10" - val pac4j = "5.2.0" - val play = "2.8.8" - val playJson = "2.9.2" - val scalaTest = "3.2.9" - val slick = "3.3.3" - val sttpClient = "3.5.0" - val tapir = "0.20.1" - val urlDsl = "0.4.0" - val waypoint = "0.5.0" - val zio = "2.0.0-RC2" - val zioConfig = "3.0.0-RC2" - val zioInteropCats = "3.3.0-RC2" - val zioJson = "0.3.0-RC3" - val zioLogging = "2.0.0-RC5" - val zioPrelude = "1.0.0-RC10" - val zioZMX = "0.0.11" - } - - object Deps extends AkkaLibs with SlickLibs { - import IWMaterials.{Versions => V} - - val zioOrg = "dev.zio" - - def zioLib(name: String, version: String): Dep = - ivy"$zioOrg::zio-$name::$version" - - lazy val zio: Dep = ivy"$zioOrg::zio:${V.zio}" - - lazy val zioTest: Dep = zioLib("test", V.zio) - lazy val zioTestSbt: Dep = zioLib("test", V.zio) - - lazy val zioConfig: Dep = zioLib("config", V.zioConfig) - lazy val zioConfigTypesafe: Dep = - zioLib("config-typesafe", V.zioConfig) - lazy val zioConfigMagnolia: Dep = - zioLib("config-magnolia", V.zioConfig) - - lazy val zioJson: Dep = zioLib("json", V.zioJson) - lazy val zioLogging: Dep = zioLib("logging", V.zioLogging) - lazy val zioLoggingSlf4j: Dep = - zioLib("logging-slf4j", V.zioLogging) - lazy val zioPrelude: Dep = zioLib("prelude", V.zioPrelude) - lazy val zioStreams: Dep = zioLib("streams", V.zio) - lazy val zioZMX: Dep = zioLib("zmx", V.zioZMX) - lazy val zioInteropCats: Dep = - zioLib("interop-cats", V.zioInteropCats) - - /* What is the equivalent? ZIOModule with prepared test config? - def useZIO(testConf: Configuration*): Agg[Dep] = Agg( - zio, - zioTest, - zioTestSbt, - testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework") - ) - */ - - /* - def useZIOAll(testConf: Configuration*): Seq[Def.Setting[_]] = - useZIO(testConf: _*) ++ Seq( - zioStreams, - zioConfig, - zioConfigTypesafe, - zioConfigMagnolia, - zioJson, - zioZMX, - zioLogging, - zioPrelude - ) - */ - - private val tapirOrg = "com.softwaremill.sttp.tapir" - def tapirLib(name: String): Dep = - ivy"${tapirOrg}::tapir-$name::${V.tapir}" - - lazy val tapirCore: Dep = tapirLib("core") - lazy val tapirZIO: Dep = tapirLib("zio") - lazy val tapirZIOJson: Dep = tapirLib("json-zio") - lazy val tapirSttpClient: Dep = tapirLib("sttp-client") - lazy val tapirCats: Dep = tapirLib("cats") - lazy val tapirZIOHttp4sServer: Dep = tapirLib("zio-http4s-server") - - private val sttpClientOrg = "com.softwaremill.sttp.client3" - def sttpClientLib(name: String): Dep = - ivy"${sttpClientOrg}::${name}:${V.sttpClient}" - - lazy val sttpClientCore: Dep = sttpClientLib("core") - - lazy val http4sBlazeServer: Dep = - ivy"org.http4s::http4s-blaze-server:${V.http4s}" - - lazy val http4sPac4J: Dep = - ivy"org.pac4j::http4s-pac4j:${V.http4sPac4J}" - lazy val pac4jOIDC: Dep = - ivy"org.pac4j:pac4j-oidc:${V.pac4j}" - - lazy val scalaTest: Dep = - ivy"org.scalatest::scalatest:${V.scalaTest}" - lazy val scalaTestPlusScalacheck: Dep = - ivy"org.scalatestplus::scalacheck-1-15:3.2.9.0" - lazy val playScalaTest: Dep = - ivy"org.scalatestplus.play::scalatestplus-play:5.1.0" - - private val playOrg = "com.typesafe.play" - lazy val playMailer: Dep = ivy"${playOrg}::play-mailer:8.0.1" - lazy val playServer: Dep = ivy"${playOrg}::play-server:${V.play}" - lazy val playAkkaServer: Dep = - ivy"${playOrg}::play-akka-http-server:${V.play}" - lazy val play: Dep = ivy"${playOrg}::play:${V.play}" - lazy val playAhcWs: Dep = ivy"${playOrg}::play-ahc-ws:${V.play}" - lazy val playJson: Dep = ivy"${playOrg}::play-json:${V.playJson}" - - private val elastic4sOrg = "com.sksamuel.elastic4s" - lazy val useElastic4S: Agg[Dep] = Agg( - ivy"${elastic4sOrg}::elastic4s-core:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-client-akka:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-http-streams:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-json-play:${V.elastic4s}" - ) - - lazy val laminar: Dep = ivy"com.raquo::laminar::${V.laminar}" - - private def laminext(name: String): Dep = - ivy"io.laminext::$name::${V.laminar}" - - lazy val laminextCore: Dep = laminext("core") - lazy val laminextTailwind: Dep = laminext("tailwind") - lazy val laminextFetch: Dep = laminext("fetch") - lazy val laminextValidationCore: Dep = laminext("validation-core") - lazy val laminextUI: Dep = laminext("ui") - - lazy val waypoint: Dep = - ivy"com.raquo::waypoint::${V.waypoint}" - - lazy val urlDsl: Dep = - ivy"be.doeraene::url-dsl::${V.urlDsl}" - - lazy val scalaJavaTime: Dep = - ivy"io.github.cquiroz::scala-java-time::2.3.0" - - lazy val scalaJavaLocales: Dep = - ivy"io.github.cquiroz::scala-java-locales::1.2.1" - - lazy val logbackClassic: Dep = - ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" - } - - trait AkkaLibs { - - self: SlickLibs => - - object akka { - val V = Versions.akka - val tOrg = "com.typesafe.akka" - val lOrg = "com.lightbend.akka" - - def akkaMod(name: String): Dep = - ivy"$tOrg::akka-$name:$V" - - lazy val actor: Dep = akkaMod("actor") - lazy val actorTyped: Dep = akkaMod("actor-typed") - lazy val stream: Dep = akkaMod("stream") - lazy val persistence: Dep = akkaMod("persistence-typed") - lazy val persistenceQuery: Dep = akkaMod("persistence-query") - lazy val persistenceJdbc: Dep = - ivy"$lOrg::akka-persistence-jdbc:5.0.4" - val persistenceTestKit: Dep = ivy"$tOrg::akka-persistence-testkit:$V" - - object http { - val V = Versions.akkaHttp - - lazy val http: Dep = ivy"$tOrg::akka-http:$V" - lazy val sprayJson: Dep = ivy"$tOrg::akka-http-spray-json:$V" - - } - - object projection { - val V = "1.2.2" - - lazy val core: Dep = - ivy"$lOrg::akka-projection-core:$V" - lazy val eventsourced: Dep = - ivy"$lOrg::akka-projection-eventsourced:$V" - lazy val slick: Dep = - ivy"$lOrg::akka-projection-slick:$V" - lazy val jdbc: Dep = - ivy"$lOrg::akka-projection-jdbc:$V" - } - - object profiles { - // TODO: deal with cross-version for Scala3 (for3Use2_13) - lazy val eventsourcedJdbcProjection: Agg[Dep] = Agg( - persistenceQuery, - projection.core, - projection.eventsourced, - projection.slick, - persistenceJdbc - ) ++ slick.default - } - } - } - - trait SlickLibs { - object slick { - val V = IWMaterials.Versions.slick - val org = "com.typesafe.slick" - - lazy val slick: Dep = ivy"$org::slick:$V" - lazy val hikaricp: Dep = ivy"$org::slick-hikaricp:$V" - lazy val default: Agg[Dep] = Agg(slick, hikaricp) - } - } - -} diff --git a/iw/build.sc b/iw/build.sc deleted file mode 100644 index fbbc3b8..0000000 --- a/iw/build.sc +++ /dev/null @@ -1,144 +0,0 @@ -import mill._, scalalib._, scalajslib._ - -import $file.bom - -object support { - val Deps = bom.IWMaterials.Deps - val Versions = bom.IWMaterials.Versions - - trait CommonModule extends ScalaModule { - def scalaVersion = "3.1.1" - def scalacOptions = Seq( - "-encoding", - "utf8", - "-deprecation", - "-explain-types", - "-explain", - "-feature", - "-language:experimental.macros", - "-language:higherKinds", - "-language:implicitConversions", - "-unchecked", - "-Xfatal-warnings", - "-Ykind-projector" - ) - } - - trait CommonJSModule extends CommonModule with ScalaJSModule { - def scalaJSVersion = "1.8.0" - } - - trait CrossPlatformModule extends Module { outer => - def ivyDeps: T[Agg[Dep]] = Agg[Dep]() - def jsDeps: T[Agg[Dep]] = Agg[Dep]() - def jvmDeps: T[Agg[Dep]] = Agg[Dep]() - def moduleDeps: Seq[Module] = Seq[Module]() - - trait PlatformModule extends CommonModule { - def platform: String - override def millSourcePath = outer.millSourcePath - override def ivyDeps = outer.ivyDeps() ++ (platform match { - case "js" => jsDeps() - case _ => jvmDeps() - }) - override def moduleDeps = outer.moduleDeps.collect { - case m: CrossPlatformModule => - platform match { - case "js" => m.js - case _ => m.jvm - } - case m: JavaModule => m - } - } - - trait JsModule extends PlatformModule with CommonJSModule { - def platform = "js" - } - - trait JvmModule extends PlatformModule { - def platform = "jvm" - } - - val js: JsModule - val jvm: JvmModule - } - - trait PureCrossModule extends CrossPlatformModule { - override object js extends JsModule - override object jvm extends JvmModule - } - - trait PureCrossSbtModule extends CrossPlatformModule { - override object js extends JsModule with SbtModule - override object jvm extends JvmModule with SbtModule - } - - trait FullCrossSbtModule extends CrossPlatformModule { - trait FullSources extends JavaModule { self: PlatformModule => - override def sources = T.sources( - millSourcePath / platform / "src" / "main" / "scala", - millSourcePath / "shared" / "src" / "main" / "scala" - ) - - override def resources = T.sources( - millSourcePath / platform / "src" / "main" / "resources", - millSourcePath / "shared" / "src" / "main" / "resources" - ) - } - - override object js extends JsModule with FullSources - override object jvm extends JvmModule with FullSources - } - -} - -import support._ - -object mongo extends CommonModule { - def ivyDeps = Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - ivy"org.mongodb.scala::mongo-scala-driver:4.2.3".withDottyCompat( - scalaVersion() - ) - ) -} - -object tapir extends FullCrossSbtModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson, Deps.zioJson) - def jvmDeps = Agg(Deps.tapirZIO, Deps.tapirZIOHttp4sServer) - def jsDeps = Agg(Deps.tapirSttpClient) -} - -object akkaPersistence extends CommonModule { - def millSourcePath = build.millSourcePath / "akka-persistence" - def ivyDeps = (Agg( - Deps.akka.persistenceQuery, - Deps.akka.persistenceJdbc, - Deps.akka.projection.core, - Deps.akka.projection.eventsourced, - Deps.akka.projection.slick - ) ++ Deps.slick.default).map(_.withDottyCompat(scalaVersion())) ++ Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - Deps.akka.persistence.withDottyCompat(scalaVersion()), - ivy"com.typesafe.akka::akka-cluster-sharding-typed:${Deps.akka.V}" - .withDottyCompat(scalaVersion()) - ) -} - -object ui extends CommonJSModule { - def ivyDeps = Agg( - Deps.zio, - Deps.laminar, - Deps.zioJson, - Deps.waypoint, - Deps.urlDsl, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) -} diff --git a/iw/domain.sc b/iw/domain.sc deleted file mode 100644 index b09c56b..0000000 --- a/iw/domain.sc +++ /dev/null @@ -1,78 +0,0 @@ -import mill._, scalalib._ - -import $file.{build => ff}, ff.support._ - -trait DomainModule extends Module { - // General extra model deps - def modelModules: Seq[Module] = Seq.empty - // General extra codecs deps - def codecsModules: Seq[Module] = Seq.empty - // General extra endpoints deps - def endpointsModules: Seq[Module] = Seq.empty - - // Implementation deps for repo - def repoModules: Seq[JavaModule] = Seq.empty - - object shared extends Module { - object model extends PureCrossModule { - def moduleDeps = modelModules - def ivyDeps = Agg(Deps.zioPrelude) - } - object codecs extends PureCrossModule { - def moduleDeps = Seq(model) ++ codecsModules - def ivyDeps = Agg(Deps.zioJson) - } - } - - trait CommonProjects extends Module { - object model extends PureCrossModule { - def ivyDeps = Agg(Deps.zioPrelude) - def moduleDeps = Seq(shared.model) - } - object codecs extends PureCrossModule { - def moduleDeps = - Seq(model, shared.model, shared.codecs, ff.tapir) - } - object endpoints extends PureCrossModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson) - def moduleDeps = Seq(model, codecs) ++ endpointsModules - } - object client extends CommonJSModule { - def moduleDeps = Seq(endpoints.js) - } - object components extends CommonJSModule { - def ivyDeps = Agg( - Deps.laminar, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) - def moduleDeps = Seq(model.js, codecs.js, client, ff.ui) - } - } - - object query extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(repo, query.endpoints.jvm) - } - object repo extends CommonModule { - def ivyDeps = Agg(Deps.zio) - def moduleDeps = Seq(model.jvm, codecs.jvm) ++ repoModules - } - object projection extends CommonModule { - def moduleDeps = Seq(repo, ff.akkaPersistence) - } - } - - object command extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(entity, command.endpoints.jvm) - } - object entity extends CommonModule { - def moduleDeps = Seq(model.jvm, codecs.jvm, ff.akkaPersistence) - } - } -} diff --git a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala b/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala deleted file mode 100644 index 0fa3493..0000000 --- a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala +++ /dev/null @@ -1,53 +0,0 @@ -package works.iterative.mongo - -import zio.* -import zio.json.* -import zio.config.* -import org.mongodb.scala.* -import org.mongodb.scala.model.Filters.* -import org.bson.json.JsonObject -import org.mongodb.scala.model.ReplaceOptions -import org.mongodb.scala.bson.conversions.Bson - -case class MongoConfig(uri: String) - -object MongoConfig: - val configDesc = - import ConfigDescriptor.* - nested("MONGO")(string("URI").default("mongodb://localhost:27017")) - .to[MongoConfig] - val fromEnv = ZConfig.fromSystemEnv(configDesc) - -extension (m: MongoClient.type) - def layer: RLayer[MongoConfig, MongoClient] = - ZIO - .serviceWithZIO[MongoConfig](c => Task.attempt(MongoClient(c.uri))) - .toLayer - -class MongoJsonRepository[Elem, Key, Criteria]( - collection: MongoCollection[JsonObject], - toFilter: Criteria => Bson, - idFilter: Elem => (String, Key) -)(using JsonCodec[Elem]) { - def matching(criteria: Criteria): Task[Seq[Elem]] = - val filter = toFilter(criteria) - val query = collection.find(filter) - - for - result <- ZIO.fromFuture(_ => query.toFuture) - proof <- ZIO.collect(result)(j => - ZIO.fromOption(j.getJson.fromJson[Elem].toOption) - ) - yield proof - - def put(elem: Elem): Task[Unit] = - Task.async(cb => - collection - .replaceOne( - equal.tupled(idFilter(elem)), - JsonObject(elem.toJson), - ReplaceOptions().upsert(true) - ) - .subscribe(_ => cb(Task.unit), t => cb(Task.fail(t))) - ) -} diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala deleted file mode 100644 index 68d7196..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala +++ /dev/null @@ -1,12 +0,0 @@ -package works.iterative.tapir - -import sttp.model.Uri - -opaque type BaseUri = Option[Uri] - -object BaseUri: - - def apply(optU: Option[Uri]): BaseUri = optU - def apply(u: Uri): BaseUri = Some(u) - - extension (v: BaseUri) def toUri: Option[Uri] = v diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 653bc51..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,22 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.client.sttp.SttpClientInterpreter -import sttp.tapir.PublicEndpoint -import sttp.tapir.client.sttp.WebSocketToPipe -import scala.concurrent.Future -import sttp.client3.SttpBackend -import sttp.capabilities.WebSockets - -trait CustomTapirPlatformSpecific extends SttpClientInterpreter: - self: CustomTapir => - - type Backend = SttpBackend[Future, WebSockets] - - def makeClient[I, E, O]( - endpoint: PublicEndpoint[I, E, O, Any] - )(using - baseUri: BaseUri, - backend: Backend, - wsToPipe: WebSocketToPipe[Any] - ): I => Future[O] = - toClientThrowErrors(endpoint, baseUri.toUri, backend) diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 2545fbd..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,5 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.ztapir.ZTapir - -trait CustomTapirPlatformSpecific extends ZTapir diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala deleted file mode 100644 index 55e4ca1..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala +++ /dev/null @@ -1,7 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.server.http4s.ztapir.ZHttp4sServerInterpreter - -trait Http4sCustomTapir[Env] - extends CustomTapir - with ZHttp4sServerInterpreter[Env] diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala deleted file mode 100644 index dd52e9b..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala +++ /dev/null @@ -1,14 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.Tapir -import sttp.tapir.json.zio.TapirJsonZio -import sttp.tapir.TapirAliases - -trait CustomTapir - extends Tapir - with TapirJsonZio - with TapirAliases - with CustomTapirPlatformSpecific: - given Schema[ServerError] = Schema.derived - -object CustomTapir extends CustomTapir diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala deleted file mode 100644 index 715591d..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.tapir - -import zio.json.* - -sealed trait ServerError -case class InternalServerError(msg: String) extends ServerError -object InternalServerError: - def fromThrowable(t: Throwable): ServerError = InternalServerError( - t.getMessage - ) - -object ServerError: - given JsonCodec[ServerError] = DeriveJsonCodec.gen diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/File.scala deleted file mode 100644 index 09f6c07..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala +++ /dev/null @@ -1,3 +0,0 @@ -package works.iterative.services.files - -case class File(url: String, name: String) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala deleted file mode 100644 index 46633a5..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala +++ /dev/null @@ -1,25 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.Renderable - -given Renderable[File] with - extension (m: File) - def toHtml: HtmlElement = - li( - cls("pl-3 pr-4 py-3 flex items-center justify-between text-sm"), - div( - cls("w-0 flex-1 flex items-center"), - Icons.solid - .paperclip() - .amend(svg.cls := "flex-shrink-0 text-gray-400"), - span(cls("ml-2 flex-1 w-0 truncate"), m.name) - ), - a( - href(m.url), - cls("font-medium text-indigo-600 hover:text-indigo-500"), - "Otevřít" - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala deleted file mode 100644 index ac59db8..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import io.laminext.syntax.core.{*, given} - -def FileList(files: List[File]): HtmlElement = - ul( - role("list"), - cls("border border-gray-200 rounded-md divide-y divide-gray-200"), - files.map(_.toHtml) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala deleted file mode 100644 index 2e8f690..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala +++ /dev/null @@ -1,81 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import io.laminext.syntax.core.{*, given} - -object FilePicker: - sealed trait Event - sealed trait DoneEvent extends Event - case class SelectionUpdated(files: Set[File]) extends DoneEvent - case object SelectionCancelled extends DoneEvent - case object AvailableFilesRequested extends Event - - def apply( - currentFiles: Signal[List[File]], - availableFilesStream: EventStream[List[File]] - )(selectionUpdates: Observer[Event]): HtmlElement = - val (updatesStream, updatesObserver) = EventStream.withObserver[Event] - val selectorOpen = Var[Boolean](false) - - // This sequence tricks browser into displaying modal content centered - // Inspired by modal in headless ui playground - // https://github.com/tailwindlabs/headlessui/blob/fdd26297953080d5ec905dda0bf5ec9607897d86/packages/playground-react/pages/transitions/component-examples/modal.tsx#L78-L79 - inline def browserCenteringModalTrick: Modifier[HtmlElement] = - Seq[Modifier[HtmlElement]]( - span(cls("hidden sm:inline-block sm:h-screen sm:align-middle")), - "​" // Zero width space - ) - - inline def overlay: Modifier[HtmlElement] = - // Page overlay - /* TODO: transition - enter="ease-out duration-300" - enterFrom="opacity-0" - enterTo="opacity-100" - leave="ease-in duration-200" - leaveFrom="opacity-100" - leaveTo="opacity-0" - */ - div( - div( - onClick.mapTo(false) --> selectorOpen.writer, - cls("fixed inset-0 transition-opacity"), - div(cls("absolute inset-0 bg-gray-500 opacity-75")) - ) - ) - - inline def modalSelector: HtmlElement = - div( - cls("fixed inset-0 z-10 overflow-y-auto"), - div( - cls("text-center sm:block sm:p-0"), - overlay, - browserCenteringModalTrick, - child <-- currentFiles.map( - FileSelector(_, availableFilesStream)(updatesObserver) - ) - ) - ) - - div( - updatesStream --> selectionUpdates, - updatesStream.collect { case _: DoneEvent => - false - } --> selectorOpen.writer, - child.maybe <-- selectorOpen.signal.map(isOpen => - if isOpen then Some(modalSelector) else None - ), - div( - cls("flex flex-col space-y-5"), - child.maybe <-- currentFiles.map(files => - if files.isEmpty then None else Some(FileList(files)) - ), - button( - tpe := "button", - cls := "bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500", - "Zvolit soubory", - onClick.mapTo(true) --> selectorOpen.writer - ) - ) - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala deleted file mode 100644 index e7e6627..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.akka - -// Base class for all command-processing related exceptions from handlers -sealed abstract class CommandHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandNotAvailable[C, S](cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd není dostupný ve stavu $state" - ) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandRejected[C, S](reason: String, cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd byl ve stavu $state odmítnut s odůvodněním $reason" - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala deleted file mode 100644 index 72a5bf9..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.akka - -sealed abstract class EventHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -case class UnhandledEvent[Event, State](event: Event, state: State) - extends EventHandlerException( - s"Událost $event nastala ve stavu $state bez možnosti zpracování" - ) diff --git a/iw/bom.sc b/iw/bom.sc deleted file mode 100644 index e9266b8..0000000 --- a/iw/bom.sc +++ /dev/null @@ -1,227 +0,0 @@ -import mill._, scalalib._ - -object IWMaterials { - - object Versions { - val akka = "2.6.16" - val akkaHttp = "10.2.4" - val cats = "2.7.0" - val elastic4s = "7.12.2" - val http4s = "0.23.10" - val http4sPac4J = "4.0.0" - val laminar = "0.14.2" - val laminext = laminar - val logbackClassic = "1.2.10" - val pac4j = "5.2.0" - val play = "2.8.8" - val playJson = "2.9.2" - val scalaTest = "3.2.9" - val slick = "3.3.3" - val sttpClient = "3.5.0" - val tapir = "0.20.1" - val urlDsl = "0.4.0" - val waypoint = "0.5.0" - val zio = "2.0.0-RC2" - val zioConfig = "3.0.0-RC2" - val zioInteropCats = "3.3.0-RC2" - val zioJson = "0.3.0-RC3" - val zioLogging = "2.0.0-RC5" - val zioPrelude = "1.0.0-RC10" - val zioZMX = "0.0.11" - } - - object Deps extends AkkaLibs with SlickLibs { - import IWMaterials.{Versions => V} - - val zioOrg = "dev.zio" - - def zioLib(name: String, version: String): Dep = - ivy"$zioOrg::zio-$name::$version" - - lazy val zio: Dep = ivy"$zioOrg::zio:${V.zio}" - - lazy val zioTest: Dep = zioLib("test", V.zio) - lazy val zioTestSbt: Dep = zioLib("test", V.zio) - - lazy val zioConfig: Dep = zioLib("config", V.zioConfig) - lazy val zioConfigTypesafe: Dep = - zioLib("config-typesafe", V.zioConfig) - lazy val zioConfigMagnolia: Dep = - zioLib("config-magnolia", V.zioConfig) - - lazy val zioJson: Dep = zioLib("json", V.zioJson) - lazy val zioLogging: Dep = zioLib("logging", V.zioLogging) - lazy val zioLoggingSlf4j: Dep = - zioLib("logging-slf4j", V.zioLogging) - lazy val zioPrelude: Dep = zioLib("prelude", V.zioPrelude) - lazy val zioStreams: Dep = zioLib("streams", V.zio) - lazy val zioZMX: Dep = zioLib("zmx", V.zioZMX) - lazy val zioInteropCats: Dep = - zioLib("interop-cats", V.zioInteropCats) - - /* What is the equivalent? ZIOModule with prepared test config? - def useZIO(testConf: Configuration*): Agg[Dep] = Agg( - zio, - zioTest, - zioTestSbt, - testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework") - ) - */ - - /* - def useZIOAll(testConf: Configuration*): Seq[Def.Setting[_]] = - useZIO(testConf: _*) ++ Seq( - zioStreams, - zioConfig, - zioConfigTypesafe, - zioConfigMagnolia, - zioJson, - zioZMX, - zioLogging, - zioPrelude - ) - */ - - private val tapirOrg = "com.softwaremill.sttp.tapir" - def tapirLib(name: String): Dep = - ivy"${tapirOrg}::tapir-$name::${V.tapir}" - - lazy val tapirCore: Dep = tapirLib("core") - lazy val tapirZIO: Dep = tapirLib("zio") - lazy val tapirZIOJson: Dep = tapirLib("json-zio") - lazy val tapirSttpClient: Dep = tapirLib("sttp-client") - lazy val tapirCats: Dep = tapirLib("cats") - lazy val tapirZIOHttp4sServer: Dep = tapirLib("zio-http4s-server") - - private val sttpClientOrg = "com.softwaremill.sttp.client3" - def sttpClientLib(name: String): Dep = - ivy"${sttpClientOrg}::${name}:${V.sttpClient}" - - lazy val sttpClientCore: Dep = sttpClientLib("core") - - lazy val http4sBlazeServer: Dep = - ivy"org.http4s::http4s-blaze-server:${V.http4s}" - - lazy val http4sPac4J: Dep = - ivy"org.pac4j::http4s-pac4j:${V.http4sPac4J}" - lazy val pac4jOIDC: Dep = - ivy"org.pac4j:pac4j-oidc:${V.pac4j}" - - lazy val scalaTest: Dep = - ivy"org.scalatest::scalatest:${V.scalaTest}" - lazy val scalaTestPlusScalacheck: Dep = - ivy"org.scalatestplus::scalacheck-1-15:3.2.9.0" - lazy val playScalaTest: Dep = - ivy"org.scalatestplus.play::scalatestplus-play:5.1.0" - - private val playOrg = "com.typesafe.play" - lazy val playMailer: Dep = ivy"${playOrg}::play-mailer:8.0.1" - lazy val playServer: Dep = ivy"${playOrg}::play-server:${V.play}" - lazy val playAkkaServer: Dep = - ivy"${playOrg}::play-akka-http-server:${V.play}" - lazy val play: Dep = ivy"${playOrg}::play:${V.play}" - lazy val playAhcWs: Dep = ivy"${playOrg}::play-ahc-ws:${V.play}" - lazy val playJson: Dep = ivy"${playOrg}::play-json:${V.playJson}" - - private val elastic4sOrg = "com.sksamuel.elastic4s" - lazy val useElastic4S: Agg[Dep] = Agg( - ivy"${elastic4sOrg}::elastic4s-core:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-client-akka:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-http-streams:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-json-play:${V.elastic4s}" - ) - - lazy val laminar: Dep = ivy"com.raquo::laminar::${V.laminar}" - - private def laminext(name: String): Dep = - ivy"io.laminext::$name::${V.laminar}" - - lazy val laminextCore: Dep = laminext("core") - lazy val laminextTailwind: Dep = laminext("tailwind") - lazy val laminextFetch: Dep = laminext("fetch") - lazy val laminextValidationCore: Dep = laminext("validation-core") - lazy val laminextUI: Dep = laminext("ui") - - lazy val waypoint: Dep = - ivy"com.raquo::waypoint::${V.waypoint}" - - lazy val urlDsl: Dep = - ivy"be.doeraene::url-dsl::${V.urlDsl}" - - lazy val scalaJavaTime: Dep = - ivy"io.github.cquiroz::scala-java-time::2.3.0" - - lazy val scalaJavaLocales: Dep = - ivy"io.github.cquiroz::scala-java-locales::1.2.1" - - lazy val logbackClassic: Dep = - ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" - } - - trait AkkaLibs { - - self: SlickLibs => - - object akka { - val V = Versions.akka - val tOrg = "com.typesafe.akka" - val lOrg = "com.lightbend.akka" - - def akkaMod(name: String): Dep = - ivy"$tOrg::akka-$name:$V" - - lazy val actor: Dep = akkaMod("actor") - lazy val actorTyped: Dep = akkaMod("actor-typed") - lazy val stream: Dep = akkaMod("stream") - lazy val persistence: Dep = akkaMod("persistence-typed") - lazy val persistenceQuery: Dep = akkaMod("persistence-query") - lazy val persistenceJdbc: Dep = - ivy"$lOrg::akka-persistence-jdbc:5.0.4" - val persistenceTestKit: Dep = ivy"$tOrg::akka-persistence-testkit:$V" - - object http { - val V = Versions.akkaHttp - - lazy val http: Dep = ivy"$tOrg::akka-http:$V" - lazy val sprayJson: Dep = ivy"$tOrg::akka-http-spray-json:$V" - - } - - object projection { - val V = "1.2.2" - - lazy val core: Dep = - ivy"$lOrg::akka-projection-core:$V" - lazy val eventsourced: Dep = - ivy"$lOrg::akka-projection-eventsourced:$V" - lazy val slick: Dep = - ivy"$lOrg::akka-projection-slick:$V" - lazy val jdbc: Dep = - ivy"$lOrg::akka-projection-jdbc:$V" - } - - object profiles { - // TODO: deal with cross-version for Scala3 (for3Use2_13) - lazy val eventsourcedJdbcProjection: Agg[Dep] = Agg( - persistenceQuery, - projection.core, - projection.eventsourced, - projection.slick, - persistenceJdbc - ) ++ slick.default - } - } - } - - trait SlickLibs { - object slick { - val V = IWMaterials.Versions.slick - val org = "com.typesafe.slick" - - lazy val slick: Dep = ivy"$org::slick:$V" - lazy val hikaricp: Dep = ivy"$org::slick-hikaricp:$V" - lazy val default: Agg[Dep] = Agg(slick, hikaricp) - } - } - -} diff --git a/iw/build.sc b/iw/build.sc deleted file mode 100644 index fbbc3b8..0000000 --- a/iw/build.sc +++ /dev/null @@ -1,144 +0,0 @@ -import mill._, scalalib._, scalajslib._ - -import $file.bom - -object support { - val Deps = bom.IWMaterials.Deps - val Versions = bom.IWMaterials.Versions - - trait CommonModule extends ScalaModule { - def scalaVersion = "3.1.1" - def scalacOptions = Seq( - "-encoding", - "utf8", - "-deprecation", - "-explain-types", - "-explain", - "-feature", - "-language:experimental.macros", - "-language:higherKinds", - "-language:implicitConversions", - "-unchecked", - "-Xfatal-warnings", - "-Ykind-projector" - ) - } - - trait CommonJSModule extends CommonModule with ScalaJSModule { - def scalaJSVersion = "1.8.0" - } - - trait CrossPlatformModule extends Module { outer => - def ivyDeps: T[Agg[Dep]] = Agg[Dep]() - def jsDeps: T[Agg[Dep]] = Agg[Dep]() - def jvmDeps: T[Agg[Dep]] = Agg[Dep]() - def moduleDeps: Seq[Module] = Seq[Module]() - - trait PlatformModule extends CommonModule { - def platform: String - override def millSourcePath = outer.millSourcePath - override def ivyDeps = outer.ivyDeps() ++ (platform match { - case "js" => jsDeps() - case _ => jvmDeps() - }) - override def moduleDeps = outer.moduleDeps.collect { - case m: CrossPlatformModule => - platform match { - case "js" => m.js - case _ => m.jvm - } - case m: JavaModule => m - } - } - - trait JsModule extends PlatformModule with CommonJSModule { - def platform = "js" - } - - trait JvmModule extends PlatformModule { - def platform = "jvm" - } - - val js: JsModule - val jvm: JvmModule - } - - trait PureCrossModule extends CrossPlatformModule { - override object js extends JsModule - override object jvm extends JvmModule - } - - trait PureCrossSbtModule extends CrossPlatformModule { - override object js extends JsModule with SbtModule - override object jvm extends JvmModule with SbtModule - } - - trait FullCrossSbtModule extends CrossPlatformModule { - trait FullSources extends JavaModule { self: PlatformModule => - override def sources = T.sources( - millSourcePath / platform / "src" / "main" / "scala", - millSourcePath / "shared" / "src" / "main" / "scala" - ) - - override def resources = T.sources( - millSourcePath / platform / "src" / "main" / "resources", - millSourcePath / "shared" / "src" / "main" / "resources" - ) - } - - override object js extends JsModule with FullSources - override object jvm extends JvmModule with FullSources - } - -} - -import support._ - -object mongo extends CommonModule { - def ivyDeps = Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - ivy"org.mongodb.scala::mongo-scala-driver:4.2.3".withDottyCompat( - scalaVersion() - ) - ) -} - -object tapir extends FullCrossSbtModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson, Deps.zioJson) - def jvmDeps = Agg(Deps.tapirZIO, Deps.tapirZIOHttp4sServer) - def jsDeps = Agg(Deps.tapirSttpClient) -} - -object akkaPersistence extends CommonModule { - def millSourcePath = build.millSourcePath / "akka-persistence" - def ivyDeps = (Agg( - Deps.akka.persistenceQuery, - Deps.akka.persistenceJdbc, - Deps.akka.projection.core, - Deps.akka.projection.eventsourced, - Deps.akka.projection.slick - ) ++ Deps.slick.default).map(_.withDottyCompat(scalaVersion())) ++ Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - Deps.akka.persistence.withDottyCompat(scalaVersion()), - ivy"com.typesafe.akka::akka-cluster-sharding-typed:${Deps.akka.V}" - .withDottyCompat(scalaVersion()) - ) -} - -object ui extends CommonJSModule { - def ivyDeps = Agg( - Deps.zio, - Deps.laminar, - Deps.zioJson, - Deps.waypoint, - Deps.urlDsl, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) -} diff --git a/iw/domain.sc b/iw/domain.sc deleted file mode 100644 index b09c56b..0000000 --- a/iw/domain.sc +++ /dev/null @@ -1,78 +0,0 @@ -import mill._, scalalib._ - -import $file.{build => ff}, ff.support._ - -trait DomainModule extends Module { - // General extra model deps - def modelModules: Seq[Module] = Seq.empty - // General extra codecs deps - def codecsModules: Seq[Module] = Seq.empty - // General extra endpoints deps - def endpointsModules: Seq[Module] = Seq.empty - - // Implementation deps for repo - def repoModules: Seq[JavaModule] = Seq.empty - - object shared extends Module { - object model extends PureCrossModule { - def moduleDeps = modelModules - def ivyDeps = Agg(Deps.zioPrelude) - } - object codecs extends PureCrossModule { - def moduleDeps = Seq(model) ++ codecsModules - def ivyDeps = Agg(Deps.zioJson) - } - } - - trait CommonProjects extends Module { - object model extends PureCrossModule { - def ivyDeps = Agg(Deps.zioPrelude) - def moduleDeps = Seq(shared.model) - } - object codecs extends PureCrossModule { - def moduleDeps = - Seq(model, shared.model, shared.codecs, ff.tapir) - } - object endpoints extends PureCrossModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson) - def moduleDeps = Seq(model, codecs) ++ endpointsModules - } - object client extends CommonJSModule { - def moduleDeps = Seq(endpoints.js) - } - object components extends CommonJSModule { - def ivyDeps = Agg( - Deps.laminar, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) - def moduleDeps = Seq(model.js, codecs.js, client, ff.ui) - } - } - - object query extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(repo, query.endpoints.jvm) - } - object repo extends CommonModule { - def ivyDeps = Agg(Deps.zio) - def moduleDeps = Seq(model.jvm, codecs.jvm) ++ repoModules - } - object projection extends CommonModule { - def moduleDeps = Seq(repo, ff.akkaPersistence) - } - } - - object command extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(entity, command.endpoints.jvm) - } - object entity extends CommonModule { - def moduleDeps = Seq(model.jvm, codecs.jvm, ff.akkaPersistence) - } - } -} diff --git a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala b/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala deleted file mode 100644 index 0fa3493..0000000 --- a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala +++ /dev/null @@ -1,53 +0,0 @@ -package works.iterative.mongo - -import zio.* -import zio.json.* -import zio.config.* -import org.mongodb.scala.* -import org.mongodb.scala.model.Filters.* -import org.bson.json.JsonObject -import org.mongodb.scala.model.ReplaceOptions -import org.mongodb.scala.bson.conversions.Bson - -case class MongoConfig(uri: String) - -object MongoConfig: - val configDesc = - import ConfigDescriptor.* - nested("MONGO")(string("URI").default("mongodb://localhost:27017")) - .to[MongoConfig] - val fromEnv = ZConfig.fromSystemEnv(configDesc) - -extension (m: MongoClient.type) - def layer: RLayer[MongoConfig, MongoClient] = - ZIO - .serviceWithZIO[MongoConfig](c => Task.attempt(MongoClient(c.uri))) - .toLayer - -class MongoJsonRepository[Elem, Key, Criteria]( - collection: MongoCollection[JsonObject], - toFilter: Criteria => Bson, - idFilter: Elem => (String, Key) -)(using JsonCodec[Elem]) { - def matching(criteria: Criteria): Task[Seq[Elem]] = - val filter = toFilter(criteria) - val query = collection.find(filter) - - for - result <- ZIO.fromFuture(_ => query.toFuture) - proof <- ZIO.collect(result)(j => - ZIO.fromOption(j.getJson.fromJson[Elem].toOption) - ) - yield proof - - def put(elem: Elem): Task[Unit] = - Task.async(cb => - collection - .replaceOne( - equal.tupled(idFilter(elem)), - JsonObject(elem.toJson), - ReplaceOptions().upsert(true) - ) - .subscribe(_ => cb(Task.unit), t => cb(Task.fail(t))) - ) -} diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala deleted file mode 100644 index 68d7196..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala +++ /dev/null @@ -1,12 +0,0 @@ -package works.iterative.tapir - -import sttp.model.Uri - -opaque type BaseUri = Option[Uri] - -object BaseUri: - - def apply(optU: Option[Uri]): BaseUri = optU - def apply(u: Uri): BaseUri = Some(u) - - extension (v: BaseUri) def toUri: Option[Uri] = v diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 653bc51..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,22 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.client.sttp.SttpClientInterpreter -import sttp.tapir.PublicEndpoint -import sttp.tapir.client.sttp.WebSocketToPipe -import scala.concurrent.Future -import sttp.client3.SttpBackend -import sttp.capabilities.WebSockets - -trait CustomTapirPlatformSpecific extends SttpClientInterpreter: - self: CustomTapir => - - type Backend = SttpBackend[Future, WebSockets] - - def makeClient[I, E, O]( - endpoint: PublicEndpoint[I, E, O, Any] - )(using - baseUri: BaseUri, - backend: Backend, - wsToPipe: WebSocketToPipe[Any] - ): I => Future[O] = - toClientThrowErrors(endpoint, baseUri.toUri, backend) diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 2545fbd..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,5 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.ztapir.ZTapir - -trait CustomTapirPlatformSpecific extends ZTapir diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala deleted file mode 100644 index 55e4ca1..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala +++ /dev/null @@ -1,7 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.server.http4s.ztapir.ZHttp4sServerInterpreter - -trait Http4sCustomTapir[Env] - extends CustomTapir - with ZHttp4sServerInterpreter[Env] diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala deleted file mode 100644 index dd52e9b..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala +++ /dev/null @@ -1,14 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.Tapir -import sttp.tapir.json.zio.TapirJsonZio -import sttp.tapir.TapirAliases - -trait CustomTapir - extends Tapir - with TapirJsonZio - with TapirAliases - with CustomTapirPlatformSpecific: - given Schema[ServerError] = Schema.derived - -object CustomTapir extends CustomTapir diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala deleted file mode 100644 index 715591d..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.tapir - -import zio.json.* - -sealed trait ServerError -case class InternalServerError(msg: String) extends ServerError -object InternalServerError: - def fromThrowable(t: Throwable): ServerError = InternalServerError( - t.getMessage - ) - -object ServerError: - given JsonCodec[ServerError] = DeriveJsonCodec.gen diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/File.scala deleted file mode 100644 index 09f6c07..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala +++ /dev/null @@ -1,3 +0,0 @@ -package works.iterative.services.files - -case class File(url: String, name: String) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala deleted file mode 100644 index 46633a5..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala +++ /dev/null @@ -1,25 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.Renderable - -given Renderable[File] with - extension (m: File) - def toHtml: HtmlElement = - li( - cls("pl-3 pr-4 py-3 flex items-center justify-between text-sm"), - div( - cls("w-0 flex-1 flex items-center"), - Icons.solid - .paperclip() - .amend(svg.cls := "flex-shrink-0 text-gray-400"), - span(cls("ml-2 flex-1 w-0 truncate"), m.name) - ), - a( - href(m.url), - cls("font-medium text-indigo-600 hover:text-indigo-500"), - "Otevřít" - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala deleted file mode 100644 index ac59db8..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import io.laminext.syntax.core.{*, given} - -def FileList(files: List[File]): HtmlElement = - ul( - role("list"), - cls("border border-gray-200 rounded-md divide-y divide-gray-200"), - files.map(_.toHtml) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala deleted file mode 100644 index 2e8f690..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala +++ /dev/null @@ -1,81 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import io.laminext.syntax.core.{*, given} - -object FilePicker: - sealed trait Event - sealed trait DoneEvent extends Event - case class SelectionUpdated(files: Set[File]) extends DoneEvent - case object SelectionCancelled extends DoneEvent - case object AvailableFilesRequested extends Event - - def apply( - currentFiles: Signal[List[File]], - availableFilesStream: EventStream[List[File]] - )(selectionUpdates: Observer[Event]): HtmlElement = - val (updatesStream, updatesObserver) = EventStream.withObserver[Event] - val selectorOpen = Var[Boolean](false) - - // This sequence tricks browser into displaying modal content centered - // Inspired by modal in headless ui playground - // https://github.com/tailwindlabs/headlessui/blob/fdd26297953080d5ec905dda0bf5ec9607897d86/packages/playground-react/pages/transitions/component-examples/modal.tsx#L78-L79 - inline def browserCenteringModalTrick: Modifier[HtmlElement] = - Seq[Modifier[HtmlElement]]( - span(cls("hidden sm:inline-block sm:h-screen sm:align-middle")), - "​" // Zero width space - ) - - inline def overlay: Modifier[HtmlElement] = - // Page overlay - /* TODO: transition - enter="ease-out duration-300" - enterFrom="opacity-0" - enterTo="opacity-100" - leave="ease-in duration-200" - leaveFrom="opacity-100" - leaveTo="opacity-0" - */ - div( - div( - onClick.mapTo(false) --> selectorOpen.writer, - cls("fixed inset-0 transition-opacity"), - div(cls("absolute inset-0 bg-gray-500 opacity-75")) - ) - ) - - inline def modalSelector: HtmlElement = - div( - cls("fixed inset-0 z-10 overflow-y-auto"), - div( - cls("text-center sm:block sm:p-0"), - overlay, - browserCenteringModalTrick, - child <-- currentFiles.map( - FileSelector(_, availableFilesStream)(updatesObserver) - ) - ) - ) - - div( - updatesStream --> selectionUpdates, - updatesStream.collect { case _: DoneEvent => - false - } --> selectorOpen.writer, - child.maybe <-- selectorOpen.signal.map(isOpen => - if isOpen then Some(modalSelector) else None - ), - div( - cls("flex flex-col space-y-5"), - child.maybe <-- currentFiles.map(files => - if files.isEmpty then None else Some(FileList(files)) - ), - button( - tpe := "button", - cls := "bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500", - "Zvolit soubory", - onClick.mapTo(true) --> selectorOpen.writer - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala deleted file mode 100644 index b43e07a..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala +++ /dev/null @@ -1,74 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.Loading -import io.laminext.syntax.core.{*, given} -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -object FileSelector: - import FilePicker._ - - def apply( - initialFiles: List[File], - availableFilesStream: EventStream[List[File]] - )(selectionUpdates: Observer[Event]): HtmlElement = - val selectedFiles = Var[Set[File]](initialFiles.to(Set)) - val availableFiles = availableFilesStream.startWithNone - // Request the files to display - selectionUpdates.onNext(AvailableFilesRequested) - div( - cls( - "inline-block transform overflow-hidden rounded-lg bg-white text-left align-bottom shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:align-middle" - ), - role("dialog"), - customHtmlAttr("aria.modal", BooleanAsTrueFalseStringCodec)(true), - aria.labelledBy("modal-headline"), - div( - cls("bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"), - div( - cls("sm:flex sm:items-start"), - div( - cls("mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"), - h3( - cls("text-lg font-medium leading-6 text-gray-900"), - idAttr("modal-headline"), - "Výběr souborů" - ) - ) - ), - child <-- availableFiles - .split(_ => ())((_, _, af) => FileTable(af, selectedFiles)) - .map(_.getOrElse(Loading)) - ), - div( - cls("bg-gray-50 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6"), - span( - cls("flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto"), - button( - typ("button"), - cls( - "focus:shadow-outline-green inline-flex w-full justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-base font-medium leading-6 text-white shadow-sm transition duration-150 ease-in-out hover:bg-indigo-500 focus:border-indigo-700 focus:outline-none sm:text-sm sm:leading-5" - ), - "Potvrdit", - composeEvents(onClick)( - _.sample(selectedFiles) - .map(SelectionUpdated(_)) - ) --> selectionUpdates - ) - ), - span( - cls("mt-3 flex w-full rounded-md shadow-sm sm:mt-0 sm:w-auto"), - button( - typ("button"), - cls( - "focus:shadow-outline-blue inline-flex w-full justify-center rounded-md border border-gray-300 bg-white px-4 py-2 text-base font-medium leading-6 text-gray-700 shadow-sm transition duration-150 ease-in-out hover:text-gray-500 focus:border-blue-300 focus:outline-none sm:text-sm sm:leading-5" - ), - "Zrušit", - onClick.mapTo(SelectionCancelled) --> selectionUpdates - ) - ) - ) - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala deleted file mode 100644 index e7e6627..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.akka - -// Base class for all command-processing related exceptions from handlers -sealed abstract class CommandHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandNotAvailable[C, S](cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd není dostupný ve stavu $state" - ) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandRejected[C, S](reason: String, cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd byl ve stavu $state odmítnut s odůvodněním $reason" - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala deleted file mode 100644 index 72a5bf9..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.akka - -sealed abstract class EventHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -case class UnhandledEvent[Event, State](event: Event, state: State) - extends EventHandlerException( - s"Událost $event nastala ve stavu $state bez možnosti zpracování" - ) diff --git a/iw/bom.sc b/iw/bom.sc deleted file mode 100644 index e9266b8..0000000 --- a/iw/bom.sc +++ /dev/null @@ -1,227 +0,0 @@ -import mill._, scalalib._ - -object IWMaterials { - - object Versions { - val akka = "2.6.16" - val akkaHttp = "10.2.4" - val cats = "2.7.0" - val elastic4s = "7.12.2" - val http4s = "0.23.10" - val http4sPac4J = "4.0.0" - val laminar = "0.14.2" - val laminext = laminar - val logbackClassic = "1.2.10" - val pac4j = "5.2.0" - val play = "2.8.8" - val playJson = "2.9.2" - val scalaTest = "3.2.9" - val slick = "3.3.3" - val sttpClient = "3.5.0" - val tapir = "0.20.1" - val urlDsl = "0.4.0" - val waypoint = "0.5.0" - val zio = "2.0.0-RC2" - val zioConfig = "3.0.0-RC2" - val zioInteropCats = "3.3.0-RC2" - val zioJson = "0.3.0-RC3" - val zioLogging = "2.0.0-RC5" - val zioPrelude = "1.0.0-RC10" - val zioZMX = "0.0.11" - } - - object Deps extends AkkaLibs with SlickLibs { - import IWMaterials.{Versions => V} - - val zioOrg = "dev.zio" - - def zioLib(name: String, version: String): Dep = - ivy"$zioOrg::zio-$name::$version" - - lazy val zio: Dep = ivy"$zioOrg::zio:${V.zio}" - - lazy val zioTest: Dep = zioLib("test", V.zio) - lazy val zioTestSbt: Dep = zioLib("test", V.zio) - - lazy val zioConfig: Dep = zioLib("config", V.zioConfig) - lazy val zioConfigTypesafe: Dep = - zioLib("config-typesafe", V.zioConfig) - lazy val zioConfigMagnolia: Dep = - zioLib("config-magnolia", V.zioConfig) - - lazy val zioJson: Dep = zioLib("json", V.zioJson) - lazy val zioLogging: Dep = zioLib("logging", V.zioLogging) - lazy val zioLoggingSlf4j: Dep = - zioLib("logging-slf4j", V.zioLogging) - lazy val zioPrelude: Dep = zioLib("prelude", V.zioPrelude) - lazy val zioStreams: Dep = zioLib("streams", V.zio) - lazy val zioZMX: Dep = zioLib("zmx", V.zioZMX) - lazy val zioInteropCats: Dep = - zioLib("interop-cats", V.zioInteropCats) - - /* What is the equivalent? ZIOModule with prepared test config? - def useZIO(testConf: Configuration*): Agg[Dep] = Agg( - zio, - zioTest, - zioTestSbt, - testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework") - ) - */ - - /* - def useZIOAll(testConf: Configuration*): Seq[Def.Setting[_]] = - useZIO(testConf: _*) ++ Seq( - zioStreams, - zioConfig, - zioConfigTypesafe, - zioConfigMagnolia, - zioJson, - zioZMX, - zioLogging, - zioPrelude - ) - */ - - private val tapirOrg = "com.softwaremill.sttp.tapir" - def tapirLib(name: String): Dep = - ivy"${tapirOrg}::tapir-$name::${V.tapir}" - - lazy val tapirCore: Dep = tapirLib("core") - lazy val tapirZIO: Dep = tapirLib("zio") - lazy val tapirZIOJson: Dep = tapirLib("json-zio") - lazy val tapirSttpClient: Dep = tapirLib("sttp-client") - lazy val tapirCats: Dep = tapirLib("cats") - lazy val tapirZIOHttp4sServer: Dep = tapirLib("zio-http4s-server") - - private val sttpClientOrg = "com.softwaremill.sttp.client3" - def sttpClientLib(name: String): Dep = - ivy"${sttpClientOrg}::${name}:${V.sttpClient}" - - lazy val sttpClientCore: Dep = sttpClientLib("core") - - lazy val http4sBlazeServer: Dep = - ivy"org.http4s::http4s-blaze-server:${V.http4s}" - - lazy val http4sPac4J: Dep = - ivy"org.pac4j::http4s-pac4j:${V.http4sPac4J}" - lazy val pac4jOIDC: Dep = - ivy"org.pac4j:pac4j-oidc:${V.pac4j}" - - lazy val scalaTest: Dep = - ivy"org.scalatest::scalatest:${V.scalaTest}" - lazy val scalaTestPlusScalacheck: Dep = - ivy"org.scalatestplus::scalacheck-1-15:3.2.9.0" - lazy val playScalaTest: Dep = - ivy"org.scalatestplus.play::scalatestplus-play:5.1.0" - - private val playOrg = "com.typesafe.play" - lazy val playMailer: Dep = ivy"${playOrg}::play-mailer:8.0.1" - lazy val playServer: Dep = ivy"${playOrg}::play-server:${V.play}" - lazy val playAkkaServer: Dep = - ivy"${playOrg}::play-akka-http-server:${V.play}" - lazy val play: Dep = ivy"${playOrg}::play:${V.play}" - lazy val playAhcWs: Dep = ivy"${playOrg}::play-ahc-ws:${V.play}" - lazy val playJson: Dep = ivy"${playOrg}::play-json:${V.playJson}" - - private val elastic4sOrg = "com.sksamuel.elastic4s" - lazy val useElastic4S: Agg[Dep] = Agg( - ivy"${elastic4sOrg}::elastic4s-core:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-client-akka:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-http-streams:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-json-play:${V.elastic4s}" - ) - - lazy val laminar: Dep = ivy"com.raquo::laminar::${V.laminar}" - - private def laminext(name: String): Dep = - ivy"io.laminext::$name::${V.laminar}" - - lazy val laminextCore: Dep = laminext("core") - lazy val laminextTailwind: Dep = laminext("tailwind") - lazy val laminextFetch: Dep = laminext("fetch") - lazy val laminextValidationCore: Dep = laminext("validation-core") - lazy val laminextUI: Dep = laminext("ui") - - lazy val waypoint: Dep = - ivy"com.raquo::waypoint::${V.waypoint}" - - lazy val urlDsl: Dep = - ivy"be.doeraene::url-dsl::${V.urlDsl}" - - lazy val scalaJavaTime: Dep = - ivy"io.github.cquiroz::scala-java-time::2.3.0" - - lazy val scalaJavaLocales: Dep = - ivy"io.github.cquiroz::scala-java-locales::1.2.1" - - lazy val logbackClassic: Dep = - ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" - } - - trait AkkaLibs { - - self: SlickLibs => - - object akka { - val V = Versions.akka - val tOrg = "com.typesafe.akka" - val lOrg = "com.lightbend.akka" - - def akkaMod(name: String): Dep = - ivy"$tOrg::akka-$name:$V" - - lazy val actor: Dep = akkaMod("actor") - lazy val actorTyped: Dep = akkaMod("actor-typed") - lazy val stream: Dep = akkaMod("stream") - lazy val persistence: Dep = akkaMod("persistence-typed") - lazy val persistenceQuery: Dep = akkaMod("persistence-query") - lazy val persistenceJdbc: Dep = - ivy"$lOrg::akka-persistence-jdbc:5.0.4" - val persistenceTestKit: Dep = ivy"$tOrg::akka-persistence-testkit:$V" - - object http { - val V = Versions.akkaHttp - - lazy val http: Dep = ivy"$tOrg::akka-http:$V" - lazy val sprayJson: Dep = ivy"$tOrg::akka-http-spray-json:$V" - - } - - object projection { - val V = "1.2.2" - - lazy val core: Dep = - ivy"$lOrg::akka-projection-core:$V" - lazy val eventsourced: Dep = - ivy"$lOrg::akka-projection-eventsourced:$V" - lazy val slick: Dep = - ivy"$lOrg::akka-projection-slick:$V" - lazy val jdbc: Dep = - ivy"$lOrg::akka-projection-jdbc:$V" - } - - object profiles { - // TODO: deal with cross-version for Scala3 (for3Use2_13) - lazy val eventsourcedJdbcProjection: Agg[Dep] = Agg( - persistenceQuery, - projection.core, - projection.eventsourced, - projection.slick, - persistenceJdbc - ) ++ slick.default - } - } - } - - trait SlickLibs { - object slick { - val V = IWMaterials.Versions.slick - val org = "com.typesafe.slick" - - lazy val slick: Dep = ivy"$org::slick:$V" - lazy val hikaricp: Dep = ivy"$org::slick-hikaricp:$V" - lazy val default: Agg[Dep] = Agg(slick, hikaricp) - } - } - -} diff --git a/iw/build.sc b/iw/build.sc deleted file mode 100644 index fbbc3b8..0000000 --- a/iw/build.sc +++ /dev/null @@ -1,144 +0,0 @@ -import mill._, scalalib._, scalajslib._ - -import $file.bom - -object support { - val Deps = bom.IWMaterials.Deps - val Versions = bom.IWMaterials.Versions - - trait CommonModule extends ScalaModule { - def scalaVersion = "3.1.1" - def scalacOptions = Seq( - "-encoding", - "utf8", - "-deprecation", - "-explain-types", - "-explain", - "-feature", - "-language:experimental.macros", - "-language:higherKinds", - "-language:implicitConversions", - "-unchecked", - "-Xfatal-warnings", - "-Ykind-projector" - ) - } - - trait CommonJSModule extends CommonModule with ScalaJSModule { - def scalaJSVersion = "1.8.0" - } - - trait CrossPlatformModule extends Module { outer => - def ivyDeps: T[Agg[Dep]] = Agg[Dep]() - def jsDeps: T[Agg[Dep]] = Agg[Dep]() - def jvmDeps: T[Agg[Dep]] = Agg[Dep]() - def moduleDeps: Seq[Module] = Seq[Module]() - - trait PlatformModule extends CommonModule { - def platform: String - override def millSourcePath = outer.millSourcePath - override def ivyDeps = outer.ivyDeps() ++ (platform match { - case "js" => jsDeps() - case _ => jvmDeps() - }) - override def moduleDeps = outer.moduleDeps.collect { - case m: CrossPlatformModule => - platform match { - case "js" => m.js - case _ => m.jvm - } - case m: JavaModule => m - } - } - - trait JsModule extends PlatformModule with CommonJSModule { - def platform = "js" - } - - trait JvmModule extends PlatformModule { - def platform = "jvm" - } - - val js: JsModule - val jvm: JvmModule - } - - trait PureCrossModule extends CrossPlatformModule { - override object js extends JsModule - override object jvm extends JvmModule - } - - trait PureCrossSbtModule extends CrossPlatformModule { - override object js extends JsModule with SbtModule - override object jvm extends JvmModule with SbtModule - } - - trait FullCrossSbtModule extends CrossPlatformModule { - trait FullSources extends JavaModule { self: PlatformModule => - override def sources = T.sources( - millSourcePath / platform / "src" / "main" / "scala", - millSourcePath / "shared" / "src" / "main" / "scala" - ) - - override def resources = T.sources( - millSourcePath / platform / "src" / "main" / "resources", - millSourcePath / "shared" / "src" / "main" / "resources" - ) - } - - override object js extends JsModule with FullSources - override object jvm extends JvmModule with FullSources - } - -} - -import support._ - -object mongo extends CommonModule { - def ivyDeps = Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - ivy"org.mongodb.scala::mongo-scala-driver:4.2.3".withDottyCompat( - scalaVersion() - ) - ) -} - -object tapir extends FullCrossSbtModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson, Deps.zioJson) - def jvmDeps = Agg(Deps.tapirZIO, Deps.tapirZIOHttp4sServer) - def jsDeps = Agg(Deps.tapirSttpClient) -} - -object akkaPersistence extends CommonModule { - def millSourcePath = build.millSourcePath / "akka-persistence" - def ivyDeps = (Agg( - Deps.akka.persistenceQuery, - Deps.akka.persistenceJdbc, - Deps.akka.projection.core, - Deps.akka.projection.eventsourced, - Deps.akka.projection.slick - ) ++ Deps.slick.default).map(_.withDottyCompat(scalaVersion())) ++ Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - Deps.akka.persistence.withDottyCompat(scalaVersion()), - ivy"com.typesafe.akka::akka-cluster-sharding-typed:${Deps.akka.V}" - .withDottyCompat(scalaVersion()) - ) -} - -object ui extends CommonJSModule { - def ivyDeps = Agg( - Deps.zio, - Deps.laminar, - Deps.zioJson, - Deps.waypoint, - Deps.urlDsl, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) -} diff --git a/iw/domain.sc b/iw/domain.sc deleted file mode 100644 index b09c56b..0000000 --- a/iw/domain.sc +++ /dev/null @@ -1,78 +0,0 @@ -import mill._, scalalib._ - -import $file.{build => ff}, ff.support._ - -trait DomainModule extends Module { - // General extra model deps - def modelModules: Seq[Module] = Seq.empty - // General extra codecs deps - def codecsModules: Seq[Module] = Seq.empty - // General extra endpoints deps - def endpointsModules: Seq[Module] = Seq.empty - - // Implementation deps for repo - def repoModules: Seq[JavaModule] = Seq.empty - - object shared extends Module { - object model extends PureCrossModule { - def moduleDeps = modelModules - def ivyDeps = Agg(Deps.zioPrelude) - } - object codecs extends PureCrossModule { - def moduleDeps = Seq(model) ++ codecsModules - def ivyDeps = Agg(Deps.zioJson) - } - } - - trait CommonProjects extends Module { - object model extends PureCrossModule { - def ivyDeps = Agg(Deps.zioPrelude) - def moduleDeps = Seq(shared.model) - } - object codecs extends PureCrossModule { - def moduleDeps = - Seq(model, shared.model, shared.codecs, ff.tapir) - } - object endpoints extends PureCrossModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson) - def moduleDeps = Seq(model, codecs) ++ endpointsModules - } - object client extends CommonJSModule { - def moduleDeps = Seq(endpoints.js) - } - object components extends CommonJSModule { - def ivyDeps = Agg( - Deps.laminar, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) - def moduleDeps = Seq(model.js, codecs.js, client, ff.ui) - } - } - - object query extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(repo, query.endpoints.jvm) - } - object repo extends CommonModule { - def ivyDeps = Agg(Deps.zio) - def moduleDeps = Seq(model.jvm, codecs.jvm) ++ repoModules - } - object projection extends CommonModule { - def moduleDeps = Seq(repo, ff.akkaPersistence) - } - } - - object command extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(entity, command.endpoints.jvm) - } - object entity extends CommonModule { - def moduleDeps = Seq(model.jvm, codecs.jvm, ff.akkaPersistence) - } - } -} diff --git a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala b/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala deleted file mode 100644 index 0fa3493..0000000 --- a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala +++ /dev/null @@ -1,53 +0,0 @@ -package works.iterative.mongo - -import zio.* -import zio.json.* -import zio.config.* -import org.mongodb.scala.* -import org.mongodb.scala.model.Filters.* -import org.bson.json.JsonObject -import org.mongodb.scala.model.ReplaceOptions -import org.mongodb.scala.bson.conversions.Bson - -case class MongoConfig(uri: String) - -object MongoConfig: - val configDesc = - import ConfigDescriptor.* - nested("MONGO")(string("URI").default("mongodb://localhost:27017")) - .to[MongoConfig] - val fromEnv = ZConfig.fromSystemEnv(configDesc) - -extension (m: MongoClient.type) - def layer: RLayer[MongoConfig, MongoClient] = - ZIO - .serviceWithZIO[MongoConfig](c => Task.attempt(MongoClient(c.uri))) - .toLayer - -class MongoJsonRepository[Elem, Key, Criteria]( - collection: MongoCollection[JsonObject], - toFilter: Criteria => Bson, - idFilter: Elem => (String, Key) -)(using JsonCodec[Elem]) { - def matching(criteria: Criteria): Task[Seq[Elem]] = - val filter = toFilter(criteria) - val query = collection.find(filter) - - for - result <- ZIO.fromFuture(_ => query.toFuture) - proof <- ZIO.collect(result)(j => - ZIO.fromOption(j.getJson.fromJson[Elem].toOption) - ) - yield proof - - def put(elem: Elem): Task[Unit] = - Task.async(cb => - collection - .replaceOne( - equal.tupled(idFilter(elem)), - JsonObject(elem.toJson), - ReplaceOptions().upsert(true) - ) - .subscribe(_ => cb(Task.unit), t => cb(Task.fail(t))) - ) -} diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala deleted file mode 100644 index 68d7196..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala +++ /dev/null @@ -1,12 +0,0 @@ -package works.iterative.tapir - -import sttp.model.Uri - -opaque type BaseUri = Option[Uri] - -object BaseUri: - - def apply(optU: Option[Uri]): BaseUri = optU - def apply(u: Uri): BaseUri = Some(u) - - extension (v: BaseUri) def toUri: Option[Uri] = v diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 653bc51..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,22 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.client.sttp.SttpClientInterpreter -import sttp.tapir.PublicEndpoint -import sttp.tapir.client.sttp.WebSocketToPipe -import scala.concurrent.Future -import sttp.client3.SttpBackend -import sttp.capabilities.WebSockets - -trait CustomTapirPlatformSpecific extends SttpClientInterpreter: - self: CustomTapir => - - type Backend = SttpBackend[Future, WebSockets] - - def makeClient[I, E, O]( - endpoint: PublicEndpoint[I, E, O, Any] - )(using - baseUri: BaseUri, - backend: Backend, - wsToPipe: WebSocketToPipe[Any] - ): I => Future[O] = - toClientThrowErrors(endpoint, baseUri.toUri, backend) diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 2545fbd..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,5 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.ztapir.ZTapir - -trait CustomTapirPlatformSpecific extends ZTapir diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala deleted file mode 100644 index 55e4ca1..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala +++ /dev/null @@ -1,7 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.server.http4s.ztapir.ZHttp4sServerInterpreter - -trait Http4sCustomTapir[Env] - extends CustomTapir - with ZHttp4sServerInterpreter[Env] diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala deleted file mode 100644 index dd52e9b..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala +++ /dev/null @@ -1,14 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.Tapir -import sttp.tapir.json.zio.TapirJsonZio -import sttp.tapir.TapirAliases - -trait CustomTapir - extends Tapir - with TapirJsonZio - with TapirAliases - with CustomTapirPlatformSpecific: - given Schema[ServerError] = Schema.derived - -object CustomTapir extends CustomTapir diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala deleted file mode 100644 index 715591d..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.tapir - -import zio.json.* - -sealed trait ServerError -case class InternalServerError(msg: String) extends ServerError -object InternalServerError: - def fromThrowable(t: Throwable): ServerError = InternalServerError( - t.getMessage - ) - -object ServerError: - given JsonCodec[ServerError] = DeriveJsonCodec.gen diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/File.scala deleted file mode 100644 index 09f6c07..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala +++ /dev/null @@ -1,3 +0,0 @@ -package works.iterative.services.files - -case class File(url: String, name: String) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala deleted file mode 100644 index 46633a5..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala +++ /dev/null @@ -1,25 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.Renderable - -given Renderable[File] with - extension (m: File) - def toHtml: HtmlElement = - li( - cls("pl-3 pr-4 py-3 flex items-center justify-between text-sm"), - div( - cls("w-0 flex-1 flex items-center"), - Icons.solid - .paperclip() - .amend(svg.cls := "flex-shrink-0 text-gray-400"), - span(cls("ml-2 flex-1 w-0 truncate"), m.name) - ), - a( - href(m.url), - cls("font-medium text-indigo-600 hover:text-indigo-500"), - "Otevřít" - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala deleted file mode 100644 index ac59db8..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import io.laminext.syntax.core.{*, given} - -def FileList(files: List[File]): HtmlElement = - ul( - role("list"), - cls("border border-gray-200 rounded-md divide-y divide-gray-200"), - files.map(_.toHtml) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala deleted file mode 100644 index 2e8f690..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala +++ /dev/null @@ -1,81 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import io.laminext.syntax.core.{*, given} - -object FilePicker: - sealed trait Event - sealed trait DoneEvent extends Event - case class SelectionUpdated(files: Set[File]) extends DoneEvent - case object SelectionCancelled extends DoneEvent - case object AvailableFilesRequested extends Event - - def apply( - currentFiles: Signal[List[File]], - availableFilesStream: EventStream[List[File]] - )(selectionUpdates: Observer[Event]): HtmlElement = - val (updatesStream, updatesObserver) = EventStream.withObserver[Event] - val selectorOpen = Var[Boolean](false) - - // This sequence tricks browser into displaying modal content centered - // Inspired by modal in headless ui playground - // https://github.com/tailwindlabs/headlessui/blob/fdd26297953080d5ec905dda0bf5ec9607897d86/packages/playground-react/pages/transitions/component-examples/modal.tsx#L78-L79 - inline def browserCenteringModalTrick: Modifier[HtmlElement] = - Seq[Modifier[HtmlElement]]( - span(cls("hidden sm:inline-block sm:h-screen sm:align-middle")), - "​" // Zero width space - ) - - inline def overlay: Modifier[HtmlElement] = - // Page overlay - /* TODO: transition - enter="ease-out duration-300" - enterFrom="opacity-0" - enterTo="opacity-100" - leave="ease-in duration-200" - leaveFrom="opacity-100" - leaveTo="opacity-0" - */ - div( - div( - onClick.mapTo(false) --> selectorOpen.writer, - cls("fixed inset-0 transition-opacity"), - div(cls("absolute inset-0 bg-gray-500 opacity-75")) - ) - ) - - inline def modalSelector: HtmlElement = - div( - cls("fixed inset-0 z-10 overflow-y-auto"), - div( - cls("text-center sm:block sm:p-0"), - overlay, - browserCenteringModalTrick, - child <-- currentFiles.map( - FileSelector(_, availableFilesStream)(updatesObserver) - ) - ) - ) - - div( - updatesStream --> selectionUpdates, - updatesStream.collect { case _: DoneEvent => - false - } --> selectorOpen.writer, - child.maybe <-- selectorOpen.signal.map(isOpen => - if isOpen then Some(modalSelector) else None - ), - div( - cls("flex flex-col space-y-5"), - child.maybe <-- currentFiles.map(files => - if files.isEmpty then None else Some(FileList(files)) - ), - button( - tpe := "button", - cls := "bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500", - "Zvolit soubory", - onClick.mapTo(true) --> selectorOpen.writer - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala deleted file mode 100644 index b43e07a..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala +++ /dev/null @@ -1,74 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.Loading -import io.laminext.syntax.core.{*, given} -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -object FileSelector: - import FilePicker._ - - def apply( - initialFiles: List[File], - availableFilesStream: EventStream[List[File]] - )(selectionUpdates: Observer[Event]): HtmlElement = - val selectedFiles = Var[Set[File]](initialFiles.to(Set)) - val availableFiles = availableFilesStream.startWithNone - // Request the files to display - selectionUpdates.onNext(AvailableFilesRequested) - div( - cls( - "inline-block transform overflow-hidden rounded-lg bg-white text-left align-bottom shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:align-middle" - ), - role("dialog"), - customHtmlAttr("aria.modal", BooleanAsTrueFalseStringCodec)(true), - aria.labelledBy("modal-headline"), - div( - cls("bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"), - div( - cls("sm:flex sm:items-start"), - div( - cls("mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"), - h3( - cls("text-lg font-medium leading-6 text-gray-900"), - idAttr("modal-headline"), - "Výběr souborů" - ) - ) - ), - child <-- availableFiles - .split(_ => ())((_, _, af) => FileTable(af, selectedFiles)) - .map(_.getOrElse(Loading)) - ), - div( - cls("bg-gray-50 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6"), - span( - cls("flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto"), - button( - typ("button"), - cls( - "focus:shadow-outline-green inline-flex w-full justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-base font-medium leading-6 text-white shadow-sm transition duration-150 ease-in-out hover:bg-indigo-500 focus:border-indigo-700 focus:outline-none sm:text-sm sm:leading-5" - ), - "Potvrdit", - composeEvents(onClick)( - _.sample(selectedFiles) - .map(SelectionUpdated(_)) - ) --> selectionUpdates - ) - ), - span( - cls("mt-3 flex w-full rounded-md shadow-sm sm:mt-0 sm:w-auto"), - button( - typ("button"), - cls( - "focus:shadow-outline-blue inline-flex w-full justify-center rounded-md border border-gray-300 bg-white px-4 py-2 text-base font-medium leading-6 text-gray-700 shadow-sm transition duration-150 ease-in-out hover:text-gray-500 focus:border-blue-300 focus:outline-none sm:text-sm sm:leading-5" - ), - "Zrušit", - onClick.mapTo(SelectionCancelled) --> selectionUpdates - ) - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala deleted file mode 100644 index aa11b2b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala +++ /dev/null @@ -1,91 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import works.iterative.ui.components.tailwind.Icons - -def FileTable( - files: Signal[List[File]], - selectedFiles: Var[Set[File]] -): HtmlElement = - val scope = customHtmlAttr("scope", StringAsIsCodec) - - def headerRow: HtmlElement = - val col = scope("col") - val baseM: Modifier[HtmlElement] = Seq(cls("px-6 py-3"), col) - val textH = cls( - "text-left text-xs font-medium text-gray-500 uppercase tracking-wider" - ) - tr( - th(baseM, span(cls("sr-only"), "Vybrat")), - th(baseM, textH, "Název"), - th(baseM, cls("relative"), span(cls("sr-only"), "Otevřít")) - ) - - def tableRow( - f: File, - idx: Int, - selected: Boolean - )(toggleSelection: Observer[Unit]): HtmlElement = - val baseC = cls("px-6 py-4 whitespace-nowrap text-sm") - tr( - cls(if idx % 2 == 0 then "bg-gray-50" else "bg-white"), - td( - cls("font-medium cursor-pointer"), - onClick.mapTo(()) --> toggleSelection, - cls(if selected then "text-green-900" else "text-gray-200"), - Icons.outline.`check-circle`().amend(svg.cls := "mx-auto"), - span(cls("sr-only"), if selected then "Vybráno" else "Nevybráno") - ), - td( - baseC, - cls("font-medium text-gray-900"), - f.name, - onClick.mapTo(()) --> toggleSelection - ), - td( - baseC, - cls("text-right font-medium"), - a( - href(f.url), - target("_blank"), - cls( - "flex items-center space-x-4 text-indigo-600 hover:text-indigo-900" - ), - Icons.outline.`external-link`(), - "Otevřít" - ) - ) - ) - - div( - cls("flex flex-col"), - div(cls("overflow-x-auto sm:-mx-6 lg:-mx-8")), - div( - cls("py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8"), - div( - cls("shadow overflow-hidden border-b border-gray-200 sm:rounded-lg"), - table( - cls("min-w-full divide-y divide-gray-200"), - thead( - cls("bg-gray-50"), - headerRow - ), - tbody( - children <-- files - .map(_.zipWithIndex) - .combineWithFn(selectedFiles)((f, sel) => - f.map((file, idx) => - val active = sel.contains(file) - tableRow(file, idx, active)( - selectedFiles.writer - .contramap(_ => if active then sel - file else sel + file) - ) - ) - ) - ) - ) - ) - ) - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala deleted file mode 100644 index e7e6627..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.akka - -// Base class for all command-processing related exceptions from handlers -sealed abstract class CommandHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandNotAvailable[C, S](cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd není dostupný ve stavu $state" - ) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandRejected[C, S](reason: String, cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd byl ve stavu $state odmítnut s odůvodněním $reason" - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala deleted file mode 100644 index 72a5bf9..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.akka - -sealed abstract class EventHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -case class UnhandledEvent[Event, State](event: Event, state: State) - extends EventHandlerException( - s"Událost $event nastala ve stavu $state bez možnosti zpracování" - ) diff --git a/iw/bom.sc b/iw/bom.sc deleted file mode 100644 index e9266b8..0000000 --- a/iw/bom.sc +++ /dev/null @@ -1,227 +0,0 @@ -import mill._, scalalib._ - -object IWMaterials { - - object Versions { - val akka = "2.6.16" - val akkaHttp = "10.2.4" - val cats = "2.7.0" - val elastic4s = "7.12.2" - val http4s = "0.23.10" - val http4sPac4J = "4.0.0" - val laminar = "0.14.2" - val laminext = laminar - val logbackClassic = "1.2.10" - val pac4j = "5.2.0" - val play = "2.8.8" - val playJson = "2.9.2" - val scalaTest = "3.2.9" - val slick = "3.3.3" - val sttpClient = "3.5.0" - val tapir = "0.20.1" - val urlDsl = "0.4.0" - val waypoint = "0.5.0" - val zio = "2.0.0-RC2" - val zioConfig = "3.0.0-RC2" - val zioInteropCats = "3.3.0-RC2" - val zioJson = "0.3.0-RC3" - val zioLogging = "2.0.0-RC5" - val zioPrelude = "1.0.0-RC10" - val zioZMX = "0.0.11" - } - - object Deps extends AkkaLibs with SlickLibs { - import IWMaterials.{Versions => V} - - val zioOrg = "dev.zio" - - def zioLib(name: String, version: String): Dep = - ivy"$zioOrg::zio-$name::$version" - - lazy val zio: Dep = ivy"$zioOrg::zio:${V.zio}" - - lazy val zioTest: Dep = zioLib("test", V.zio) - lazy val zioTestSbt: Dep = zioLib("test", V.zio) - - lazy val zioConfig: Dep = zioLib("config", V.zioConfig) - lazy val zioConfigTypesafe: Dep = - zioLib("config-typesafe", V.zioConfig) - lazy val zioConfigMagnolia: Dep = - zioLib("config-magnolia", V.zioConfig) - - lazy val zioJson: Dep = zioLib("json", V.zioJson) - lazy val zioLogging: Dep = zioLib("logging", V.zioLogging) - lazy val zioLoggingSlf4j: Dep = - zioLib("logging-slf4j", V.zioLogging) - lazy val zioPrelude: Dep = zioLib("prelude", V.zioPrelude) - lazy val zioStreams: Dep = zioLib("streams", V.zio) - lazy val zioZMX: Dep = zioLib("zmx", V.zioZMX) - lazy val zioInteropCats: Dep = - zioLib("interop-cats", V.zioInteropCats) - - /* What is the equivalent? ZIOModule with prepared test config? - def useZIO(testConf: Configuration*): Agg[Dep] = Agg( - zio, - zioTest, - zioTestSbt, - testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework") - ) - */ - - /* - def useZIOAll(testConf: Configuration*): Seq[Def.Setting[_]] = - useZIO(testConf: _*) ++ Seq( - zioStreams, - zioConfig, - zioConfigTypesafe, - zioConfigMagnolia, - zioJson, - zioZMX, - zioLogging, - zioPrelude - ) - */ - - private val tapirOrg = "com.softwaremill.sttp.tapir" - def tapirLib(name: String): Dep = - ivy"${tapirOrg}::tapir-$name::${V.tapir}" - - lazy val tapirCore: Dep = tapirLib("core") - lazy val tapirZIO: Dep = tapirLib("zio") - lazy val tapirZIOJson: Dep = tapirLib("json-zio") - lazy val tapirSttpClient: Dep = tapirLib("sttp-client") - lazy val tapirCats: Dep = tapirLib("cats") - lazy val tapirZIOHttp4sServer: Dep = tapirLib("zio-http4s-server") - - private val sttpClientOrg = "com.softwaremill.sttp.client3" - def sttpClientLib(name: String): Dep = - ivy"${sttpClientOrg}::${name}:${V.sttpClient}" - - lazy val sttpClientCore: Dep = sttpClientLib("core") - - lazy val http4sBlazeServer: Dep = - ivy"org.http4s::http4s-blaze-server:${V.http4s}" - - lazy val http4sPac4J: Dep = - ivy"org.pac4j::http4s-pac4j:${V.http4sPac4J}" - lazy val pac4jOIDC: Dep = - ivy"org.pac4j:pac4j-oidc:${V.pac4j}" - - lazy val scalaTest: Dep = - ivy"org.scalatest::scalatest:${V.scalaTest}" - lazy val scalaTestPlusScalacheck: Dep = - ivy"org.scalatestplus::scalacheck-1-15:3.2.9.0" - lazy val playScalaTest: Dep = - ivy"org.scalatestplus.play::scalatestplus-play:5.1.0" - - private val playOrg = "com.typesafe.play" - lazy val playMailer: Dep = ivy"${playOrg}::play-mailer:8.0.1" - lazy val playServer: Dep = ivy"${playOrg}::play-server:${V.play}" - lazy val playAkkaServer: Dep = - ivy"${playOrg}::play-akka-http-server:${V.play}" - lazy val play: Dep = ivy"${playOrg}::play:${V.play}" - lazy val playAhcWs: Dep = ivy"${playOrg}::play-ahc-ws:${V.play}" - lazy val playJson: Dep = ivy"${playOrg}::play-json:${V.playJson}" - - private val elastic4sOrg = "com.sksamuel.elastic4s" - lazy val useElastic4S: Agg[Dep] = Agg( - ivy"${elastic4sOrg}::elastic4s-core:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-client-akka:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-http-streams:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-json-play:${V.elastic4s}" - ) - - lazy val laminar: Dep = ivy"com.raquo::laminar::${V.laminar}" - - private def laminext(name: String): Dep = - ivy"io.laminext::$name::${V.laminar}" - - lazy val laminextCore: Dep = laminext("core") - lazy val laminextTailwind: Dep = laminext("tailwind") - lazy val laminextFetch: Dep = laminext("fetch") - lazy val laminextValidationCore: Dep = laminext("validation-core") - lazy val laminextUI: Dep = laminext("ui") - - lazy val waypoint: Dep = - ivy"com.raquo::waypoint::${V.waypoint}" - - lazy val urlDsl: Dep = - ivy"be.doeraene::url-dsl::${V.urlDsl}" - - lazy val scalaJavaTime: Dep = - ivy"io.github.cquiroz::scala-java-time::2.3.0" - - lazy val scalaJavaLocales: Dep = - ivy"io.github.cquiroz::scala-java-locales::1.2.1" - - lazy val logbackClassic: Dep = - ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" - } - - trait AkkaLibs { - - self: SlickLibs => - - object akka { - val V = Versions.akka - val tOrg = "com.typesafe.akka" - val lOrg = "com.lightbend.akka" - - def akkaMod(name: String): Dep = - ivy"$tOrg::akka-$name:$V" - - lazy val actor: Dep = akkaMod("actor") - lazy val actorTyped: Dep = akkaMod("actor-typed") - lazy val stream: Dep = akkaMod("stream") - lazy val persistence: Dep = akkaMod("persistence-typed") - lazy val persistenceQuery: Dep = akkaMod("persistence-query") - lazy val persistenceJdbc: Dep = - ivy"$lOrg::akka-persistence-jdbc:5.0.4" - val persistenceTestKit: Dep = ivy"$tOrg::akka-persistence-testkit:$V" - - object http { - val V = Versions.akkaHttp - - lazy val http: Dep = ivy"$tOrg::akka-http:$V" - lazy val sprayJson: Dep = ivy"$tOrg::akka-http-spray-json:$V" - - } - - object projection { - val V = "1.2.2" - - lazy val core: Dep = - ivy"$lOrg::akka-projection-core:$V" - lazy val eventsourced: Dep = - ivy"$lOrg::akka-projection-eventsourced:$V" - lazy val slick: Dep = - ivy"$lOrg::akka-projection-slick:$V" - lazy val jdbc: Dep = - ivy"$lOrg::akka-projection-jdbc:$V" - } - - object profiles { - // TODO: deal with cross-version for Scala3 (for3Use2_13) - lazy val eventsourcedJdbcProjection: Agg[Dep] = Agg( - persistenceQuery, - projection.core, - projection.eventsourced, - projection.slick, - persistenceJdbc - ) ++ slick.default - } - } - } - - trait SlickLibs { - object slick { - val V = IWMaterials.Versions.slick - val org = "com.typesafe.slick" - - lazy val slick: Dep = ivy"$org::slick:$V" - lazy val hikaricp: Dep = ivy"$org::slick-hikaricp:$V" - lazy val default: Agg[Dep] = Agg(slick, hikaricp) - } - } - -} diff --git a/iw/build.sc b/iw/build.sc deleted file mode 100644 index fbbc3b8..0000000 --- a/iw/build.sc +++ /dev/null @@ -1,144 +0,0 @@ -import mill._, scalalib._, scalajslib._ - -import $file.bom - -object support { - val Deps = bom.IWMaterials.Deps - val Versions = bom.IWMaterials.Versions - - trait CommonModule extends ScalaModule { - def scalaVersion = "3.1.1" - def scalacOptions = Seq( - "-encoding", - "utf8", - "-deprecation", - "-explain-types", - "-explain", - "-feature", - "-language:experimental.macros", - "-language:higherKinds", - "-language:implicitConversions", - "-unchecked", - "-Xfatal-warnings", - "-Ykind-projector" - ) - } - - trait CommonJSModule extends CommonModule with ScalaJSModule { - def scalaJSVersion = "1.8.0" - } - - trait CrossPlatformModule extends Module { outer => - def ivyDeps: T[Agg[Dep]] = Agg[Dep]() - def jsDeps: T[Agg[Dep]] = Agg[Dep]() - def jvmDeps: T[Agg[Dep]] = Agg[Dep]() - def moduleDeps: Seq[Module] = Seq[Module]() - - trait PlatformModule extends CommonModule { - def platform: String - override def millSourcePath = outer.millSourcePath - override def ivyDeps = outer.ivyDeps() ++ (platform match { - case "js" => jsDeps() - case _ => jvmDeps() - }) - override def moduleDeps = outer.moduleDeps.collect { - case m: CrossPlatformModule => - platform match { - case "js" => m.js - case _ => m.jvm - } - case m: JavaModule => m - } - } - - trait JsModule extends PlatformModule with CommonJSModule { - def platform = "js" - } - - trait JvmModule extends PlatformModule { - def platform = "jvm" - } - - val js: JsModule - val jvm: JvmModule - } - - trait PureCrossModule extends CrossPlatformModule { - override object js extends JsModule - override object jvm extends JvmModule - } - - trait PureCrossSbtModule extends CrossPlatformModule { - override object js extends JsModule with SbtModule - override object jvm extends JvmModule with SbtModule - } - - trait FullCrossSbtModule extends CrossPlatformModule { - trait FullSources extends JavaModule { self: PlatformModule => - override def sources = T.sources( - millSourcePath / platform / "src" / "main" / "scala", - millSourcePath / "shared" / "src" / "main" / "scala" - ) - - override def resources = T.sources( - millSourcePath / platform / "src" / "main" / "resources", - millSourcePath / "shared" / "src" / "main" / "resources" - ) - } - - override object js extends JsModule with FullSources - override object jvm extends JvmModule with FullSources - } - -} - -import support._ - -object mongo extends CommonModule { - def ivyDeps = Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - ivy"org.mongodb.scala::mongo-scala-driver:4.2.3".withDottyCompat( - scalaVersion() - ) - ) -} - -object tapir extends FullCrossSbtModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson, Deps.zioJson) - def jvmDeps = Agg(Deps.tapirZIO, Deps.tapirZIOHttp4sServer) - def jsDeps = Agg(Deps.tapirSttpClient) -} - -object akkaPersistence extends CommonModule { - def millSourcePath = build.millSourcePath / "akka-persistence" - def ivyDeps = (Agg( - Deps.akka.persistenceQuery, - Deps.akka.persistenceJdbc, - Deps.akka.projection.core, - Deps.akka.projection.eventsourced, - Deps.akka.projection.slick - ) ++ Deps.slick.default).map(_.withDottyCompat(scalaVersion())) ++ Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - Deps.akka.persistence.withDottyCompat(scalaVersion()), - ivy"com.typesafe.akka::akka-cluster-sharding-typed:${Deps.akka.V}" - .withDottyCompat(scalaVersion()) - ) -} - -object ui extends CommonJSModule { - def ivyDeps = Agg( - Deps.zio, - Deps.laminar, - Deps.zioJson, - Deps.waypoint, - Deps.urlDsl, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) -} diff --git a/iw/domain.sc b/iw/domain.sc deleted file mode 100644 index b09c56b..0000000 --- a/iw/domain.sc +++ /dev/null @@ -1,78 +0,0 @@ -import mill._, scalalib._ - -import $file.{build => ff}, ff.support._ - -trait DomainModule extends Module { - // General extra model deps - def modelModules: Seq[Module] = Seq.empty - // General extra codecs deps - def codecsModules: Seq[Module] = Seq.empty - // General extra endpoints deps - def endpointsModules: Seq[Module] = Seq.empty - - // Implementation deps for repo - def repoModules: Seq[JavaModule] = Seq.empty - - object shared extends Module { - object model extends PureCrossModule { - def moduleDeps = modelModules - def ivyDeps = Agg(Deps.zioPrelude) - } - object codecs extends PureCrossModule { - def moduleDeps = Seq(model) ++ codecsModules - def ivyDeps = Agg(Deps.zioJson) - } - } - - trait CommonProjects extends Module { - object model extends PureCrossModule { - def ivyDeps = Agg(Deps.zioPrelude) - def moduleDeps = Seq(shared.model) - } - object codecs extends PureCrossModule { - def moduleDeps = - Seq(model, shared.model, shared.codecs, ff.tapir) - } - object endpoints extends PureCrossModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson) - def moduleDeps = Seq(model, codecs) ++ endpointsModules - } - object client extends CommonJSModule { - def moduleDeps = Seq(endpoints.js) - } - object components extends CommonJSModule { - def ivyDeps = Agg( - Deps.laminar, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) - def moduleDeps = Seq(model.js, codecs.js, client, ff.ui) - } - } - - object query extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(repo, query.endpoints.jvm) - } - object repo extends CommonModule { - def ivyDeps = Agg(Deps.zio) - def moduleDeps = Seq(model.jvm, codecs.jvm) ++ repoModules - } - object projection extends CommonModule { - def moduleDeps = Seq(repo, ff.akkaPersistence) - } - } - - object command extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(entity, command.endpoints.jvm) - } - object entity extends CommonModule { - def moduleDeps = Seq(model.jvm, codecs.jvm, ff.akkaPersistence) - } - } -} diff --git a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala b/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala deleted file mode 100644 index 0fa3493..0000000 --- a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala +++ /dev/null @@ -1,53 +0,0 @@ -package works.iterative.mongo - -import zio.* -import zio.json.* -import zio.config.* -import org.mongodb.scala.* -import org.mongodb.scala.model.Filters.* -import org.bson.json.JsonObject -import org.mongodb.scala.model.ReplaceOptions -import org.mongodb.scala.bson.conversions.Bson - -case class MongoConfig(uri: String) - -object MongoConfig: - val configDesc = - import ConfigDescriptor.* - nested("MONGO")(string("URI").default("mongodb://localhost:27017")) - .to[MongoConfig] - val fromEnv = ZConfig.fromSystemEnv(configDesc) - -extension (m: MongoClient.type) - def layer: RLayer[MongoConfig, MongoClient] = - ZIO - .serviceWithZIO[MongoConfig](c => Task.attempt(MongoClient(c.uri))) - .toLayer - -class MongoJsonRepository[Elem, Key, Criteria]( - collection: MongoCollection[JsonObject], - toFilter: Criteria => Bson, - idFilter: Elem => (String, Key) -)(using JsonCodec[Elem]) { - def matching(criteria: Criteria): Task[Seq[Elem]] = - val filter = toFilter(criteria) - val query = collection.find(filter) - - for - result <- ZIO.fromFuture(_ => query.toFuture) - proof <- ZIO.collect(result)(j => - ZIO.fromOption(j.getJson.fromJson[Elem].toOption) - ) - yield proof - - def put(elem: Elem): Task[Unit] = - Task.async(cb => - collection - .replaceOne( - equal.tupled(idFilter(elem)), - JsonObject(elem.toJson), - ReplaceOptions().upsert(true) - ) - .subscribe(_ => cb(Task.unit), t => cb(Task.fail(t))) - ) -} diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala deleted file mode 100644 index 68d7196..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala +++ /dev/null @@ -1,12 +0,0 @@ -package works.iterative.tapir - -import sttp.model.Uri - -opaque type BaseUri = Option[Uri] - -object BaseUri: - - def apply(optU: Option[Uri]): BaseUri = optU - def apply(u: Uri): BaseUri = Some(u) - - extension (v: BaseUri) def toUri: Option[Uri] = v diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 653bc51..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,22 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.client.sttp.SttpClientInterpreter -import sttp.tapir.PublicEndpoint -import sttp.tapir.client.sttp.WebSocketToPipe -import scala.concurrent.Future -import sttp.client3.SttpBackend -import sttp.capabilities.WebSockets - -trait CustomTapirPlatformSpecific extends SttpClientInterpreter: - self: CustomTapir => - - type Backend = SttpBackend[Future, WebSockets] - - def makeClient[I, E, O]( - endpoint: PublicEndpoint[I, E, O, Any] - )(using - baseUri: BaseUri, - backend: Backend, - wsToPipe: WebSocketToPipe[Any] - ): I => Future[O] = - toClientThrowErrors(endpoint, baseUri.toUri, backend) diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 2545fbd..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,5 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.ztapir.ZTapir - -trait CustomTapirPlatformSpecific extends ZTapir diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala deleted file mode 100644 index 55e4ca1..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala +++ /dev/null @@ -1,7 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.server.http4s.ztapir.ZHttp4sServerInterpreter - -trait Http4sCustomTapir[Env] - extends CustomTapir - with ZHttp4sServerInterpreter[Env] diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala deleted file mode 100644 index dd52e9b..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala +++ /dev/null @@ -1,14 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.Tapir -import sttp.tapir.json.zio.TapirJsonZio -import sttp.tapir.TapirAliases - -trait CustomTapir - extends Tapir - with TapirJsonZio - with TapirAliases - with CustomTapirPlatformSpecific: - given Schema[ServerError] = Schema.derived - -object CustomTapir extends CustomTapir diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala deleted file mode 100644 index 715591d..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.tapir - -import zio.json.* - -sealed trait ServerError -case class InternalServerError(msg: String) extends ServerError -object InternalServerError: - def fromThrowable(t: Throwable): ServerError = InternalServerError( - t.getMessage - ) - -object ServerError: - given JsonCodec[ServerError] = DeriveJsonCodec.gen diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/File.scala deleted file mode 100644 index 09f6c07..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala +++ /dev/null @@ -1,3 +0,0 @@ -package works.iterative.services.files - -case class File(url: String, name: String) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala deleted file mode 100644 index 46633a5..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala +++ /dev/null @@ -1,25 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.Renderable - -given Renderable[File] with - extension (m: File) - def toHtml: HtmlElement = - li( - cls("pl-3 pr-4 py-3 flex items-center justify-between text-sm"), - div( - cls("w-0 flex-1 flex items-center"), - Icons.solid - .paperclip() - .amend(svg.cls := "flex-shrink-0 text-gray-400"), - span(cls("ml-2 flex-1 w-0 truncate"), m.name) - ), - a( - href(m.url), - cls("font-medium text-indigo-600 hover:text-indigo-500"), - "Otevřít" - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala deleted file mode 100644 index ac59db8..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import io.laminext.syntax.core.{*, given} - -def FileList(files: List[File]): HtmlElement = - ul( - role("list"), - cls("border border-gray-200 rounded-md divide-y divide-gray-200"), - files.map(_.toHtml) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala deleted file mode 100644 index 2e8f690..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala +++ /dev/null @@ -1,81 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import io.laminext.syntax.core.{*, given} - -object FilePicker: - sealed trait Event - sealed trait DoneEvent extends Event - case class SelectionUpdated(files: Set[File]) extends DoneEvent - case object SelectionCancelled extends DoneEvent - case object AvailableFilesRequested extends Event - - def apply( - currentFiles: Signal[List[File]], - availableFilesStream: EventStream[List[File]] - )(selectionUpdates: Observer[Event]): HtmlElement = - val (updatesStream, updatesObserver) = EventStream.withObserver[Event] - val selectorOpen = Var[Boolean](false) - - // This sequence tricks browser into displaying modal content centered - // Inspired by modal in headless ui playground - // https://github.com/tailwindlabs/headlessui/blob/fdd26297953080d5ec905dda0bf5ec9607897d86/packages/playground-react/pages/transitions/component-examples/modal.tsx#L78-L79 - inline def browserCenteringModalTrick: Modifier[HtmlElement] = - Seq[Modifier[HtmlElement]]( - span(cls("hidden sm:inline-block sm:h-screen sm:align-middle")), - "​" // Zero width space - ) - - inline def overlay: Modifier[HtmlElement] = - // Page overlay - /* TODO: transition - enter="ease-out duration-300" - enterFrom="opacity-0" - enterTo="opacity-100" - leave="ease-in duration-200" - leaveFrom="opacity-100" - leaveTo="opacity-0" - */ - div( - div( - onClick.mapTo(false) --> selectorOpen.writer, - cls("fixed inset-0 transition-opacity"), - div(cls("absolute inset-0 bg-gray-500 opacity-75")) - ) - ) - - inline def modalSelector: HtmlElement = - div( - cls("fixed inset-0 z-10 overflow-y-auto"), - div( - cls("text-center sm:block sm:p-0"), - overlay, - browserCenteringModalTrick, - child <-- currentFiles.map( - FileSelector(_, availableFilesStream)(updatesObserver) - ) - ) - ) - - div( - updatesStream --> selectionUpdates, - updatesStream.collect { case _: DoneEvent => - false - } --> selectorOpen.writer, - child.maybe <-- selectorOpen.signal.map(isOpen => - if isOpen then Some(modalSelector) else None - ), - div( - cls("flex flex-col space-y-5"), - child.maybe <-- currentFiles.map(files => - if files.isEmpty then None else Some(FileList(files)) - ), - button( - tpe := "button", - cls := "bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500", - "Zvolit soubory", - onClick.mapTo(true) --> selectorOpen.writer - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala deleted file mode 100644 index b43e07a..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala +++ /dev/null @@ -1,74 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.Loading -import io.laminext.syntax.core.{*, given} -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -object FileSelector: - import FilePicker._ - - def apply( - initialFiles: List[File], - availableFilesStream: EventStream[List[File]] - )(selectionUpdates: Observer[Event]): HtmlElement = - val selectedFiles = Var[Set[File]](initialFiles.to(Set)) - val availableFiles = availableFilesStream.startWithNone - // Request the files to display - selectionUpdates.onNext(AvailableFilesRequested) - div( - cls( - "inline-block transform overflow-hidden rounded-lg bg-white text-left align-bottom shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:align-middle" - ), - role("dialog"), - customHtmlAttr("aria.modal", BooleanAsTrueFalseStringCodec)(true), - aria.labelledBy("modal-headline"), - div( - cls("bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"), - div( - cls("sm:flex sm:items-start"), - div( - cls("mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"), - h3( - cls("text-lg font-medium leading-6 text-gray-900"), - idAttr("modal-headline"), - "Výběr souborů" - ) - ) - ), - child <-- availableFiles - .split(_ => ())((_, _, af) => FileTable(af, selectedFiles)) - .map(_.getOrElse(Loading)) - ), - div( - cls("bg-gray-50 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6"), - span( - cls("flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto"), - button( - typ("button"), - cls( - "focus:shadow-outline-green inline-flex w-full justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-base font-medium leading-6 text-white shadow-sm transition duration-150 ease-in-out hover:bg-indigo-500 focus:border-indigo-700 focus:outline-none sm:text-sm sm:leading-5" - ), - "Potvrdit", - composeEvents(onClick)( - _.sample(selectedFiles) - .map(SelectionUpdated(_)) - ) --> selectionUpdates - ) - ), - span( - cls("mt-3 flex w-full rounded-md shadow-sm sm:mt-0 sm:w-auto"), - button( - typ("button"), - cls( - "focus:shadow-outline-blue inline-flex w-full justify-center rounded-md border border-gray-300 bg-white px-4 py-2 text-base font-medium leading-6 text-gray-700 shadow-sm transition duration-150 ease-in-out hover:text-gray-500 focus:border-blue-300 focus:outline-none sm:text-sm sm:leading-5" - ), - "Zrušit", - onClick.mapTo(SelectionCancelled) --> selectionUpdates - ) - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala deleted file mode 100644 index aa11b2b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala +++ /dev/null @@ -1,91 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import works.iterative.ui.components.tailwind.Icons - -def FileTable( - files: Signal[List[File]], - selectedFiles: Var[Set[File]] -): HtmlElement = - val scope = customHtmlAttr("scope", StringAsIsCodec) - - def headerRow: HtmlElement = - val col = scope("col") - val baseM: Modifier[HtmlElement] = Seq(cls("px-6 py-3"), col) - val textH = cls( - "text-left text-xs font-medium text-gray-500 uppercase tracking-wider" - ) - tr( - th(baseM, span(cls("sr-only"), "Vybrat")), - th(baseM, textH, "Název"), - th(baseM, cls("relative"), span(cls("sr-only"), "Otevřít")) - ) - - def tableRow( - f: File, - idx: Int, - selected: Boolean - )(toggleSelection: Observer[Unit]): HtmlElement = - val baseC = cls("px-6 py-4 whitespace-nowrap text-sm") - tr( - cls(if idx % 2 == 0 then "bg-gray-50" else "bg-white"), - td( - cls("font-medium cursor-pointer"), - onClick.mapTo(()) --> toggleSelection, - cls(if selected then "text-green-900" else "text-gray-200"), - Icons.outline.`check-circle`().amend(svg.cls := "mx-auto"), - span(cls("sr-only"), if selected then "Vybráno" else "Nevybráno") - ), - td( - baseC, - cls("font-medium text-gray-900"), - f.name, - onClick.mapTo(()) --> toggleSelection - ), - td( - baseC, - cls("text-right font-medium"), - a( - href(f.url), - target("_blank"), - cls( - "flex items-center space-x-4 text-indigo-600 hover:text-indigo-900" - ), - Icons.outline.`external-link`(), - "Otevřít" - ) - ) - ) - - div( - cls("flex flex-col"), - div(cls("overflow-x-auto sm:-mx-6 lg:-mx-8")), - div( - cls("py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8"), - div( - cls("shadow overflow-hidden border-b border-gray-200 sm:rounded-lg"), - table( - cls("min-w-full divide-y divide-gray-200"), - thead( - cls("bg-gray-50"), - headerRow - ), - tbody( - children <-- files - .map(_.zipWithIndex) - .combineWithFn(selectedFiles)((f, sel) => - f.map((file, idx) => - val active = sel.contains(file) - tableRow(file, idx, active)( - selectedFiles.writer - .contramap(_ => if active then sel - file else sel + file) - ) - ) - ) - ) - ) - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala deleted file mode 100644 index 0fc8796..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala +++ /dev/null @@ -1,32 +0,0 @@ -package works.iterative.ui.components.tailwind - -import CustomAttrs.ariaHidden -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -// TODO: render icon or picture based on img signal -class Avatar($avatarImg: Signal[Option[String]]): - inline def avatarPlaceholder(size: Int): HtmlElement = - div( - cls := s"rounded-full text-indigo-200 bg-indigo-500 h-${size} w-${size} flex items-center justify-center", - Icons.outline.user(size - 2) - ) - - inline def avatarImage(size: Int): Signal[HtmlElement] = - $avatarImg.split(_ => ())((_, _, $url) => - img( - cls := s"w-$size h-$size rounded-full", - src <-- $url, - alt := "" - ) - ).map(_.getOrElse(avatarPlaceholder(size))) - - inline def avatar(size: Int): HtmlElement = - div( - cls := "relative", - child <-- avatarImage(size), - span( - cls := "absolute inset-0 shadow-inner rounded-full", - ariaHidden := true - ) - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala deleted file mode 100644 index e7e6627..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.akka - -// Base class for all command-processing related exceptions from handlers -sealed abstract class CommandHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandNotAvailable[C, S](cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd není dostupný ve stavu $state" - ) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandRejected[C, S](reason: String, cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd byl ve stavu $state odmítnut s odůvodněním $reason" - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala deleted file mode 100644 index 72a5bf9..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.akka - -sealed abstract class EventHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -case class UnhandledEvent[Event, State](event: Event, state: State) - extends EventHandlerException( - s"Událost $event nastala ve stavu $state bez možnosti zpracování" - ) diff --git a/iw/bom.sc b/iw/bom.sc deleted file mode 100644 index e9266b8..0000000 --- a/iw/bom.sc +++ /dev/null @@ -1,227 +0,0 @@ -import mill._, scalalib._ - -object IWMaterials { - - object Versions { - val akka = "2.6.16" - val akkaHttp = "10.2.4" - val cats = "2.7.0" - val elastic4s = "7.12.2" - val http4s = "0.23.10" - val http4sPac4J = "4.0.0" - val laminar = "0.14.2" - val laminext = laminar - val logbackClassic = "1.2.10" - val pac4j = "5.2.0" - val play = "2.8.8" - val playJson = "2.9.2" - val scalaTest = "3.2.9" - val slick = "3.3.3" - val sttpClient = "3.5.0" - val tapir = "0.20.1" - val urlDsl = "0.4.0" - val waypoint = "0.5.0" - val zio = "2.0.0-RC2" - val zioConfig = "3.0.0-RC2" - val zioInteropCats = "3.3.0-RC2" - val zioJson = "0.3.0-RC3" - val zioLogging = "2.0.0-RC5" - val zioPrelude = "1.0.0-RC10" - val zioZMX = "0.0.11" - } - - object Deps extends AkkaLibs with SlickLibs { - import IWMaterials.{Versions => V} - - val zioOrg = "dev.zio" - - def zioLib(name: String, version: String): Dep = - ivy"$zioOrg::zio-$name::$version" - - lazy val zio: Dep = ivy"$zioOrg::zio:${V.zio}" - - lazy val zioTest: Dep = zioLib("test", V.zio) - lazy val zioTestSbt: Dep = zioLib("test", V.zio) - - lazy val zioConfig: Dep = zioLib("config", V.zioConfig) - lazy val zioConfigTypesafe: Dep = - zioLib("config-typesafe", V.zioConfig) - lazy val zioConfigMagnolia: Dep = - zioLib("config-magnolia", V.zioConfig) - - lazy val zioJson: Dep = zioLib("json", V.zioJson) - lazy val zioLogging: Dep = zioLib("logging", V.zioLogging) - lazy val zioLoggingSlf4j: Dep = - zioLib("logging-slf4j", V.zioLogging) - lazy val zioPrelude: Dep = zioLib("prelude", V.zioPrelude) - lazy val zioStreams: Dep = zioLib("streams", V.zio) - lazy val zioZMX: Dep = zioLib("zmx", V.zioZMX) - lazy val zioInteropCats: Dep = - zioLib("interop-cats", V.zioInteropCats) - - /* What is the equivalent? ZIOModule with prepared test config? - def useZIO(testConf: Configuration*): Agg[Dep] = Agg( - zio, - zioTest, - zioTestSbt, - testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework") - ) - */ - - /* - def useZIOAll(testConf: Configuration*): Seq[Def.Setting[_]] = - useZIO(testConf: _*) ++ Seq( - zioStreams, - zioConfig, - zioConfigTypesafe, - zioConfigMagnolia, - zioJson, - zioZMX, - zioLogging, - zioPrelude - ) - */ - - private val tapirOrg = "com.softwaremill.sttp.tapir" - def tapirLib(name: String): Dep = - ivy"${tapirOrg}::tapir-$name::${V.tapir}" - - lazy val tapirCore: Dep = tapirLib("core") - lazy val tapirZIO: Dep = tapirLib("zio") - lazy val tapirZIOJson: Dep = tapirLib("json-zio") - lazy val tapirSttpClient: Dep = tapirLib("sttp-client") - lazy val tapirCats: Dep = tapirLib("cats") - lazy val tapirZIOHttp4sServer: Dep = tapirLib("zio-http4s-server") - - private val sttpClientOrg = "com.softwaremill.sttp.client3" - def sttpClientLib(name: String): Dep = - ivy"${sttpClientOrg}::${name}:${V.sttpClient}" - - lazy val sttpClientCore: Dep = sttpClientLib("core") - - lazy val http4sBlazeServer: Dep = - ivy"org.http4s::http4s-blaze-server:${V.http4s}" - - lazy val http4sPac4J: Dep = - ivy"org.pac4j::http4s-pac4j:${V.http4sPac4J}" - lazy val pac4jOIDC: Dep = - ivy"org.pac4j:pac4j-oidc:${V.pac4j}" - - lazy val scalaTest: Dep = - ivy"org.scalatest::scalatest:${V.scalaTest}" - lazy val scalaTestPlusScalacheck: Dep = - ivy"org.scalatestplus::scalacheck-1-15:3.2.9.0" - lazy val playScalaTest: Dep = - ivy"org.scalatestplus.play::scalatestplus-play:5.1.0" - - private val playOrg = "com.typesafe.play" - lazy val playMailer: Dep = ivy"${playOrg}::play-mailer:8.0.1" - lazy val playServer: Dep = ivy"${playOrg}::play-server:${V.play}" - lazy val playAkkaServer: Dep = - ivy"${playOrg}::play-akka-http-server:${V.play}" - lazy val play: Dep = ivy"${playOrg}::play:${V.play}" - lazy val playAhcWs: Dep = ivy"${playOrg}::play-ahc-ws:${V.play}" - lazy val playJson: Dep = ivy"${playOrg}::play-json:${V.playJson}" - - private val elastic4sOrg = "com.sksamuel.elastic4s" - lazy val useElastic4S: Agg[Dep] = Agg( - ivy"${elastic4sOrg}::elastic4s-core:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-client-akka:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-http-streams:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-json-play:${V.elastic4s}" - ) - - lazy val laminar: Dep = ivy"com.raquo::laminar::${V.laminar}" - - private def laminext(name: String): Dep = - ivy"io.laminext::$name::${V.laminar}" - - lazy val laminextCore: Dep = laminext("core") - lazy val laminextTailwind: Dep = laminext("tailwind") - lazy val laminextFetch: Dep = laminext("fetch") - lazy val laminextValidationCore: Dep = laminext("validation-core") - lazy val laminextUI: Dep = laminext("ui") - - lazy val waypoint: Dep = - ivy"com.raquo::waypoint::${V.waypoint}" - - lazy val urlDsl: Dep = - ivy"be.doeraene::url-dsl::${V.urlDsl}" - - lazy val scalaJavaTime: Dep = - ivy"io.github.cquiroz::scala-java-time::2.3.0" - - lazy val scalaJavaLocales: Dep = - ivy"io.github.cquiroz::scala-java-locales::1.2.1" - - lazy val logbackClassic: Dep = - ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" - } - - trait AkkaLibs { - - self: SlickLibs => - - object akka { - val V = Versions.akka - val tOrg = "com.typesafe.akka" - val lOrg = "com.lightbend.akka" - - def akkaMod(name: String): Dep = - ivy"$tOrg::akka-$name:$V" - - lazy val actor: Dep = akkaMod("actor") - lazy val actorTyped: Dep = akkaMod("actor-typed") - lazy val stream: Dep = akkaMod("stream") - lazy val persistence: Dep = akkaMod("persistence-typed") - lazy val persistenceQuery: Dep = akkaMod("persistence-query") - lazy val persistenceJdbc: Dep = - ivy"$lOrg::akka-persistence-jdbc:5.0.4" - val persistenceTestKit: Dep = ivy"$tOrg::akka-persistence-testkit:$V" - - object http { - val V = Versions.akkaHttp - - lazy val http: Dep = ivy"$tOrg::akka-http:$V" - lazy val sprayJson: Dep = ivy"$tOrg::akka-http-spray-json:$V" - - } - - object projection { - val V = "1.2.2" - - lazy val core: Dep = - ivy"$lOrg::akka-projection-core:$V" - lazy val eventsourced: Dep = - ivy"$lOrg::akka-projection-eventsourced:$V" - lazy val slick: Dep = - ivy"$lOrg::akka-projection-slick:$V" - lazy val jdbc: Dep = - ivy"$lOrg::akka-projection-jdbc:$V" - } - - object profiles { - // TODO: deal with cross-version for Scala3 (for3Use2_13) - lazy val eventsourcedJdbcProjection: Agg[Dep] = Agg( - persistenceQuery, - projection.core, - projection.eventsourced, - projection.slick, - persistenceJdbc - ) ++ slick.default - } - } - } - - trait SlickLibs { - object slick { - val V = IWMaterials.Versions.slick - val org = "com.typesafe.slick" - - lazy val slick: Dep = ivy"$org::slick:$V" - lazy val hikaricp: Dep = ivy"$org::slick-hikaricp:$V" - lazy val default: Agg[Dep] = Agg(slick, hikaricp) - } - } - -} diff --git a/iw/build.sc b/iw/build.sc deleted file mode 100644 index fbbc3b8..0000000 --- a/iw/build.sc +++ /dev/null @@ -1,144 +0,0 @@ -import mill._, scalalib._, scalajslib._ - -import $file.bom - -object support { - val Deps = bom.IWMaterials.Deps - val Versions = bom.IWMaterials.Versions - - trait CommonModule extends ScalaModule { - def scalaVersion = "3.1.1" - def scalacOptions = Seq( - "-encoding", - "utf8", - "-deprecation", - "-explain-types", - "-explain", - "-feature", - "-language:experimental.macros", - "-language:higherKinds", - "-language:implicitConversions", - "-unchecked", - "-Xfatal-warnings", - "-Ykind-projector" - ) - } - - trait CommonJSModule extends CommonModule with ScalaJSModule { - def scalaJSVersion = "1.8.0" - } - - trait CrossPlatformModule extends Module { outer => - def ivyDeps: T[Agg[Dep]] = Agg[Dep]() - def jsDeps: T[Agg[Dep]] = Agg[Dep]() - def jvmDeps: T[Agg[Dep]] = Agg[Dep]() - def moduleDeps: Seq[Module] = Seq[Module]() - - trait PlatformModule extends CommonModule { - def platform: String - override def millSourcePath = outer.millSourcePath - override def ivyDeps = outer.ivyDeps() ++ (platform match { - case "js" => jsDeps() - case _ => jvmDeps() - }) - override def moduleDeps = outer.moduleDeps.collect { - case m: CrossPlatformModule => - platform match { - case "js" => m.js - case _ => m.jvm - } - case m: JavaModule => m - } - } - - trait JsModule extends PlatformModule with CommonJSModule { - def platform = "js" - } - - trait JvmModule extends PlatformModule { - def platform = "jvm" - } - - val js: JsModule - val jvm: JvmModule - } - - trait PureCrossModule extends CrossPlatformModule { - override object js extends JsModule - override object jvm extends JvmModule - } - - trait PureCrossSbtModule extends CrossPlatformModule { - override object js extends JsModule with SbtModule - override object jvm extends JvmModule with SbtModule - } - - trait FullCrossSbtModule extends CrossPlatformModule { - trait FullSources extends JavaModule { self: PlatformModule => - override def sources = T.sources( - millSourcePath / platform / "src" / "main" / "scala", - millSourcePath / "shared" / "src" / "main" / "scala" - ) - - override def resources = T.sources( - millSourcePath / platform / "src" / "main" / "resources", - millSourcePath / "shared" / "src" / "main" / "resources" - ) - } - - override object js extends JsModule with FullSources - override object jvm extends JvmModule with FullSources - } - -} - -import support._ - -object mongo extends CommonModule { - def ivyDeps = Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - ivy"org.mongodb.scala::mongo-scala-driver:4.2.3".withDottyCompat( - scalaVersion() - ) - ) -} - -object tapir extends FullCrossSbtModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson, Deps.zioJson) - def jvmDeps = Agg(Deps.tapirZIO, Deps.tapirZIOHttp4sServer) - def jsDeps = Agg(Deps.tapirSttpClient) -} - -object akkaPersistence extends CommonModule { - def millSourcePath = build.millSourcePath / "akka-persistence" - def ivyDeps = (Agg( - Deps.akka.persistenceQuery, - Deps.akka.persistenceJdbc, - Deps.akka.projection.core, - Deps.akka.projection.eventsourced, - Deps.akka.projection.slick - ) ++ Deps.slick.default).map(_.withDottyCompat(scalaVersion())) ++ Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - Deps.akka.persistence.withDottyCompat(scalaVersion()), - ivy"com.typesafe.akka::akka-cluster-sharding-typed:${Deps.akka.V}" - .withDottyCompat(scalaVersion()) - ) -} - -object ui extends CommonJSModule { - def ivyDeps = Agg( - Deps.zio, - Deps.laminar, - Deps.zioJson, - Deps.waypoint, - Deps.urlDsl, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) -} diff --git a/iw/domain.sc b/iw/domain.sc deleted file mode 100644 index b09c56b..0000000 --- a/iw/domain.sc +++ /dev/null @@ -1,78 +0,0 @@ -import mill._, scalalib._ - -import $file.{build => ff}, ff.support._ - -trait DomainModule extends Module { - // General extra model deps - def modelModules: Seq[Module] = Seq.empty - // General extra codecs deps - def codecsModules: Seq[Module] = Seq.empty - // General extra endpoints deps - def endpointsModules: Seq[Module] = Seq.empty - - // Implementation deps for repo - def repoModules: Seq[JavaModule] = Seq.empty - - object shared extends Module { - object model extends PureCrossModule { - def moduleDeps = modelModules - def ivyDeps = Agg(Deps.zioPrelude) - } - object codecs extends PureCrossModule { - def moduleDeps = Seq(model) ++ codecsModules - def ivyDeps = Agg(Deps.zioJson) - } - } - - trait CommonProjects extends Module { - object model extends PureCrossModule { - def ivyDeps = Agg(Deps.zioPrelude) - def moduleDeps = Seq(shared.model) - } - object codecs extends PureCrossModule { - def moduleDeps = - Seq(model, shared.model, shared.codecs, ff.tapir) - } - object endpoints extends PureCrossModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson) - def moduleDeps = Seq(model, codecs) ++ endpointsModules - } - object client extends CommonJSModule { - def moduleDeps = Seq(endpoints.js) - } - object components extends CommonJSModule { - def ivyDeps = Agg( - Deps.laminar, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) - def moduleDeps = Seq(model.js, codecs.js, client, ff.ui) - } - } - - object query extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(repo, query.endpoints.jvm) - } - object repo extends CommonModule { - def ivyDeps = Agg(Deps.zio) - def moduleDeps = Seq(model.jvm, codecs.jvm) ++ repoModules - } - object projection extends CommonModule { - def moduleDeps = Seq(repo, ff.akkaPersistence) - } - } - - object command extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(entity, command.endpoints.jvm) - } - object entity extends CommonModule { - def moduleDeps = Seq(model.jvm, codecs.jvm, ff.akkaPersistence) - } - } -} diff --git a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala b/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala deleted file mode 100644 index 0fa3493..0000000 --- a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala +++ /dev/null @@ -1,53 +0,0 @@ -package works.iterative.mongo - -import zio.* -import zio.json.* -import zio.config.* -import org.mongodb.scala.* -import org.mongodb.scala.model.Filters.* -import org.bson.json.JsonObject -import org.mongodb.scala.model.ReplaceOptions -import org.mongodb.scala.bson.conversions.Bson - -case class MongoConfig(uri: String) - -object MongoConfig: - val configDesc = - import ConfigDescriptor.* - nested("MONGO")(string("URI").default("mongodb://localhost:27017")) - .to[MongoConfig] - val fromEnv = ZConfig.fromSystemEnv(configDesc) - -extension (m: MongoClient.type) - def layer: RLayer[MongoConfig, MongoClient] = - ZIO - .serviceWithZIO[MongoConfig](c => Task.attempt(MongoClient(c.uri))) - .toLayer - -class MongoJsonRepository[Elem, Key, Criteria]( - collection: MongoCollection[JsonObject], - toFilter: Criteria => Bson, - idFilter: Elem => (String, Key) -)(using JsonCodec[Elem]) { - def matching(criteria: Criteria): Task[Seq[Elem]] = - val filter = toFilter(criteria) - val query = collection.find(filter) - - for - result <- ZIO.fromFuture(_ => query.toFuture) - proof <- ZIO.collect(result)(j => - ZIO.fromOption(j.getJson.fromJson[Elem].toOption) - ) - yield proof - - def put(elem: Elem): Task[Unit] = - Task.async(cb => - collection - .replaceOne( - equal.tupled(idFilter(elem)), - JsonObject(elem.toJson), - ReplaceOptions().upsert(true) - ) - .subscribe(_ => cb(Task.unit), t => cb(Task.fail(t))) - ) -} diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala deleted file mode 100644 index 68d7196..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala +++ /dev/null @@ -1,12 +0,0 @@ -package works.iterative.tapir - -import sttp.model.Uri - -opaque type BaseUri = Option[Uri] - -object BaseUri: - - def apply(optU: Option[Uri]): BaseUri = optU - def apply(u: Uri): BaseUri = Some(u) - - extension (v: BaseUri) def toUri: Option[Uri] = v diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 653bc51..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,22 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.client.sttp.SttpClientInterpreter -import sttp.tapir.PublicEndpoint -import sttp.tapir.client.sttp.WebSocketToPipe -import scala.concurrent.Future -import sttp.client3.SttpBackend -import sttp.capabilities.WebSockets - -trait CustomTapirPlatformSpecific extends SttpClientInterpreter: - self: CustomTapir => - - type Backend = SttpBackend[Future, WebSockets] - - def makeClient[I, E, O]( - endpoint: PublicEndpoint[I, E, O, Any] - )(using - baseUri: BaseUri, - backend: Backend, - wsToPipe: WebSocketToPipe[Any] - ): I => Future[O] = - toClientThrowErrors(endpoint, baseUri.toUri, backend) diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 2545fbd..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,5 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.ztapir.ZTapir - -trait CustomTapirPlatformSpecific extends ZTapir diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala deleted file mode 100644 index 55e4ca1..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala +++ /dev/null @@ -1,7 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.server.http4s.ztapir.ZHttp4sServerInterpreter - -trait Http4sCustomTapir[Env] - extends CustomTapir - with ZHttp4sServerInterpreter[Env] diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala deleted file mode 100644 index dd52e9b..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala +++ /dev/null @@ -1,14 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.Tapir -import sttp.tapir.json.zio.TapirJsonZio -import sttp.tapir.TapirAliases - -trait CustomTapir - extends Tapir - with TapirJsonZio - with TapirAliases - with CustomTapirPlatformSpecific: - given Schema[ServerError] = Schema.derived - -object CustomTapir extends CustomTapir diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala deleted file mode 100644 index 715591d..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.tapir - -import zio.json.* - -sealed trait ServerError -case class InternalServerError(msg: String) extends ServerError -object InternalServerError: - def fromThrowable(t: Throwable): ServerError = InternalServerError( - t.getMessage - ) - -object ServerError: - given JsonCodec[ServerError] = DeriveJsonCodec.gen diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/File.scala deleted file mode 100644 index 09f6c07..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala +++ /dev/null @@ -1,3 +0,0 @@ -package works.iterative.services.files - -case class File(url: String, name: String) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala deleted file mode 100644 index 46633a5..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala +++ /dev/null @@ -1,25 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.Renderable - -given Renderable[File] with - extension (m: File) - def toHtml: HtmlElement = - li( - cls("pl-3 pr-4 py-3 flex items-center justify-between text-sm"), - div( - cls("w-0 flex-1 flex items-center"), - Icons.solid - .paperclip() - .amend(svg.cls := "flex-shrink-0 text-gray-400"), - span(cls("ml-2 flex-1 w-0 truncate"), m.name) - ), - a( - href(m.url), - cls("font-medium text-indigo-600 hover:text-indigo-500"), - "Otevřít" - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala deleted file mode 100644 index ac59db8..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import io.laminext.syntax.core.{*, given} - -def FileList(files: List[File]): HtmlElement = - ul( - role("list"), - cls("border border-gray-200 rounded-md divide-y divide-gray-200"), - files.map(_.toHtml) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala deleted file mode 100644 index 2e8f690..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala +++ /dev/null @@ -1,81 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import io.laminext.syntax.core.{*, given} - -object FilePicker: - sealed trait Event - sealed trait DoneEvent extends Event - case class SelectionUpdated(files: Set[File]) extends DoneEvent - case object SelectionCancelled extends DoneEvent - case object AvailableFilesRequested extends Event - - def apply( - currentFiles: Signal[List[File]], - availableFilesStream: EventStream[List[File]] - )(selectionUpdates: Observer[Event]): HtmlElement = - val (updatesStream, updatesObserver) = EventStream.withObserver[Event] - val selectorOpen = Var[Boolean](false) - - // This sequence tricks browser into displaying modal content centered - // Inspired by modal in headless ui playground - // https://github.com/tailwindlabs/headlessui/blob/fdd26297953080d5ec905dda0bf5ec9607897d86/packages/playground-react/pages/transitions/component-examples/modal.tsx#L78-L79 - inline def browserCenteringModalTrick: Modifier[HtmlElement] = - Seq[Modifier[HtmlElement]]( - span(cls("hidden sm:inline-block sm:h-screen sm:align-middle")), - "​" // Zero width space - ) - - inline def overlay: Modifier[HtmlElement] = - // Page overlay - /* TODO: transition - enter="ease-out duration-300" - enterFrom="opacity-0" - enterTo="opacity-100" - leave="ease-in duration-200" - leaveFrom="opacity-100" - leaveTo="opacity-0" - */ - div( - div( - onClick.mapTo(false) --> selectorOpen.writer, - cls("fixed inset-0 transition-opacity"), - div(cls("absolute inset-0 bg-gray-500 opacity-75")) - ) - ) - - inline def modalSelector: HtmlElement = - div( - cls("fixed inset-0 z-10 overflow-y-auto"), - div( - cls("text-center sm:block sm:p-0"), - overlay, - browserCenteringModalTrick, - child <-- currentFiles.map( - FileSelector(_, availableFilesStream)(updatesObserver) - ) - ) - ) - - div( - updatesStream --> selectionUpdates, - updatesStream.collect { case _: DoneEvent => - false - } --> selectorOpen.writer, - child.maybe <-- selectorOpen.signal.map(isOpen => - if isOpen then Some(modalSelector) else None - ), - div( - cls("flex flex-col space-y-5"), - child.maybe <-- currentFiles.map(files => - if files.isEmpty then None else Some(FileList(files)) - ), - button( - tpe := "button", - cls := "bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500", - "Zvolit soubory", - onClick.mapTo(true) --> selectorOpen.writer - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala deleted file mode 100644 index b43e07a..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala +++ /dev/null @@ -1,74 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.Loading -import io.laminext.syntax.core.{*, given} -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -object FileSelector: - import FilePicker._ - - def apply( - initialFiles: List[File], - availableFilesStream: EventStream[List[File]] - )(selectionUpdates: Observer[Event]): HtmlElement = - val selectedFiles = Var[Set[File]](initialFiles.to(Set)) - val availableFiles = availableFilesStream.startWithNone - // Request the files to display - selectionUpdates.onNext(AvailableFilesRequested) - div( - cls( - "inline-block transform overflow-hidden rounded-lg bg-white text-left align-bottom shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:align-middle" - ), - role("dialog"), - customHtmlAttr("aria.modal", BooleanAsTrueFalseStringCodec)(true), - aria.labelledBy("modal-headline"), - div( - cls("bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"), - div( - cls("sm:flex sm:items-start"), - div( - cls("mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"), - h3( - cls("text-lg font-medium leading-6 text-gray-900"), - idAttr("modal-headline"), - "Výběr souborů" - ) - ) - ), - child <-- availableFiles - .split(_ => ())((_, _, af) => FileTable(af, selectedFiles)) - .map(_.getOrElse(Loading)) - ), - div( - cls("bg-gray-50 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6"), - span( - cls("flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto"), - button( - typ("button"), - cls( - "focus:shadow-outline-green inline-flex w-full justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-base font-medium leading-6 text-white shadow-sm transition duration-150 ease-in-out hover:bg-indigo-500 focus:border-indigo-700 focus:outline-none sm:text-sm sm:leading-5" - ), - "Potvrdit", - composeEvents(onClick)( - _.sample(selectedFiles) - .map(SelectionUpdated(_)) - ) --> selectionUpdates - ) - ), - span( - cls("mt-3 flex w-full rounded-md shadow-sm sm:mt-0 sm:w-auto"), - button( - typ("button"), - cls( - "focus:shadow-outline-blue inline-flex w-full justify-center rounded-md border border-gray-300 bg-white px-4 py-2 text-base font-medium leading-6 text-gray-700 shadow-sm transition duration-150 ease-in-out hover:text-gray-500 focus:border-blue-300 focus:outline-none sm:text-sm sm:leading-5" - ), - "Zrušit", - onClick.mapTo(SelectionCancelled) --> selectionUpdates - ) - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala deleted file mode 100644 index aa11b2b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala +++ /dev/null @@ -1,91 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import works.iterative.ui.components.tailwind.Icons - -def FileTable( - files: Signal[List[File]], - selectedFiles: Var[Set[File]] -): HtmlElement = - val scope = customHtmlAttr("scope", StringAsIsCodec) - - def headerRow: HtmlElement = - val col = scope("col") - val baseM: Modifier[HtmlElement] = Seq(cls("px-6 py-3"), col) - val textH = cls( - "text-left text-xs font-medium text-gray-500 uppercase tracking-wider" - ) - tr( - th(baseM, span(cls("sr-only"), "Vybrat")), - th(baseM, textH, "Název"), - th(baseM, cls("relative"), span(cls("sr-only"), "Otevřít")) - ) - - def tableRow( - f: File, - idx: Int, - selected: Boolean - )(toggleSelection: Observer[Unit]): HtmlElement = - val baseC = cls("px-6 py-4 whitespace-nowrap text-sm") - tr( - cls(if idx % 2 == 0 then "bg-gray-50" else "bg-white"), - td( - cls("font-medium cursor-pointer"), - onClick.mapTo(()) --> toggleSelection, - cls(if selected then "text-green-900" else "text-gray-200"), - Icons.outline.`check-circle`().amend(svg.cls := "mx-auto"), - span(cls("sr-only"), if selected then "Vybráno" else "Nevybráno") - ), - td( - baseC, - cls("font-medium text-gray-900"), - f.name, - onClick.mapTo(()) --> toggleSelection - ), - td( - baseC, - cls("text-right font-medium"), - a( - href(f.url), - target("_blank"), - cls( - "flex items-center space-x-4 text-indigo-600 hover:text-indigo-900" - ), - Icons.outline.`external-link`(), - "Otevřít" - ) - ) - ) - - div( - cls("flex flex-col"), - div(cls("overflow-x-auto sm:-mx-6 lg:-mx-8")), - div( - cls("py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8"), - div( - cls("shadow overflow-hidden border-b border-gray-200 sm:rounded-lg"), - table( - cls("min-w-full divide-y divide-gray-200"), - thead( - cls("bg-gray-50"), - headerRow - ), - tbody( - children <-- files - .map(_.zipWithIndex) - .combineWithFn(selectedFiles)((f, sel) => - f.map((file, idx) => - val active = sel.contains(file) - tableRow(file, idx, active)( - selectedFiles.writer - .contramap(_ => if active then sel - file else sel + file) - ) - ) - ) - ) - ) - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala deleted file mode 100644 index 0fc8796..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala +++ /dev/null @@ -1,32 +0,0 @@ -package works.iterative.ui.components.tailwind - -import CustomAttrs.ariaHidden -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -// TODO: render icon or picture based on img signal -class Avatar($avatarImg: Signal[Option[String]]): - inline def avatarPlaceholder(size: Int): HtmlElement = - div( - cls := s"rounded-full text-indigo-200 bg-indigo-500 h-${size} w-${size} flex items-center justify-center", - Icons.outline.user(size - 2) - ) - - inline def avatarImage(size: Int): Signal[HtmlElement] = - $avatarImg.split(_ => ())((_, _, $url) => - img( - cls := s"w-$size h-$size rounded-full", - src <-- $url, - alt := "" - ) - ).map(_.getOrElse(avatarPlaceholder(size))) - - inline def avatar(size: Int): HtmlElement = - div( - cls := "relative", - child <-- avatarImage(size), - span( - cls := "absolute inset-0 shadow-inner rounded-full", - ariaHidden := true - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala deleted file mode 100644 index bcbed88..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala +++ /dev/null @@ -1,77 +0,0 @@ -package works.iterative.ui.components.tailwind - -enum ColorWeight(value: String): - inline def toCSS: String = value - // To be used for black and white until - // we support better mechanism via extension methods - case w__ extends ColorWeight("") - case w50 extends ColorWeight("50") - case w100 extends ColorWeight("100") - case w200 extends ColorWeight("200") - case w300 extends ColorWeight("300") - case w400 extends ColorWeight("400") - case w500 extends ColorWeight("500") - case w600 extends ColorWeight("600") - case w700 extends ColorWeight("700") - case w800 extends ColorWeight("800") - case w900 extends ColorWeight("900") - -enum Color(name: String): - import ColorWeight._ - - inline def toCSSNoColorWeight(prefix: String): String = - s"${prefix}-${name}" - inline def toCSSWithColorWeight( - prefix: String, - weight: ColorWeight - ): String = - s"${prefix}-${name}-${weight.toCSS}" - inline def toCSS(prefix: String)(weight: ColorWeight): String = - weight match { - case `w__` => toCSSNoColorWeight(prefix) - case _ => toCSSWithColorWeight(prefix, weight) - } - inline def bg: ColorWeight => String = toCSS("bg")(_) - inline def text: ColorWeight => String = toCSS("text")(_) - inline def decoration: ColorWeight => String = toCSS("decoration")(_) - inline def border: ColorWeight => String = toCSS("border")(_) - inline def outline: ColorWeight => String = toCSS("outline")(_) - inline def divide: ColorWeight => String = toCSS("divide")(_) - inline def ring: ColorWeight => String = toCSS("ring")(_) - inline def ringOffset: ColorWeight => String = toCSS("ring-offset")(_) - inline def shadow: ColorWeight => String = toCSS("shadow")(_) - inline def accent: ColorWeight => String = toCSS("accent")(_) - - // TODO: change the "stupid" methods to extension methods - // that will keep the invariants in comments lower - case current extends Color("current") - case inherit extends Color("inherit") - // Not present in for all methods - case transp extends Color("transparent") - // Seen in accent, not preset otherwise - case auto extends Color("auto") - // Black and white do not have weight - case black extends Color("black") - case white extends Color("white") - case slate extends Color("slate") - case gray extends Color("gray") - case zinc extends Color("zinc") - case neutral extends Color("neutral") - case stone extends Color("stone") - case red extends Color("red") - case orange extends Color("orange") - case amber extends Color("amber") - case yellow extends Color("yellow") - case lime extends Color("lime") - case green extends Color("green") - case emerald extends Color("emerald") - case teal extends Color("teal") - case cyan extends Color("cyan") - case sky extends Color("sky") - case blue extends Color("blue") - case indigo extends Color("indigo") - case violet extends Color("violet") - case purple extends Color("purple") - case fuchsia extends Color("fuchsia") - case pink extends Color("pink") - case rose extends Color("rose") diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala deleted file mode 100644 index e7e6627..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.akka - -// Base class for all command-processing related exceptions from handlers -sealed abstract class CommandHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandNotAvailable[C, S](cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd není dostupný ve stavu $state" - ) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandRejected[C, S](reason: String, cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd byl ve stavu $state odmítnut s odůvodněním $reason" - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala deleted file mode 100644 index 72a5bf9..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.akka - -sealed abstract class EventHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -case class UnhandledEvent[Event, State](event: Event, state: State) - extends EventHandlerException( - s"Událost $event nastala ve stavu $state bez možnosti zpracování" - ) diff --git a/iw/bom.sc b/iw/bom.sc deleted file mode 100644 index e9266b8..0000000 --- a/iw/bom.sc +++ /dev/null @@ -1,227 +0,0 @@ -import mill._, scalalib._ - -object IWMaterials { - - object Versions { - val akka = "2.6.16" - val akkaHttp = "10.2.4" - val cats = "2.7.0" - val elastic4s = "7.12.2" - val http4s = "0.23.10" - val http4sPac4J = "4.0.0" - val laminar = "0.14.2" - val laminext = laminar - val logbackClassic = "1.2.10" - val pac4j = "5.2.0" - val play = "2.8.8" - val playJson = "2.9.2" - val scalaTest = "3.2.9" - val slick = "3.3.3" - val sttpClient = "3.5.0" - val tapir = "0.20.1" - val urlDsl = "0.4.0" - val waypoint = "0.5.0" - val zio = "2.0.0-RC2" - val zioConfig = "3.0.0-RC2" - val zioInteropCats = "3.3.0-RC2" - val zioJson = "0.3.0-RC3" - val zioLogging = "2.0.0-RC5" - val zioPrelude = "1.0.0-RC10" - val zioZMX = "0.0.11" - } - - object Deps extends AkkaLibs with SlickLibs { - import IWMaterials.{Versions => V} - - val zioOrg = "dev.zio" - - def zioLib(name: String, version: String): Dep = - ivy"$zioOrg::zio-$name::$version" - - lazy val zio: Dep = ivy"$zioOrg::zio:${V.zio}" - - lazy val zioTest: Dep = zioLib("test", V.zio) - lazy val zioTestSbt: Dep = zioLib("test", V.zio) - - lazy val zioConfig: Dep = zioLib("config", V.zioConfig) - lazy val zioConfigTypesafe: Dep = - zioLib("config-typesafe", V.zioConfig) - lazy val zioConfigMagnolia: Dep = - zioLib("config-magnolia", V.zioConfig) - - lazy val zioJson: Dep = zioLib("json", V.zioJson) - lazy val zioLogging: Dep = zioLib("logging", V.zioLogging) - lazy val zioLoggingSlf4j: Dep = - zioLib("logging-slf4j", V.zioLogging) - lazy val zioPrelude: Dep = zioLib("prelude", V.zioPrelude) - lazy val zioStreams: Dep = zioLib("streams", V.zio) - lazy val zioZMX: Dep = zioLib("zmx", V.zioZMX) - lazy val zioInteropCats: Dep = - zioLib("interop-cats", V.zioInteropCats) - - /* What is the equivalent? ZIOModule with prepared test config? - def useZIO(testConf: Configuration*): Agg[Dep] = Agg( - zio, - zioTest, - zioTestSbt, - testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework") - ) - */ - - /* - def useZIOAll(testConf: Configuration*): Seq[Def.Setting[_]] = - useZIO(testConf: _*) ++ Seq( - zioStreams, - zioConfig, - zioConfigTypesafe, - zioConfigMagnolia, - zioJson, - zioZMX, - zioLogging, - zioPrelude - ) - */ - - private val tapirOrg = "com.softwaremill.sttp.tapir" - def tapirLib(name: String): Dep = - ivy"${tapirOrg}::tapir-$name::${V.tapir}" - - lazy val tapirCore: Dep = tapirLib("core") - lazy val tapirZIO: Dep = tapirLib("zio") - lazy val tapirZIOJson: Dep = tapirLib("json-zio") - lazy val tapirSttpClient: Dep = tapirLib("sttp-client") - lazy val tapirCats: Dep = tapirLib("cats") - lazy val tapirZIOHttp4sServer: Dep = tapirLib("zio-http4s-server") - - private val sttpClientOrg = "com.softwaremill.sttp.client3" - def sttpClientLib(name: String): Dep = - ivy"${sttpClientOrg}::${name}:${V.sttpClient}" - - lazy val sttpClientCore: Dep = sttpClientLib("core") - - lazy val http4sBlazeServer: Dep = - ivy"org.http4s::http4s-blaze-server:${V.http4s}" - - lazy val http4sPac4J: Dep = - ivy"org.pac4j::http4s-pac4j:${V.http4sPac4J}" - lazy val pac4jOIDC: Dep = - ivy"org.pac4j:pac4j-oidc:${V.pac4j}" - - lazy val scalaTest: Dep = - ivy"org.scalatest::scalatest:${V.scalaTest}" - lazy val scalaTestPlusScalacheck: Dep = - ivy"org.scalatestplus::scalacheck-1-15:3.2.9.0" - lazy val playScalaTest: Dep = - ivy"org.scalatestplus.play::scalatestplus-play:5.1.0" - - private val playOrg = "com.typesafe.play" - lazy val playMailer: Dep = ivy"${playOrg}::play-mailer:8.0.1" - lazy val playServer: Dep = ivy"${playOrg}::play-server:${V.play}" - lazy val playAkkaServer: Dep = - ivy"${playOrg}::play-akka-http-server:${V.play}" - lazy val play: Dep = ivy"${playOrg}::play:${V.play}" - lazy val playAhcWs: Dep = ivy"${playOrg}::play-ahc-ws:${V.play}" - lazy val playJson: Dep = ivy"${playOrg}::play-json:${V.playJson}" - - private val elastic4sOrg = "com.sksamuel.elastic4s" - lazy val useElastic4S: Agg[Dep] = Agg( - ivy"${elastic4sOrg}::elastic4s-core:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-client-akka:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-http-streams:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-json-play:${V.elastic4s}" - ) - - lazy val laminar: Dep = ivy"com.raquo::laminar::${V.laminar}" - - private def laminext(name: String): Dep = - ivy"io.laminext::$name::${V.laminar}" - - lazy val laminextCore: Dep = laminext("core") - lazy val laminextTailwind: Dep = laminext("tailwind") - lazy val laminextFetch: Dep = laminext("fetch") - lazy val laminextValidationCore: Dep = laminext("validation-core") - lazy val laminextUI: Dep = laminext("ui") - - lazy val waypoint: Dep = - ivy"com.raquo::waypoint::${V.waypoint}" - - lazy val urlDsl: Dep = - ivy"be.doeraene::url-dsl::${V.urlDsl}" - - lazy val scalaJavaTime: Dep = - ivy"io.github.cquiroz::scala-java-time::2.3.0" - - lazy val scalaJavaLocales: Dep = - ivy"io.github.cquiroz::scala-java-locales::1.2.1" - - lazy val logbackClassic: Dep = - ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" - } - - trait AkkaLibs { - - self: SlickLibs => - - object akka { - val V = Versions.akka - val tOrg = "com.typesafe.akka" - val lOrg = "com.lightbend.akka" - - def akkaMod(name: String): Dep = - ivy"$tOrg::akka-$name:$V" - - lazy val actor: Dep = akkaMod("actor") - lazy val actorTyped: Dep = akkaMod("actor-typed") - lazy val stream: Dep = akkaMod("stream") - lazy val persistence: Dep = akkaMod("persistence-typed") - lazy val persistenceQuery: Dep = akkaMod("persistence-query") - lazy val persistenceJdbc: Dep = - ivy"$lOrg::akka-persistence-jdbc:5.0.4" - val persistenceTestKit: Dep = ivy"$tOrg::akka-persistence-testkit:$V" - - object http { - val V = Versions.akkaHttp - - lazy val http: Dep = ivy"$tOrg::akka-http:$V" - lazy val sprayJson: Dep = ivy"$tOrg::akka-http-spray-json:$V" - - } - - object projection { - val V = "1.2.2" - - lazy val core: Dep = - ivy"$lOrg::akka-projection-core:$V" - lazy val eventsourced: Dep = - ivy"$lOrg::akka-projection-eventsourced:$V" - lazy val slick: Dep = - ivy"$lOrg::akka-projection-slick:$V" - lazy val jdbc: Dep = - ivy"$lOrg::akka-projection-jdbc:$V" - } - - object profiles { - // TODO: deal with cross-version for Scala3 (for3Use2_13) - lazy val eventsourcedJdbcProjection: Agg[Dep] = Agg( - persistenceQuery, - projection.core, - projection.eventsourced, - projection.slick, - persistenceJdbc - ) ++ slick.default - } - } - } - - trait SlickLibs { - object slick { - val V = IWMaterials.Versions.slick - val org = "com.typesafe.slick" - - lazy val slick: Dep = ivy"$org::slick:$V" - lazy val hikaricp: Dep = ivy"$org::slick-hikaricp:$V" - lazy val default: Agg[Dep] = Agg(slick, hikaricp) - } - } - -} diff --git a/iw/build.sc b/iw/build.sc deleted file mode 100644 index fbbc3b8..0000000 --- a/iw/build.sc +++ /dev/null @@ -1,144 +0,0 @@ -import mill._, scalalib._, scalajslib._ - -import $file.bom - -object support { - val Deps = bom.IWMaterials.Deps - val Versions = bom.IWMaterials.Versions - - trait CommonModule extends ScalaModule { - def scalaVersion = "3.1.1" - def scalacOptions = Seq( - "-encoding", - "utf8", - "-deprecation", - "-explain-types", - "-explain", - "-feature", - "-language:experimental.macros", - "-language:higherKinds", - "-language:implicitConversions", - "-unchecked", - "-Xfatal-warnings", - "-Ykind-projector" - ) - } - - trait CommonJSModule extends CommonModule with ScalaJSModule { - def scalaJSVersion = "1.8.0" - } - - trait CrossPlatformModule extends Module { outer => - def ivyDeps: T[Agg[Dep]] = Agg[Dep]() - def jsDeps: T[Agg[Dep]] = Agg[Dep]() - def jvmDeps: T[Agg[Dep]] = Agg[Dep]() - def moduleDeps: Seq[Module] = Seq[Module]() - - trait PlatformModule extends CommonModule { - def platform: String - override def millSourcePath = outer.millSourcePath - override def ivyDeps = outer.ivyDeps() ++ (platform match { - case "js" => jsDeps() - case _ => jvmDeps() - }) - override def moduleDeps = outer.moduleDeps.collect { - case m: CrossPlatformModule => - platform match { - case "js" => m.js - case _ => m.jvm - } - case m: JavaModule => m - } - } - - trait JsModule extends PlatformModule with CommonJSModule { - def platform = "js" - } - - trait JvmModule extends PlatformModule { - def platform = "jvm" - } - - val js: JsModule - val jvm: JvmModule - } - - trait PureCrossModule extends CrossPlatformModule { - override object js extends JsModule - override object jvm extends JvmModule - } - - trait PureCrossSbtModule extends CrossPlatformModule { - override object js extends JsModule with SbtModule - override object jvm extends JvmModule with SbtModule - } - - trait FullCrossSbtModule extends CrossPlatformModule { - trait FullSources extends JavaModule { self: PlatformModule => - override def sources = T.sources( - millSourcePath / platform / "src" / "main" / "scala", - millSourcePath / "shared" / "src" / "main" / "scala" - ) - - override def resources = T.sources( - millSourcePath / platform / "src" / "main" / "resources", - millSourcePath / "shared" / "src" / "main" / "resources" - ) - } - - override object js extends JsModule with FullSources - override object jvm extends JvmModule with FullSources - } - -} - -import support._ - -object mongo extends CommonModule { - def ivyDeps = Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - ivy"org.mongodb.scala::mongo-scala-driver:4.2.3".withDottyCompat( - scalaVersion() - ) - ) -} - -object tapir extends FullCrossSbtModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson, Deps.zioJson) - def jvmDeps = Agg(Deps.tapirZIO, Deps.tapirZIOHttp4sServer) - def jsDeps = Agg(Deps.tapirSttpClient) -} - -object akkaPersistence extends CommonModule { - def millSourcePath = build.millSourcePath / "akka-persistence" - def ivyDeps = (Agg( - Deps.akka.persistenceQuery, - Deps.akka.persistenceJdbc, - Deps.akka.projection.core, - Deps.akka.projection.eventsourced, - Deps.akka.projection.slick - ) ++ Deps.slick.default).map(_.withDottyCompat(scalaVersion())) ++ Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - Deps.akka.persistence.withDottyCompat(scalaVersion()), - ivy"com.typesafe.akka::akka-cluster-sharding-typed:${Deps.akka.V}" - .withDottyCompat(scalaVersion()) - ) -} - -object ui extends CommonJSModule { - def ivyDeps = Agg( - Deps.zio, - Deps.laminar, - Deps.zioJson, - Deps.waypoint, - Deps.urlDsl, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) -} diff --git a/iw/domain.sc b/iw/domain.sc deleted file mode 100644 index b09c56b..0000000 --- a/iw/domain.sc +++ /dev/null @@ -1,78 +0,0 @@ -import mill._, scalalib._ - -import $file.{build => ff}, ff.support._ - -trait DomainModule extends Module { - // General extra model deps - def modelModules: Seq[Module] = Seq.empty - // General extra codecs deps - def codecsModules: Seq[Module] = Seq.empty - // General extra endpoints deps - def endpointsModules: Seq[Module] = Seq.empty - - // Implementation deps for repo - def repoModules: Seq[JavaModule] = Seq.empty - - object shared extends Module { - object model extends PureCrossModule { - def moduleDeps = modelModules - def ivyDeps = Agg(Deps.zioPrelude) - } - object codecs extends PureCrossModule { - def moduleDeps = Seq(model) ++ codecsModules - def ivyDeps = Agg(Deps.zioJson) - } - } - - trait CommonProjects extends Module { - object model extends PureCrossModule { - def ivyDeps = Agg(Deps.zioPrelude) - def moduleDeps = Seq(shared.model) - } - object codecs extends PureCrossModule { - def moduleDeps = - Seq(model, shared.model, shared.codecs, ff.tapir) - } - object endpoints extends PureCrossModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson) - def moduleDeps = Seq(model, codecs) ++ endpointsModules - } - object client extends CommonJSModule { - def moduleDeps = Seq(endpoints.js) - } - object components extends CommonJSModule { - def ivyDeps = Agg( - Deps.laminar, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) - def moduleDeps = Seq(model.js, codecs.js, client, ff.ui) - } - } - - object query extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(repo, query.endpoints.jvm) - } - object repo extends CommonModule { - def ivyDeps = Agg(Deps.zio) - def moduleDeps = Seq(model.jvm, codecs.jvm) ++ repoModules - } - object projection extends CommonModule { - def moduleDeps = Seq(repo, ff.akkaPersistence) - } - } - - object command extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(entity, command.endpoints.jvm) - } - object entity extends CommonModule { - def moduleDeps = Seq(model.jvm, codecs.jvm, ff.akkaPersistence) - } - } -} diff --git a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala b/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala deleted file mode 100644 index 0fa3493..0000000 --- a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala +++ /dev/null @@ -1,53 +0,0 @@ -package works.iterative.mongo - -import zio.* -import zio.json.* -import zio.config.* -import org.mongodb.scala.* -import org.mongodb.scala.model.Filters.* -import org.bson.json.JsonObject -import org.mongodb.scala.model.ReplaceOptions -import org.mongodb.scala.bson.conversions.Bson - -case class MongoConfig(uri: String) - -object MongoConfig: - val configDesc = - import ConfigDescriptor.* - nested("MONGO")(string("URI").default("mongodb://localhost:27017")) - .to[MongoConfig] - val fromEnv = ZConfig.fromSystemEnv(configDesc) - -extension (m: MongoClient.type) - def layer: RLayer[MongoConfig, MongoClient] = - ZIO - .serviceWithZIO[MongoConfig](c => Task.attempt(MongoClient(c.uri))) - .toLayer - -class MongoJsonRepository[Elem, Key, Criteria]( - collection: MongoCollection[JsonObject], - toFilter: Criteria => Bson, - idFilter: Elem => (String, Key) -)(using JsonCodec[Elem]) { - def matching(criteria: Criteria): Task[Seq[Elem]] = - val filter = toFilter(criteria) - val query = collection.find(filter) - - for - result <- ZIO.fromFuture(_ => query.toFuture) - proof <- ZIO.collect(result)(j => - ZIO.fromOption(j.getJson.fromJson[Elem].toOption) - ) - yield proof - - def put(elem: Elem): Task[Unit] = - Task.async(cb => - collection - .replaceOne( - equal.tupled(idFilter(elem)), - JsonObject(elem.toJson), - ReplaceOptions().upsert(true) - ) - .subscribe(_ => cb(Task.unit), t => cb(Task.fail(t))) - ) -} diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala deleted file mode 100644 index 68d7196..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala +++ /dev/null @@ -1,12 +0,0 @@ -package works.iterative.tapir - -import sttp.model.Uri - -opaque type BaseUri = Option[Uri] - -object BaseUri: - - def apply(optU: Option[Uri]): BaseUri = optU - def apply(u: Uri): BaseUri = Some(u) - - extension (v: BaseUri) def toUri: Option[Uri] = v diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 653bc51..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,22 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.client.sttp.SttpClientInterpreter -import sttp.tapir.PublicEndpoint -import sttp.tapir.client.sttp.WebSocketToPipe -import scala.concurrent.Future -import sttp.client3.SttpBackend -import sttp.capabilities.WebSockets - -trait CustomTapirPlatformSpecific extends SttpClientInterpreter: - self: CustomTapir => - - type Backend = SttpBackend[Future, WebSockets] - - def makeClient[I, E, O]( - endpoint: PublicEndpoint[I, E, O, Any] - )(using - baseUri: BaseUri, - backend: Backend, - wsToPipe: WebSocketToPipe[Any] - ): I => Future[O] = - toClientThrowErrors(endpoint, baseUri.toUri, backend) diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 2545fbd..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,5 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.ztapir.ZTapir - -trait CustomTapirPlatformSpecific extends ZTapir diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala deleted file mode 100644 index 55e4ca1..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala +++ /dev/null @@ -1,7 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.server.http4s.ztapir.ZHttp4sServerInterpreter - -trait Http4sCustomTapir[Env] - extends CustomTapir - with ZHttp4sServerInterpreter[Env] diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala deleted file mode 100644 index dd52e9b..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala +++ /dev/null @@ -1,14 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.Tapir -import sttp.tapir.json.zio.TapirJsonZio -import sttp.tapir.TapirAliases - -trait CustomTapir - extends Tapir - with TapirJsonZio - with TapirAliases - with CustomTapirPlatformSpecific: - given Schema[ServerError] = Schema.derived - -object CustomTapir extends CustomTapir diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala deleted file mode 100644 index 715591d..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.tapir - -import zio.json.* - -sealed trait ServerError -case class InternalServerError(msg: String) extends ServerError -object InternalServerError: - def fromThrowable(t: Throwable): ServerError = InternalServerError( - t.getMessage - ) - -object ServerError: - given JsonCodec[ServerError] = DeriveJsonCodec.gen diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/File.scala deleted file mode 100644 index 09f6c07..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala +++ /dev/null @@ -1,3 +0,0 @@ -package works.iterative.services.files - -case class File(url: String, name: String) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala deleted file mode 100644 index 46633a5..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala +++ /dev/null @@ -1,25 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.Renderable - -given Renderable[File] with - extension (m: File) - def toHtml: HtmlElement = - li( - cls("pl-3 pr-4 py-3 flex items-center justify-between text-sm"), - div( - cls("w-0 flex-1 flex items-center"), - Icons.solid - .paperclip() - .amend(svg.cls := "flex-shrink-0 text-gray-400"), - span(cls("ml-2 flex-1 w-0 truncate"), m.name) - ), - a( - href(m.url), - cls("font-medium text-indigo-600 hover:text-indigo-500"), - "Otevřít" - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala deleted file mode 100644 index ac59db8..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import io.laminext.syntax.core.{*, given} - -def FileList(files: List[File]): HtmlElement = - ul( - role("list"), - cls("border border-gray-200 rounded-md divide-y divide-gray-200"), - files.map(_.toHtml) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala deleted file mode 100644 index 2e8f690..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala +++ /dev/null @@ -1,81 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import io.laminext.syntax.core.{*, given} - -object FilePicker: - sealed trait Event - sealed trait DoneEvent extends Event - case class SelectionUpdated(files: Set[File]) extends DoneEvent - case object SelectionCancelled extends DoneEvent - case object AvailableFilesRequested extends Event - - def apply( - currentFiles: Signal[List[File]], - availableFilesStream: EventStream[List[File]] - )(selectionUpdates: Observer[Event]): HtmlElement = - val (updatesStream, updatesObserver) = EventStream.withObserver[Event] - val selectorOpen = Var[Boolean](false) - - // This sequence tricks browser into displaying modal content centered - // Inspired by modal in headless ui playground - // https://github.com/tailwindlabs/headlessui/blob/fdd26297953080d5ec905dda0bf5ec9607897d86/packages/playground-react/pages/transitions/component-examples/modal.tsx#L78-L79 - inline def browserCenteringModalTrick: Modifier[HtmlElement] = - Seq[Modifier[HtmlElement]]( - span(cls("hidden sm:inline-block sm:h-screen sm:align-middle")), - "​" // Zero width space - ) - - inline def overlay: Modifier[HtmlElement] = - // Page overlay - /* TODO: transition - enter="ease-out duration-300" - enterFrom="opacity-0" - enterTo="opacity-100" - leave="ease-in duration-200" - leaveFrom="opacity-100" - leaveTo="opacity-0" - */ - div( - div( - onClick.mapTo(false) --> selectorOpen.writer, - cls("fixed inset-0 transition-opacity"), - div(cls("absolute inset-0 bg-gray-500 opacity-75")) - ) - ) - - inline def modalSelector: HtmlElement = - div( - cls("fixed inset-0 z-10 overflow-y-auto"), - div( - cls("text-center sm:block sm:p-0"), - overlay, - browserCenteringModalTrick, - child <-- currentFiles.map( - FileSelector(_, availableFilesStream)(updatesObserver) - ) - ) - ) - - div( - updatesStream --> selectionUpdates, - updatesStream.collect { case _: DoneEvent => - false - } --> selectorOpen.writer, - child.maybe <-- selectorOpen.signal.map(isOpen => - if isOpen then Some(modalSelector) else None - ), - div( - cls("flex flex-col space-y-5"), - child.maybe <-- currentFiles.map(files => - if files.isEmpty then None else Some(FileList(files)) - ), - button( - tpe := "button", - cls := "bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500", - "Zvolit soubory", - onClick.mapTo(true) --> selectorOpen.writer - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala deleted file mode 100644 index b43e07a..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala +++ /dev/null @@ -1,74 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.Loading -import io.laminext.syntax.core.{*, given} -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -object FileSelector: - import FilePicker._ - - def apply( - initialFiles: List[File], - availableFilesStream: EventStream[List[File]] - )(selectionUpdates: Observer[Event]): HtmlElement = - val selectedFiles = Var[Set[File]](initialFiles.to(Set)) - val availableFiles = availableFilesStream.startWithNone - // Request the files to display - selectionUpdates.onNext(AvailableFilesRequested) - div( - cls( - "inline-block transform overflow-hidden rounded-lg bg-white text-left align-bottom shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:align-middle" - ), - role("dialog"), - customHtmlAttr("aria.modal", BooleanAsTrueFalseStringCodec)(true), - aria.labelledBy("modal-headline"), - div( - cls("bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"), - div( - cls("sm:flex sm:items-start"), - div( - cls("mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"), - h3( - cls("text-lg font-medium leading-6 text-gray-900"), - idAttr("modal-headline"), - "Výběr souborů" - ) - ) - ), - child <-- availableFiles - .split(_ => ())((_, _, af) => FileTable(af, selectedFiles)) - .map(_.getOrElse(Loading)) - ), - div( - cls("bg-gray-50 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6"), - span( - cls("flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto"), - button( - typ("button"), - cls( - "focus:shadow-outline-green inline-flex w-full justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-base font-medium leading-6 text-white shadow-sm transition duration-150 ease-in-out hover:bg-indigo-500 focus:border-indigo-700 focus:outline-none sm:text-sm sm:leading-5" - ), - "Potvrdit", - composeEvents(onClick)( - _.sample(selectedFiles) - .map(SelectionUpdated(_)) - ) --> selectionUpdates - ) - ), - span( - cls("mt-3 flex w-full rounded-md shadow-sm sm:mt-0 sm:w-auto"), - button( - typ("button"), - cls( - "focus:shadow-outline-blue inline-flex w-full justify-center rounded-md border border-gray-300 bg-white px-4 py-2 text-base font-medium leading-6 text-gray-700 shadow-sm transition duration-150 ease-in-out hover:text-gray-500 focus:border-blue-300 focus:outline-none sm:text-sm sm:leading-5" - ), - "Zrušit", - onClick.mapTo(SelectionCancelled) --> selectionUpdates - ) - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala deleted file mode 100644 index aa11b2b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala +++ /dev/null @@ -1,91 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import works.iterative.ui.components.tailwind.Icons - -def FileTable( - files: Signal[List[File]], - selectedFiles: Var[Set[File]] -): HtmlElement = - val scope = customHtmlAttr("scope", StringAsIsCodec) - - def headerRow: HtmlElement = - val col = scope("col") - val baseM: Modifier[HtmlElement] = Seq(cls("px-6 py-3"), col) - val textH = cls( - "text-left text-xs font-medium text-gray-500 uppercase tracking-wider" - ) - tr( - th(baseM, span(cls("sr-only"), "Vybrat")), - th(baseM, textH, "Název"), - th(baseM, cls("relative"), span(cls("sr-only"), "Otevřít")) - ) - - def tableRow( - f: File, - idx: Int, - selected: Boolean - )(toggleSelection: Observer[Unit]): HtmlElement = - val baseC = cls("px-6 py-4 whitespace-nowrap text-sm") - tr( - cls(if idx % 2 == 0 then "bg-gray-50" else "bg-white"), - td( - cls("font-medium cursor-pointer"), - onClick.mapTo(()) --> toggleSelection, - cls(if selected then "text-green-900" else "text-gray-200"), - Icons.outline.`check-circle`().amend(svg.cls := "mx-auto"), - span(cls("sr-only"), if selected then "Vybráno" else "Nevybráno") - ), - td( - baseC, - cls("font-medium text-gray-900"), - f.name, - onClick.mapTo(()) --> toggleSelection - ), - td( - baseC, - cls("text-right font-medium"), - a( - href(f.url), - target("_blank"), - cls( - "flex items-center space-x-4 text-indigo-600 hover:text-indigo-900" - ), - Icons.outline.`external-link`(), - "Otevřít" - ) - ) - ) - - div( - cls("flex flex-col"), - div(cls("overflow-x-auto sm:-mx-6 lg:-mx-8")), - div( - cls("py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8"), - div( - cls("shadow overflow-hidden border-b border-gray-200 sm:rounded-lg"), - table( - cls("min-w-full divide-y divide-gray-200"), - thead( - cls("bg-gray-50"), - headerRow - ), - tbody( - children <-- files - .map(_.zipWithIndex) - .combineWithFn(selectedFiles)((f, sel) => - f.map((file, idx) => - val active = sel.contains(file) - tableRow(file, idx, active)( - selectedFiles.writer - .contramap(_ => if active then sel - file else sel + file) - ) - ) - ) - ) - ) - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala deleted file mode 100644 index 0fc8796..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala +++ /dev/null @@ -1,32 +0,0 @@ -package works.iterative.ui.components.tailwind - -import CustomAttrs.ariaHidden -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -// TODO: render icon or picture based on img signal -class Avatar($avatarImg: Signal[Option[String]]): - inline def avatarPlaceholder(size: Int): HtmlElement = - div( - cls := s"rounded-full text-indigo-200 bg-indigo-500 h-${size} w-${size} flex items-center justify-center", - Icons.outline.user(size - 2) - ) - - inline def avatarImage(size: Int): Signal[HtmlElement] = - $avatarImg.split(_ => ())((_, _, $url) => - img( - cls := s"w-$size h-$size rounded-full", - src <-- $url, - alt := "" - ) - ).map(_.getOrElse(avatarPlaceholder(size))) - - inline def avatar(size: Int): HtmlElement = - div( - cls := "relative", - child <-- avatarImage(size), - span( - cls := "absolute inset-0 shadow-inner rounded-full", - ariaHidden := true - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala deleted file mode 100644 index bcbed88..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala +++ /dev/null @@ -1,77 +0,0 @@ -package works.iterative.ui.components.tailwind - -enum ColorWeight(value: String): - inline def toCSS: String = value - // To be used for black and white until - // we support better mechanism via extension methods - case w__ extends ColorWeight("") - case w50 extends ColorWeight("50") - case w100 extends ColorWeight("100") - case w200 extends ColorWeight("200") - case w300 extends ColorWeight("300") - case w400 extends ColorWeight("400") - case w500 extends ColorWeight("500") - case w600 extends ColorWeight("600") - case w700 extends ColorWeight("700") - case w800 extends ColorWeight("800") - case w900 extends ColorWeight("900") - -enum Color(name: String): - import ColorWeight._ - - inline def toCSSNoColorWeight(prefix: String): String = - s"${prefix}-${name}" - inline def toCSSWithColorWeight( - prefix: String, - weight: ColorWeight - ): String = - s"${prefix}-${name}-${weight.toCSS}" - inline def toCSS(prefix: String)(weight: ColorWeight): String = - weight match { - case `w__` => toCSSNoColorWeight(prefix) - case _ => toCSSWithColorWeight(prefix, weight) - } - inline def bg: ColorWeight => String = toCSS("bg")(_) - inline def text: ColorWeight => String = toCSS("text")(_) - inline def decoration: ColorWeight => String = toCSS("decoration")(_) - inline def border: ColorWeight => String = toCSS("border")(_) - inline def outline: ColorWeight => String = toCSS("outline")(_) - inline def divide: ColorWeight => String = toCSS("divide")(_) - inline def ring: ColorWeight => String = toCSS("ring")(_) - inline def ringOffset: ColorWeight => String = toCSS("ring-offset")(_) - inline def shadow: ColorWeight => String = toCSS("shadow")(_) - inline def accent: ColorWeight => String = toCSS("accent")(_) - - // TODO: change the "stupid" methods to extension methods - // that will keep the invariants in comments lower - case current extends Color("current") - case inherit extends Color("inherit") - // Not present in for all methods - case transp extends Color("transparent") - // Seen in accent, not preset otherwise - case auto extends Color("auto") - // Black and white do not have weight - case black extends Color("black") - case white extends Color("white") - case slate extends Color("slate") - case gray extends Color("gray") - case zinc extends Color("zinc") - case neutral extends Color("neutral") - case stone extends Color("stone") - case red extends Color("red") - case orange extends Color("orange") - case amber extends Color("amber") - case yellow extends Color("yellow") - case lime extends Color("lime") - case green extends Color("green") - case emerald extends Color("emerald") - case teal extends Color("teal") - case cyan extends Color("cyan") - case sky extends Color("sky") - case blue extends Color("blue") - case indigo extends Color("indigo") - case violet extends Color("violet") - case purple extends Color("purple") - case fuchsia extends Color("fuchsia") - case pink extends Color("pink") - case rose extends Color("rose") diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala deleted file mode 100644 index 6fb453c..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -object CustomAttrs { - // Made a pull request to add aria-current to scala-dom-types, remove after - val ariaCurrent = customHtmlAttr("aria-current", StringAsIsCodec) - val ariaHidden = customHtmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - - val datetime = customHtmlAttr("datetime", StringAsIsCodec) - - object svg { - import com.raquo.laminar.api.L.svg.{*, given} - val ariaHidden = - customSvgAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - } -} diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala deleted file mode 100644 index e7e6627..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.akka - -// Base class for all command-processing related exceptions from handlers -sealed abstract class CommandHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandNotAvailable[C, S](cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd není dostupný ve stavu $state" - ) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandRejected[C, S](reason: String, cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd byl ve stavu $state odmítnut s odůvodněním $reason" - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala deleted file mode 100644 index 72a5bf9..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.akka - -sealed abstract class EventHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -case class UnhandledEvent[Event, State](event: Event, state: State) - extends EventHandlerException( - s"Událost $event nastala ve stavu $state bez možnosti zpracování" - ) diff --git a/iw/bom.sc b/iw/bom.sc deleted file mode 100644 index e9266b8..0000000 --- a/iw/bom.sc +++ /dev/null @@ -1,227 +0,0 @@ -import mill._, scalalib._ - -object IWMaterials { - - object Versions { - val akka = "2.6.16" - val akkaHttp = "10.2.4" - val cats = "2.7.0" - val elastic4s = "7.12.2" - val http4s = "0.23.10" - val http4sPac4J = "4.0.0" - val laminar = "0.14.2" - val laminext = laminar - val logbackClassic = "1.2.10" - val pac4j = "5.2.0" - val play = "2.8.8" - val playJson = "2.9.2" - val scalaTest = "3.2.9" - val slick = "3.3.3" - val sttpClient = "3.5.0" - val tapir = "0.20.1" - val urlDsl = "0.4.0" - val waypoint = "0.5.0" - val zio = "2.0.0-RC2" - val zioConfig = "3.0.0-RC2" - val zioInteropCats = "3.3.0-RC2" - val zioJson = "0.3.0-RC3" - val zioLogging = "2.0.0-RC5" - val zioPrelude = "1.0.0-RC10" - val zioZMX = "0.0.11" - } - - object Deps extends AkkaLibs with SlickLibs { - import IWMaterials.{Versions => V} - - val zioOrg = "dev.zio" - - def zioLib(name: String, version: String): Dep = - ivy"$zioOrg::zio-$name::$version" - - lazy val zio: Dep = ivy"$zioOrg::zio:${V.zio}" - - lazy val zioTest: Dep = zioLib("test", V.zio) - lazy val zioTestSbt: Dep = zioLib("test", V.zio) - - lazy val zioConfig: Dep = zioLib("config", V.zioConfig) - lazy val zioConfigTypesafe: Dep = - zioLib("config-typesafe", V.zioConfig) - lazy val zioConfigMagnolia: Dep = - zioLib("config-magnolia", V.zioConfig) - - lazy val zioJson: Dep = zioLib("json", V.zioJson) - lazy val zioLogging: Dep = zioLib("logging", V.zioLogging) - lazy val zioLoggingSlf4j: Dep = - zioLib("logging-slf4j", V.zioLogging) - lazy val zioPrelude: Dep = zioLib("prelude", V.zioPrelude) - lazy val zioStreams: Dep = zioLib("streams", V.zio) - lazy val zioZMX: Dep = zioLib("zmx", V.zioZMX) - lazy val zioInteropCats: Dep = - zioLib("interop-cats", V.zioInteropCats) - - /* What is the equivalent? ZIOModule with prepared test config? - def useZIO(testConf: Configuration*): Agg[Dep] = Agg( - zio, - zioTest, - zioTestSbt, - testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework") - ) - */ - - /* - def useZIOAll(testConf: Configuration*): Seq[Def.Setting[_]] = - useZIO(testConf: _*) ++ Seq( - zioStreams, - zioConfig, - zioConfigTypesafe, - zioConfigMagnolia, - zioJson, - zioZMX, - zioLogging, - zioPrelude - ) - */ - - private val tapirOrg = "com.softwaremill.sttp.tapir" - def tapirLib(name: String): Dep = - ivy"${tapirOrg}::tapir-$name::${V.tapir}" - - lazy val tapirCore: Dep = tapirLib("core") - lazy val tapirZIO: Dep = tapirLib("zio") - lazy val tapirZIOJson: Dep = tapirLib("json-zio") - lazy val tapirSttpClient: Dep = tapirLib("sttp-client") - lazy val tapirCats: Dep = tapirLib("cats") - lazy val tapirZIOHttp4sServer: Dep = tapirLib("zio-http4s-server") - - private val sttpClientOrg = "com.softwaremill.sttp.client3" - def sttpClientLib(name: String): Dep = - ivy"${sttpClientOrg}::${name}:${V.sttpClient}" - - lazy val sttpClientCore: Dep = sttpClientLib("core") - - lazy val http4sBlazeServer: Dep = - ivy"org.http4s::http4s-blaze-server:${V.http4s}" - - lazy val http4sPac4J: Dep = - ivy"org.pac4j::http4s-pac4j:${V.http4sPac4J}" - lazy val pac4jOIDC: Dep = - ivy"org.pac4j:pac4j-oidc:${V.pac4j}" - - lazy val scalaTest: Dep = - ivy"org.scalatest::scalatest:${V.scalaTest}" - lazy val scalaTestPlusScalacheck: Dep = - ivy"org.scalatestplus::scalacheck-1-15:3.2.9.0" - lazy val playScalaTest: Dep = - ivy"org.scalatestplus.play::scalatestplus-play:5.1.0" - - private val playOrg = "com.typesafe.play" - lazy val playMailer: Dep = ivy"${playOrg}::play-mailer:8.0.1" - lazy val playServer: Dep = ivy"${playOrg}::play-server:${V.play}" - lazy val playAkkaServer: Dep = - ivy"${playOrg}::play-akka-http-server:${V.play}" - lazy val play: Dep = ivy"${playOrg}::play:${V.play}" - lazy val playAhcWs: Dep = ivy"${playOrg}::play-ahc-ws:${V.play}" - lazy val playJson: Dep = ivy"${playOrg}::play-json:${V.playJson}" - - private val elastic4sOrg = "com.sksamuel.elastic4s" - lazy val useElastic4S: Agg[Dep] = Agg( - ivy"${elastic4sOrg}::elastic4s-core:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-client-akka:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-http-streams:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-json-play:${V.elastic4s}" - ) - - lazy val laminar: Dep = ivy"com.raquo::laminar::${V.laminar}" - - private def laminext(name: String): Dep = - ivy"io.laminext::$name::${V.laminar}" - - lazy val laminextCore: Dep = laminext("core") - lazy val laminextTailwind: Dep = laminext("tailwind") - lazy val laminextFetch: Dep = laminext("fetch") - lazy val laminextValidationCore: Dep = laminext("validation-core") - lazy val laminextUI: Dep = laminext("ui") - - lazy val waypoint: Dep = - ivy"com.raquo::waypoint::${V.waypoint}" - - lazy val urlDsl: Dep = - ivy"be.doeraene::url-dsl::${V.urlDsl}" - - lazy val scalaJavaTime: Dep = - ivy"io.github.cquiroz::scala-java-time::2.3.0" - - lazy val scalaJavaLocales: Dep = - ivy"io.github.cquiroz::scala-java-locales::1.2.1" - - lazy val logbackClassic: Dep = - ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" - } - - trait AkkaLibs { - - self: SlickLibs => - - object akka { - val V = Versions.akka - val tOrg = "com.typesafe.akka" - val lOrg = "com.lightbend.akka" - - def akkaMod(name: String): Dep = - ivy"$tOrg::akka-$name:$V" - - lazy val actor: Dep = akkaMod("actor") - lazy val actorTyped: Dep = akkaMod("actor-typed") - lazy val stream: Dep = akkaMod("stream") - lazy val persistence: Dep = akkaMod("persistence-typed") - lazy val persistenceQuery: Dep = akkaMod("persistence-query") - lazy val persistenceJdbc: Dep = - ivy"$lOrg::akka-persistence-jdbc:5.0.4" - val persistenceTestKit: Dep = ivy"$tOrg::akka-persistence-testkit:$V" - - object http { - val V = Versions.akkaHttp - - lazy val http: Dep = ivy"$tOrg::akka-http:$V" - lazy val sprayJson: Dep = ivy"$tOrg::akka-http-spray-json:$V" - - } - - object projection { - val V = "1.2.2" - - lazy val core: Dep = - ivy"$lOrg::akka-projection-core:$V" - lazy val eventsourced: Dep = - ivy"$lOrg::akka-projection-eventsourced:$V" - lazy val slick: Dep = - ivy"$lOrg::akka-projection-slick:$V" - lazy val jdbc: Dep = - ivy"$lOrg::akka-projection-jdbc:$V" - } - - object profiles { - // TODO: deal with cross-version for Scala3 (for3Use2_13) - lazy val eventsourcedJdbcProjection: Agg[Dep] = Agg( - persistenceQuery, - projection.core, - projection.eventsourced, - projection.slick, - persistenceJdbc - ) ++ slick.default - } - } - } - - trait SlickLibs { - object slick { - val V = IWMaterials.Versions.slick - val org = "com.typesafe.slick" - - lazy val slick: Dep = ivy"$org::slick:$V" - lazy val hikaricp: Dep = ivy"$org::slick-hikaricp:$V" - lazy val default: Agg[Dep] = Agg(slick, hikaricp) - } - } - -} diff --git a/iw/build.sc b/iw/build.sc deleted file mode 100644 index fbbc3b8..0000000 --- a/iw/build.sc +++ /dev/null @@ -1,144 +0,0 @@ -import mill._, scalalib._, scalajslib._ - -import $file.bom - -object support { - val Deps = bom.IWMaterials.Deps - val Versions = bom.IWMaterials.Versions - - trait CommonModule extends ScalaModule { - def scalaVersion = "3.1.1" - def scalacOptions = Seq( - "-encoding", - "utf8", - "-deprecation", - "-explain-types", - "-explain", - "-feature", - "-language:experimental.macros", - "-language:higherKinds", - "-language:implicitConversions", - "-unchecked", - "-Xfatal-warnings", - "-Ykind-projector" - ) - } - - trait CommonJSModule extends CommonModule with ScalaJSModule { - def scalaJSVersion = "1.8.0" - } - - trait CrossPlatformModule extends Module { outer => - def ivyDeps: T[Agg[Dep]] = Agg[Dep]() - def jsDeps: T[Agg[Dep]] = Agg[Dep]() - def jvmDeps: T[Agg[Dep]] = Agg[Dep]() - def moduleDeps: Seq[Module] = Seq[Module]() - - trait PlatformModule extends CommonModule { - def platform: String - override def millSourcePath = outer.millSourcePath - override def ivyDeps = outer.ivyDeps() ++ (platform match { - case "js" => jsDeps() - case _ => jvmDeps() - }) - override def moduleDeps = outer.moduleDeps.collect { - case m: CrossPlatformModule => - platform match { - case "js" => m.js - case _ => m.jvm - } - case m: JavaModule => m - } - } - - trait JsModule extends PlatformModule with CommonJSModule { - def platform = "js" - } - - trait JvmModule extends PlatformModule { - def platform = "jvm" - } - - val js: JsModule - val jvm: JvmModule - } - - trait PureCrossModule extends CrossPlatformModule { - override object js extends JsModule - override object jvm extends JvmModule - } - - trait PureCrossSbtModule extends CrossPlatformModule { - override object js extends JsModule with SbtModule - override object jvm extends JvmModule with SbtModule - } - - trait FullCrossSbtModule extends CrossPlatformModule { - trait FullSources extends JavaModule { self: PlatformModule => - override def sources = T.sources( - millSourcePath / platform / "src" / "main" / "scala", - millSourcePath / "shared" / "src" / "main" / "scala" - ) - - override def resources = T.sources( - millSourcePath / platform / "src" / "main" / "resources", - millSourcePath / "shared" / "src" / "main" / "resources" - ) - } - - override object js extends JsModule with FullSources - override object jvm extends JvmModule with FullSources - } - -} - -import support._ - -object mongo extends CommonModule { - def ivyDeps = Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - ivy"org.mongodb.scala::mongo-scala-driver:4.2.3".withDottyCompat( - scalaVersion() - ) - ) -} - -object tapir extends FullCrossSbtModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson, Deps.zioJson) - def jvmDeps = Agg(Deps.tapirZIO, Deps.tapirZIOHttp4sServer) - def jsDeps = Agg(Deps.tapirSttpClient) -} - -object akkaPersistence extends CommonModule { - def millSourcePath = build.millSourcePath / "akka-persistence" - def ivyDeps = (Agg( - Deps.akka.persistenceQuery, - Deps.akka.persistenceJdbc, - Deps.akka.projection.core, - Deps.akka.projection.eventsourced, - Deps.akka.projection.slick - ) ++ Deps.slick.default).map(_.withDottyCompat(scalaVersion())) ++ Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - Deps.akka.persistence.withDottyCompat(scalaVersion()), - ivy"com.typesafe.akka::akka-cluster-sharding-typed:${Deps.akka.V}" - .withDottyCompat(scalaVersion()) - ) -} - -object ui extends CommonJSModule { - def ivyDeps = Agg( - Deps.zio, - Deps.laminar, - Deps.zioJson, - Deps.waypoint, - Deps.urlDsl, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) -} diff --git a/iw/domain.sc b/iw/domain.sc deleted file mode 100644 index b09c56b..0000000 --- a/iw/domain.sc +++ /dev/null @@ -1,78 +0,0 @@ -import mill._, scalalib._ - -import $file.{build => ff}, ff.support._ - -trait DomainModule extends Module { - // General extra model deps - def modelModules: Seq[Module] = Seq.empty - // General extra codecs deps - def codecsModules: Seq[Module] = Seq.empty - // General extra endpoints deps - def endpointsModules: Seq[Module] = Seq.empty - - // Implementation deps for repo - def repoModules: Seq[JavaModule] = Seq.empty - - object shared extends Module { - object model extends PureCrossModule { - def moduleDeps = modelModules - def ivyDeps = Agg(Deps.zioPrelude) - } - object codecs extends PureCrossModule { - def moduleDeps = Seq(model) ++ codecsModules - def ivyDeps = Agg(Deps.zioJson) - } - } - - trait CommonProjects extends Module { - object model extends PureCrossModule { - def ivyDeps = Agg(Deps.zioPrelude) - def moduleDeps = Seq(shared.model) - } - object codecs extends PureCrossModule { - def moduleDeps = - Seq(model, shared.model, shared.codecs, ff.tapir) - } - object endpoints extends PureCrossModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson) - def moduleDeps = Seq(model, codecs) ++ endpointsModules - } - object client extends CommonJSModule { - def moduleDeps = Seq(endpoints.js) - } - object components extends CommonJSModule { - def ivyDeps = Agg( - Deps.laminar, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) - def moduleDeps = Seq(model.js, codecs.js, client, ff.ui) - } - } - - object query extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(repo, query.endpoints.jvm) - } - object repo extends CommonModule { - def ivyDeps = Agg(Deps.zio) - def moduleDeps = Seq(model.jvm, codecs.jvm) ++ repoModules - } - object projection extends CommonModule { - def moduleDeps = Seq(repo, ff.akkaPersistence) - } - } - - object command extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(entity, command.endpoints.jvm) - } - object entity extends CommonModule { - def moduleDeps = Seq(model.jvm, codecs.jvm, ff.akkaPersistence) - } - } -} diff --git a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala b/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala deleted file mode 100644 index 0fa3493..0000000 --- a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala +++ /dev/null @@ -1,53 +0,0 @@ -package works.iterative.mongo - -import zio.* -import zio.json.* -import zio.config.* -import org.mongodb.scala.* -import org.mongodb.scala.model.Filters.* -import org.bson.json.JsonObject -import org.mongodb.scala.model.ReplaceOptions -import org.mongodb.scala.bson.conversions.Bson - -case class MongoConfig(uri: String) - -object MongoConfig: - val configDesc = - import ConfigDescriptor.* - nested("MONGO")(string("URI").default("mongodb://localhost:27017")) - .to[MongoConfig] - val fromEnv = ZConfig.fromSystemEnv(configDesc) - -extension (m: MongoClient.type) - def layer: RLayer[MongoConfig, MongoClient] = - ZIO - .serviceWithZIO[MongoConfig](c => Task.attempt(MongoClient(c.uri))) - .toLayer - -class MongoJsonRepository[Elem, Key, Criteria]( - collection: MongoCollection[JsonObject], - toFilter: Criteria => Bson, - idFilter: Elem => (String, Key) -)(using JsonCodec[Elem]) { - def matching(criteria: Criteria): Task[Seq[Elem]] = - val filter = toFilter(criteria) - val query = collection.find(filter) - - for - result <- ZIO.fromFuture(_ => query.toFuture) - proof <- ZIO.collect(result)(j => - ZIO.fromOption(j.getJson.fromJson[Elem].toOption) - ) - yield proof - - def put(elem: Elem): Task[Unit] = - Task.async(cb => - collection - .replaceOne( - equal.tupled(idFilter(elem)), - JsonObject(elem.toJson), - ReplaceOptions().upsert(true) - ) - .subscribe(_ => cb(Task.unit), t => cb(Task.fail(t))) - ) -} diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala deleted file mode 100644 index 68d7196..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala +++ /dev/null @@ -1,12 +0,0 @@ -package works.iterative.tapir - -import sttp.model.Uri - -opaque type BaseUri = Option[Uri] - -object BaseUri: - - def apply(optU: Option[Uri]): BaseUri = optU - def apply(u: Uri): BaseUri = Some(u) - - extension (v: BaseUri) def toUri: Option[Uri] = v diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 653bc51..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,22 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.client.sttp.SttpClientInterpreter -import sttp.tapir.PublicEndpoint -import sttp.tapir.client.sttp.WebSocketToPipe -import scala.concurrent.Future -import sttp.client3.SttpBackend -import sttp.capabilities.WebSockets - -trait CustomTapirPlatformSpecific extends SttpClientInterpreter: - self: CustomTapir => - - type Backend = SttpBackend[Future, WebSockets] - - def makeClient[I, E, O]( - endpoint: PublicEndpoint[I, E, O, Any] - )(using - baseUri: BaseUri, - backend: Backend, - wsToPipe: WebSocketToPipe[Any] - ): I => Future[O] = - toClientThrowErrors(endpoint, baseUri.toUri, backend) diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 2545fbd..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,5 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.ztapir.ZTapir - -trait CustomTapirPlatformSpecific extends ZTapir diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala deleted file mode 100644 index 55e4ca1..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala +++ /dev/null @@ -1,7 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.server.http4s.ztapir.ZHttp4sServerInterpreter - -trait Http4sCustomTapir[Env] - extends CustomTapir - with ZHttp4sServerInterpreter[Env] diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala deleted file mode 100644 index dd52e9b..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala +++ /dev/null @@ -1,14 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.Tapir -import sttp.tapir.json.zio.TapirJsonZio -import sttp.tapir.TapirAliases - -trait CustomTapir - extends Tapir - with TapirJsonZio - with TapirAliases - with CustomTapirPlatformSpecific: - given Schema[ServerError] = Schema.derived - -object CustomTapir extends CustomTapir diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala deleted file mode 100644 index 715591d..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.tapir - -import zio.json.* - -sealed trait ServerError -case class InternalServerError(msg: String) extends ServerError -object InternalServerError: - def fromThrowable(t: Throwable): ServerError = InternalServerError( - t.getMessage - ) - -object ServerError: - given JsonCodec[ServerError] = DeriveJsonCodec.gen diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/File.scala deleted file mode 100644 index 09f6c07..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala +++ /dev/null @@ -1,3 +0,0 @@ -package works.iterative.services.files - -case class File(url: String, name: String) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala deleted file mode 100644 index 46633a5..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala +++ /dev/null @@ -1,25 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.Renderable - -given Renderable[File] with - extension (m: File) - def toHtml: HtmlElement = - li( - cls("pl-3 pr-4 py-3 flex items-center justify-between text-sm"), - div( - cls("w-0 flex-1 flex items-center"), - Icons.solid - .paperclip() - .amend(svg.cls := "flex-shrink-0 text-gray-400"), - span(cls("ml-2 flex-1 w-0 truncate"), m.name) - ), - a( - href(m.url), - cls("font-medium text-indigo-600 hover:text-indigo-500"), - "Otevřít" - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala deleted file mode 100644 index ac59db8..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import io.laminext.syntax.core.{*, given} - -def FileList(files: List[File]): HtmlElement = - ul( - role("list"), - cls("border border-gray-200 rounded-md divide-y divide-gray-200"), - files.map(_.toHtml) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala deleted file mode 100644 index 2e8f690..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala +++ /dev/null @@ -1,81 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import io.laminext.syntax.core.{*, given} - -object FilePicker: - sealed trait Event - sealed trait DoneEvent extends Event - case class SelectionUpdated(files: Set[File]) extends DoneEvent - case object SelectionCancelled extends DoneEvent - case object AvailableFilesRequested extends Event - - def apply( - currentFiles: Signal[List[File]], - availableFilesStream: EventStream[List[File]] - )(selectionUpdates: Observer[Event]): HtmlElement = - val (updatesStream, updatesObserver) = EventStream.withObserver[Event] - val selectorOpen = Var[Boolean](false) - - // This sequence tricks browser into displaying modal content centered - // Inspired by modal in headless ui playground - // https://github.com/tailwindlabs/headlessui/blob/fdd26297953080d5ec905dda0bf5ec9607897d86/packages/playground-react/pages/transitions/component-examples/modal.tsx#L78-L79 - inline def browserCenteringModalTrick: Modifier[HtmlElement] = - Seq[Modifier[HtmlElement]]( - span(cls("hidden sm:inline-block sm:h-screen sm:align-middle")), - "​" // Zero width space - ) - - inline def overlay: Modifier[HtmlElement] = - // Page overlay - /* TODO: transition - enter="ease-out duration-300" - enterFrom="opacity-0" - enterTo="opacity-100" - leave="ease-in duration-200" - leaveFrom="opacity-100" - leaveTo="opacity-0" - */ - div( - div( - onClick.mapTo(false) --> selectorOpen.writer, - cls("fixed inset-0 transition-opacity"), - div(cls("absolute inset-0 bg-gray-500 opacity-75")) - ) - ) - - inline def modalSelector: HtmlElement = - div( - cls("fixed inset-0 z-10 overflow-y-auto"), - div( - cls("text-center sm:block sm:p-0"), - overlay, - browserCenteringModalTrick, - child <-- currentFiles.map( - FileSelector(_, availableFilesStream)(updatesObserver) - ) - ) - ) - - div( - updatesStream --> selectionUpdates, - updatesStream.collect { case _: DoneEvent => - false - } --> selectorOpen.writer, - child.maybe <-- selectorOpen.signal.map(isOpen => - if isOpen then Some(modalSelector) else None - ), - div( - cls("flex flex-col space-y-5"), - child.maybe <-- currentFiles.map(files => - if files.isEmpty then None else Some(FileList(files)) - ), - button( - tpe := "button", - cls := "bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500", - "Zvolit soubory", - onClick.mapTo(true) --> selectorOpen.writer - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala deleted file mode 100644 index b43e07a..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala +++ /dev/null @@ -1,74 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.Loading -import io.laminext.syntax.core.{*, given} -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -object FileSelector: - import FilePicker._ - - def apply( - initialFiles: List[File], - availableFilesStream: EventStream[List[File]] - )(selectionUpdates: Observer[Event]): HtmlElement = - val selectedFiles = Var[Set[File]](initialFiles.to(Set)) - val availableFiles = availableFilesStream.startWithNone - // Request the files to display - selectionUpdates.onNext(AvailableFilesRequested) - div( - cls( - "inline-block transform overflow-hidden rounded-lg bg-white text-left align-bottom shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:align-middle" - ), - role("dialog"), - customHtmlAttr("aria.modal", BooleanAsTrueFalseStringCodec)(true), - aria.labelledBy("modal-headline"), - div( - cls("bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"), - div( - cls("sm:flex sm:items-start"), - div( - cls("mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"), - h3( - cls("text-lg font-medium leading-6 text-gray-900"), - idAttr("modal-headline"), - "Výběr souborů" - ) - ) - ), - child <-- availableFiles - .split(_ => ())((_, _, af) => FileTable(af, selectedFiles)) - .map(_.getOrElse(Loading)) - ), - div( - cls("bg-gray-50 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6"), - span( - cls("flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto"), - button( - typ("button"), - cls( - "focus:shadow-outline-green inline-flex w-full justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-base font-medium leading-6 text-white shadow-sm transition duration-150 ease-in-out hover:bg-indigo-500 focus:border-indigo-700 focus:outline-none sm:text-sm sm:leading-5" - ), - "Potvrdit", - composeEvents(onClick)( - _.sample(selectedFiles) - .map(SelectionUpdated(_)) - ) --> selectionUpdates - ) - ), - span( - cls("mt-3 flex w-full rounded-md shadow-sm sm:mt-0 sm:w-auto"), - button( - typ("button"), - cls( - "focus:shadow-outline-blue inline-flex w-full justify-center rounded-md border border-gray-300 bg-white px-4 py-2 text-base font-medium leading-6 text-gray-700 shadow-sm transition duration-150 ease-in-out hover:text-gray-500 focus:border-blue-300 focus:outline-none sm:text-sm sm:leading-5" - ), - "Zrušit", - onClick.mapTo(SelectionCancelled) --> selectionUpdates - ) - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala deleted file mode 100644 index aa11b2b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala +++ /dev/null @@ -1,91 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import works.iterative.ui.components.tailwind.Icons - -def FileTable( - files: Signal[List[File]], - selectedFiles: Var[Set[File]] -): HtmlElement = - val scope = customHtmlAttr("scope", StringAsIsCodec) - - def headerRow: HtmlElement = - val col = scope("col") - val baseM: Modifier[HtmlElement] = Seq(cls("px-6 py-3"), col) - val textH = cls( - "text-left text-xs font-medium text-gray-500 uppercase tracking-wider" - ) - tr( - th(baseM, span(cls("sr-only"), "Vybrat")), - th(baseM, textH, "Název"), - th(baseM, cls("relative"), span(cls("sr-only"), "Otevřít")) - ) - - def tableRow( - f: File, - idx: Int, - selected: Boolean - )(toggleSelection: Observer[Unit]): HtmlElement = - val baseC = cls("px-6 py-4 whitespace-nowrap text-sm") - tr( - cls(if idx % 2 == 0 then "bg-gray-50" else "bg-white"), - td( - cls("font-medium cursor-pointer"), - onClick.mapTo(()) --> toggleSelection, - cls(if selected then "text-green-900" else "text-gray-200"), - Icons.outline.`check-circle`().amend(svg.cls := "mx-auto"), - span(cls("sr-only"), if selected then "Vybráno" else "Nevybráno") - ), - td( - baseC, - cls("font-medium text-gray-900"), - f.name, - onClick.mapTo(()) --> toggleSelection - ), - td( - baseC, - cls("text-right font-medium"), - a( - href(f.url), - target("_blank"), - cls( - "flex items-center space-x-4 text-indigo-600 hover:text-indigo-900" - ), - Icons.outline.`external-link`(), - "Otevřít" - ) - ) - ) - - div( - cls("flex flex-col"), - div(cls("overflow-x-auto sm:-mx-6 lg:-mx-8")), - div( - cls("py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8"), - div( - cls("shadow overflow-hidden border-b border-gray-200 sm:rounded-lg"), - table( - cls("min-w-full divide-y divide-gray-200"), - thead( - cls("bg-gray-50"), - headerRow - ), - tbody( - children <-- files - .map(_.zipWithIndex) - .combineWithFn(selectedFiles)((f, sel) => - f.map((file, idx) => - val active = sel.contains(file) - tableRow(file, idx, active)( - selectedFiles.writer - .contramap(_ => if active then sel - file else sel + file) - ) - ) - ) - ) - ) - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala deleted file mode 100644 index 0fc8796..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala +++ /dev/null @@ -1,32 +0,0 @@ -package works.iterative.ui.components.tailwind - -import CustomAttrs.ariaHidden -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -// TODO: render icon or picture based on img signal -class Avatar($avatarImg: Signal[Option[String]]): - inline def avatarPlaceholder(size: Int): HtmlElement = - div( - cls := s"rounded-full text-indigo-200 bg-indigo-500 h-${size} w-${size} flex items-center justify-center", - Icons.outline.user(size - 2) - ) - - inline def avatarImage(size: Int): Signal[HtmlElement] = - $avatarImg.split(_ => ())((_, _, $url) => - img( - cls := s"w-$size h-$size rounded-full", - src <-- $url, - alt := "" - ) - ).map(_.getOrElse(avatarPlaceholder(size))) - - inline def avatar(size: Int): HtmlElement = - div( - cls := "relative", - child <-- avatarImage(size), - span( - cls := "absolute inset-0 shadow-inner rounded-full", - ariaHidden := true - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala deleted file mode 100644 index bcbed88..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala +++ /dev/null @@ -1,77 +0,0 @@ -package works.iterative.ui.components.tailwind - -enum ColorWeight(value: String): - inline def toCSS: String = value - // To be used for black and white until - // we support better mechanism via extension methods - case w__ extends ColorWeight("") - case w50 extends ColorWeight("50") - case w100 extends ColorWeight("100") - case w200 extends ColorWeight("200") - case w300 extends ColorWeight("300") - case w400 extends ColorWeight("400") - case w500 extends ColorWeight("500") - case w600 extends ColorWeight("600") - case w700 extends ColorWeight("700") - case w800 extends ColorWeight("800") - case w900 extends ColorWeight("900") - -enum Color(name: String): - import ColorWeight._ - - inline def toCSSNoColorWeight(prefix: String): String = - s"${prefix}-${name}" - inline def toCSSWithColorWeight( - prefix: String, - weight: ColorWeight - ): String = - s"${prefix}-${name}-${weight.toCSS}" - inline def toCSS(prefix: String)(weight: ColorWeight): String = - weight match { - case `w__` => toCSSNoColorWeight(prefix) - case _ => toCSSWithColorWeight(prefix, weight) - } - inline def bg: ColorWeight => String = toCSS("bg")(_) - inline def text: ColorWeight => String = toCSS("text")(_) - inline def decoration: ColorWeight => String = toCSS("decoration")(_) - inline def border: ColorWeight => String = toCSS("border")(_) - inline def outline: ColorWeight => String = toCSS("outline")(_) - inline def divide: ColorWeight => String = toCSS("divide")(_) - inline def ring: ColorWeight => String = toCSS("ring")(_) - inline def ringOffset: ColorWeight => String = toCSS("ring-offset")(_) - inline def shadow: ColorWeight => String = toCSS("shadow")(_) - inline def accent: ColorWeight => String = toCSS("accent")(_) - - // TODO: change the "stupid" methods to extension methods - // that will keep the invariants in comments lower - case current extends Color("current") - case inherit extends Color("inherit") - // Not present in for all methods - case transp extends Color("transparent") - // Seen in accent, not preset otherwise - case auto extends Color("auto") - // Black and white do not have weight - case black extends Color("black") - case white extends Color("white") - case slate extends Color("slate") - case gray extends Color("gray") - case zinc extends Color("zinc") - case neutral extends Color("neutral") - case stone extends Color("stone") - case red extends Color("red") - case orange extends Color("orange") - case amber extends Color("amber") - case yellow extends Color("yellow") - case lime extends Color("lime") - case green extends Color("green") - case emerald extends Color("emerald") - case teal extends Color("teal") - case cyan extends Color("cyan") - case sky extends Color("sky") - case blue extends Color("blue") - case indigo extends Color("indigo") - case violet extends Color("violet") - case purple extends Color("purple") - case fuchsia extends Color("fuchsia") - case pink extends Color("pink") - case rose extends Color("rose") diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala deleted file mode 100644 index 6fb453c..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -object CustomAttrs { - // Made a pull request to add aria-current to scala-dom-types, remove after - val ariaCurrent = customHtmlAttr("aria-current", StringAsIsCodec) - val ariaHidden = customHtmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - - val datetime = customHtmlAttr("datetime", StringAsIsCodec) - - object svg { - import com.raquo.laminar.api.L.svg.{*, given} - val ariaHidden = - customSvgAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - } -} diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala deleted file mode 100644 index 6cda48b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala +++ /dev/null @@ -1,28 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -object Display: - - enum Breakpoint: - case sm, md, lg, xl, `2xl` - - enum DisplayClass: - case block, `inline-block`, `inline`, flex, `inline-flex`, table, - `inline-table`, `table-caption` - - object ShowUpFrom: - inline def apply( - br: Breakpoint, - dc: DisplayClass = DisplayClass.block - ): HtmlElement = - div( - cls := "hidden", - cls := s"${br}:${dc}" - ) - - object HideUpTo: - inline def apply(br: Breakpoint): HtmlElement = - div( - cls := s"${br}:hidden" - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala deleted file mode 100644 index e7e6627..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.akka - -// Base class for all command-processing related exceptions from handlers -sealed abstract class CommandHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandNotAvailable[C, S](cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd není dostupný ve stavu $state" - ) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandRejected[C, S](reason: String, cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd byl ve stavu $state odmítnut s odůvodněním $reason" - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala deleted file mode 100644 index 72a5bf9..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.akka - -sealed abstract class EventHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -case class UnhandledEvent[Event, State](event: Event, state: State) - extends EventHandlerException( - s"Událost $event nastala ve stavu $state bez možnosti zpracování" - ) diff --git a/iw/bom.sc b/iw/bom.sc deleted file mode 100644 index e9266b8..0000000 --- a/iw/bom.sc +++ /dev/null @@ -1,227 +0,0 @@ -import mill._, scalalib._ - -object IWMaterials { - - object Versions { - val akka = "2.6.16" - val akkaHttp = "10.2.4" - val cats = "2.7.0" - val elastic4s = "7.12.2" - val http4s = "0.23.10" - val http4sPac4J = "4.0.0" - val laminar = "0.14.2" - val laminext = laminar - val logbackClassic = "1.2.10" - val pac4j = "5.2.0" - val play = "2.8.8" - val playJson = "2.9.2" - val scalaTest = "3.2.9" - val slick = "3.3.3" - val sttpClient = "3.5.0" - val tapir = "0.20.1" - val urlDsl = "0.4.0" - val waypoint = "0.5.0" - val zio = "2.0.0-RC2" - val zioConfig = "3.0.0-RC2" - val zioInteropCats = "3.3.0-RC2" - val zioJson = "0.3.0-RC3" - val zioLogging = "2.0.0-RC5" - val zioPrelude = "1.0.0-RC10" - val zioZMX = "0.0.11" - } - - object Deps extends AkkaLibs with SlickLibs { - import IWMaterials.{Versions => V} - - val zioOrg = "dev.zio" - - def zioLib(name: String, version: String): Dep = - ivy"$zioOrg::zio-$name::$version" - - lazy val zio: Dep = ivy"$zioOrg::zio:${V.zio}" - - lazy val zioTest: Dep = zioLib("test", V.zio) - lazy val zioTestSbt: Dep = zioLib("test", V.zio) - - lazy val zioConfig: Dep = zioLib("config", V.zioConfig) - lazy val zioConfigTypesafe: Dep = - zioLib("config-typesafe", V.zioConfig) - lazy val zioConfigMagnolia: Dep = - zioLib("config-magnolia", V.zioConfig) - - lazy val zioJson: Dep = zioLib("json", V.zioJson) - lazy val zioLogging: Dep = zioLib("logging", V.zioLogging) - lazy val zioLoggingSlf4j: Dep = - zioLib("logging-slf4j", V.zioLogging) - lazy val zioPrelude: Dep = zioLib("prelude", V.zioPrelude) - lazy val zioStreams: Dep = zioLib("streams", V.zio) - lazy val zioZMX: Dep = zioLib("zmx", V.zioZMX) - lazy val zioInteropCats: Dep = - zioLib("interop-cats", V.zioInteropCats) - - /* What is the equivalent? ZIOModule with prepared test config? - def useZIO(testConf: Configuration*): Agg[Dep] = Agg( - zio, - zioTest, - zioTestSbt, - testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework") - ) - */ - - /* - def useZIOAll(testConf: Configuration*): Seq[Def.Setting[_]] = - useZIO(testConf: _*) ++ Seq( - zioStreams, - zioConfig, - zioConfigTypesafe, - zioConfigMagnolia, - zioJson, - zioZMX, - zioLogging, - zioPrelude - ) - */ - - private val tapirOrg = "com.softwaremill.sttp.tapir" - def tapirLib(name: String): Dep = - ivy"${tapirOrg}::tapir-$name::${V.tapir}" - - lazy val tapirCore: Dep = tapirLib("core") - lazy val tapirZIO: Dep = tapirLib("zio") - lazy val tapirZIOJson: Dep = tapirLib("json-zio") - lazy val tapirSttpClient: Dep = tapirLib("sttp-client") - lazy val tapirCats: Dep = tapirLib("cats") - lazy val tapirZIOHttp4sServer: Dep = tapirLib("zio-http4s-server") - - private val sttpClientOrg = "com.softwaremill.sttp.client3" - def sttpClientLib(name: String): Dep = - ivy"${sttpClientOrg}::${name}:${V.sttpClient}" - - lazy val sttpClientCore: Dep = sttpClientLib("core") - - lazy val http4sBlazeServer: Dep = - ivy"org.http4s::http4s-blaze-server:${V.http4s}" - - lazy val http4sPac4J: Dep = - ivy"org.pac4j::http4s-pac4j:${V.http4sPac4J}" - lazy val pac4jOIDC: Dep = - ivy"org.pac4j:pac4j-oidc:${V.pac4j}" - - lazy val scalaTest: Dep = - ivy"org.scalatest::scalatest:${V.scalaTest}" - lazy val scalaTestPlusScalacheck: Dep = - ivy"org.scalatestplus::scalacheck-1-15:3.2.9.0" - lazy val playScalaTest: Dep = - ivy"org.scalatestplus.play::scalatestplus-play:5.1.0" - - private val playOrg = "com.typesafe.play" - lazy val playMailer: Dep = ivy"${playOrg}::play-mailer:8.0.1" - lazy val playServer: Dep = ivy"${playOrg}::play-server:${V.play}" - lazy val playAkkaServer: Dep = - ivy"${playOrg}::play-akka-http-server:${V.play}" - lazy val play: Dep = ivy"${playOrg}::play:${V.play}" - lazy val playAhcWs: Dep = ivy"${playOrg}::play-ahc-ws:${V.play}" - lazy val playJson: Dep = ivy"${playOrg}::play-json:${V.playJson}" - - private val elastic4sOrg = "com.sksamuel.elastic4s" - lazy val useElastic4S: Agg[Dep] = Agg( - ivy"${elastic4sOrg}::elastic4s-core:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-client-akka:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-http-streams:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-json-play:${V.elastic4s}" - ) - - lazy val laminar: Dep = ivy"com.raquo::laminar::${V.laminar}" - - private def laminext(name: String): Dep = - ivy"io.laminext::$name::${V.laminar}" - - lazy val laminextCore: Dep = laminext("core") - lazy val laminextTailwind: Dep = laminext("tailwind") - lazy val laminextFetch: Dep = laminext("fetch") - lazy val laminextValidationCore: Dep = laminext("validation-core") - lazy val laminextUI: Dep = laminext("ui") - - lazy val waypoint: Dep = - ivy"com.raquo::waypoint::${V.waypoint}" - - lazy val urlDsl: Dep = - ivy"be.doeraene::url-dsl::${V.urlDsl}" - - lazy val scalaJavaTime: Dep = - ivy"io.github.cquiroz::scala-java-time::2.3.0" - - lazy val scalaJavaLocales: Dep = - ivy"io.github.cquiroz::scala-java-locales::1.2.1" - - lazy val logbackClassic: Dep = - ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" - } - - trait AkkaLibs { - - self: SlickLibs => - - object akka { - val V = Versions.akka - val tOrg = "com.typesafe.akka" - val lOrg = "com.lightbend.akka" - - def akkaMod(name: String): Dep = - ivy"$tOrg::akka-$name:$V" - - lazy val actor: Dep = akkaMod("actor") - lazy val actorTyped: Dep = akkaMod("actor-typed") - lazy val stream: Dep = akkaMod("stream") - lazy val persistence: Dep = akkaMod("persistence-typed") - lazy val persistenceQuery: Dep = akkaMod("persistence-query") - lazy val persistenceJdbc: Dep = - ivy"$lOrg::akka-persistence-jdbc:5.0.4" - val persistenceTestKit: Dep = ivy"$tOrg::akka-persistence-testkit:$V" - - object http { - val V = Versions.akkaHttp - - lazy val http: Dep = ivy"$tOrg::akka-http:$V" - lazy val sprayJson: Dep = ivy"$tOrg::akka-http-spray-json:$V" - - } - - object projection { - val V = "1.2.2" - - lazy val core: Dep = - ivy"$lOrg::akka-projection-core:$V" - lazy val eventsourced: Dep = - ivy"$lOrg::akka-projection-eventsourced:$V" - lazy val slick: Dep = - ivy"$lOrg::akka-projection-slick:$V" - lazy val jdbc: Dep = - ivy"$lOrg::akka-projection-jdbc:$V" - } - - object profiles { - // TODO: deal with cross-version for Scala3 (for3Use2_13) - lazy val eventsourcedJdbcProjection: Agg[Dep] = Agg( - persistenceQuery, - projection.core, - projection.eventsourced, - projection.slick, - persistenceJdbc - ) ++ slick.default - } - } - } - - trait SlickLibs { - object slick { - val V = IWMaterials.Versions.slick - val org = "com.typesafe.slick" - - lazy val slick: Dep = ivy"$org::slick:$V" - lazy val hikaricp: Dep = ivy"$org::slick-hikaricp:$V" - lazy val default: Agg[Dep] = Agg(slick, hikaricp) - } - } - -} diff --git a/iw/build.sc b/iw/build.sc deleted file mode 100644 index fbbc3b8..0000000 --- a/iw/build.sc +++ /dev/null @@ -1,144 +0,0 @@ -import mill._, scalalib._, scalajslib._ - -import $file.bom - -object support { - val Deps = bom.IWMaterials.Deps - val Versions = bom.IWMaterials.Versions - - trait CommonModule extends ScalaModule { - def scalaVersion = "3.1.1" - def scalacOptions = Seq( - "-encoding", - "utf8", - "-deprecation", - "-explain-types", - "-explain", - "-feature", - "-language:experimental.macros", - "-language:higherKinds", - "-language:implicitConversions", - "-unchecked", - "-Xfatal-warnings", - "-Ykind-projector" - ) - } - - trait CommonJSModule extends CommonModule with ScalaJSModule { - def scalaJSVersion = "1.8.0" - } - - trait CrossPlatformModule extends Module { outer => - def ivyDeps: T[Agg[Dep]] = Agg[Dep]() - def jsDeps: T[Agg[Dep]] = Agg[Dep]() - def jvmDeps: T[Agg[Dep]] = Agg[Dep]() - def moduleDeps: Seq[Module] = Seq[Module]() - - trait PlatformModule extends CommonModule { - def platform: String - override def millSourcePath = outer.millSourcePath - override def ivyDeps = outer.ivyDeps() ++ (platform match { - case "js" => jsDeps() - case _ => jvmDeps() - }) - override def moduleDeps = outer.moduleDeps.collect { - case m: CrossPlatformModule => - platform match { - case "js" => m.js - case _ => m.jvm - } - case m: JavaModule => m - } - } - - trait JsModule extends PlatformModule with CommonJSModule { - def platform = "js" - } - - trait JvmModule extends PlatformModule { - def platform = "jvm" - } - - val js: JsModule - val jvm: JvmModule - } - - trait PureCrossModule extends CrossPlatformModule { - override object js extends JsModule - override object jvm extends JvmModule - } - - trait PureCrossSbtModule extends CrossPlatformModule { - override object js extends JsModule with SbtModule - override object jvm extends JvmModule with SbtModule - } - - trait FullCrossSbtModule extends CrossPlatformModule { - trait FullSources extends JavaModule { self: PlatformModule => - override def sources = T.sources( - millSourcePath / platform / "src" / "main" / "scala", - millSourcePath / "shared" / "src" / "main" / "scala" - ) - - override def resources = T.sources( - millSourcePath / platform / "src" / "main" / "resources", - millSourcePath / "shared" / "src" / "main" / "resources" - ) - } - - override object js extends JsModule with FullSources - override object jvm extends JvmModule with FullSources - } - -} - -import support._ - -object mongo extends CommonModule { - def ivyDeps = Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - ivy"org.mongodb.scala::mongo-scala-driver:4.2.3".withDottyCompat( - scalaVersion() - ) - ) -} - -object tapir extends FullCrossSbtModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson, Deps.zioJson) - def jvmDeps = Agg(Deps.tapirZIO, Deps.tapirZIOHttp4sServer) - def jsDeps = Agg(Deps.tapirSttpClient) -} - -object akkaPersistence extends CommonModule { - def millSourcePath = build.millSourcePath / "akka-persistence" - def ivyDeps = (Agg( - Deps.akka.persistenceQuery, - Deps.akka.persistenceJdbc, - Deps.akka.projection.core, - Deps.akka.projection.eventsourced, - Deps.akka.projection.slick - ) ++ Deps.slick.default).map(_.withDottyCompat(scalaVersion())) ++ Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - Deps.akka.persistence.withDottyCompat(scalaVersion()), - ivy"com.typesafe.akka::akka-cluster-sharding-typed:${Deps.akka.V}" - .withDottyCompat(scalaVersion()) - ) -} - -object ui extends CommonJSModule { - def ivyDeps = Agg( - Deps.zio, - Deps.laminar, - Deps.zioJson, - Deps.waypoint, - Deps.urlDsl, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) -} diff --git a/iw/domain.sc b/iw/domain.sc deleted file mode 100644 index b09c56b..0000000 --- a/iw/domain.sc +++ /dev/null @@ -1,78 +0,0 @@ -import mill._, scalalib._ - -import $file.{build => ff}, ff.support._ - -trait DomainModule extends Module { - // General extra model deps - def modelModules: Seq[Module] = Seq.empty - // General extra codecs deps - def codecsModules: Seq[Module] = Seq.empty - // General extra endpoints deps - def endpointsModules: Seq[Module] = Seq.empty - - // Implementation deps for repo - def repoModules: Seq[JavaModule] = Seq.empty - - object shared extends Module { - object model extends PureCrossModule { - def moduleDeps = modelModules - def ivyDeps = Agg(Deps.zioPrelude) - } - object codecs extends PureCrossModule { - def moduleDeps = Seq(model) ++ codecsModules - def ivyDeps = Agg(Deps.zioJson) - } - } - - trait CommonProjects extends Module { - object model extends PureCrossModule { - def ivyDeps = Agg(Deps.zioPrelude) - def moduleDeps = Seq(shared.model) - } - object codecs extends PureCrossModule { - def moduleDeps = - Seq(model, shared.model, shared.codecs, ff.tapir) - } - object endpoints extends PureCrossModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson) - def moduleDeps = Seq(model, codecs) ++ endpointsModules - } - object client extends CommonJSModule { - def moduleDeps = Seq(endpoints.js) - } - object components extends CommonJSModule { - def ivyDeps = Agg( - Deps.laminar, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) - def moduleDeps = Seq(model.js, codecs.js, client, ff.ui) - } - } - - object query extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(repo, query.endpoints.jvm) - } - object repo extends CommonModule { - def ivyDeps = Agg(Deps.zio) - def moduleDeps = Seq(model.jvm, codecs.jvm) ++ repoModules - } - object projection extends CommonModule { - def moduleDeps = Seq(repo, ff.akkaPersistence) - } - } - - object command extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(entity, command.endpoints.jvm) - } - object entity extends CommonModule { - def moduleDeps = Seq(model.jvm, codecs.jvm, ff.akkaPersistence) - } - } -} diff --git a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala b/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala deleted file mode 100644 index 0fa3493..0000000 --- a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala +++ /dev/null @@ -1,53 +0,0 @@ -package works.iterative.mongo - -import zio.* -import zio.json.* -import zio.config.* -import org.mongodb.scala.* -import org.mongodb.scala.model.Filters.* -import org.bson.json.JsonObject -import org.mongodb.scala.model.ReplaceOptions -import org.mongodb.scala.bson.conversions.Bson - -case class MongoConfig(uri: String) - -object MongoConfig: - val configDesc = - import ConfigDescriptor.* - nested("MONGO")(string("URI").default("mongodb://localhost:27017")) - .to[MongoConfig] - val fromEnv = ZConfig.fromSystemEnv(configDesc) - -extension (m: MongoClient.type) - def layer: RLayer[MongoConfig, MongoClient] = - ZIO - .serviceWithZIO[MongoConfig](c => Task.attempt(MongoClient(c.uri))) - .toLayer - -class MongoJsonRepository[Elem, Key, Criteria]( - collection: MongoCollection[JsonObject], - toFilter: Criteria => Bson, - idFilter: Elem => (String, Key) -)(using JsonCodec[Elem]) { - def matching(criteria: Criteria): Task[Seq[Elem]] = - val filter = toFilter(criteria) - val query = collection.find(filter) - - for - result <- ZIO.fromFuture(_ => query.toFuture) - proof <- ZIO.collect(result)(j => - ZIO.fromOption(j.getJson.fromJson[Elem].toOption) - ) - yield proof - - def put(elem: Elem): Task[Unit] = - Task.async(cb => - collection - .replaceOne( - equal.tupled(idFilter(elem)), - JsonObject(elem.toJson), - ReplaceOptions().upsert(true) - ) - .subscribe(_ => cb(Task.unit), t => cb(Task.fail(t))) - ) -} diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala deleted file mode 100644 index 68d7196..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala +++ /dev/null @@ -1,12 +0,0 @@ -package works.iterative.tapir - -import sttp.model.Uri - -opaque type BaseUri = Option[Uri] - -object BaseUri: - - def apply(optU: Option[Uri]): BaseUri = optU - def apply(u: Uri): BaseUri = Some(u) - - extension (v: BaseUri) def toUri: Option[Uri] = v diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 653bc51..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,22 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.client.sttp.SttpClientInterpreter -import sttp.tapir.PublicEndpoint -import sttp.tapir.client.sttp.WebSocketToPipe -import scala.concurrent.Future -import sttp.client3.SttpBackend -import sttp.capabilities.WebSockets - -trait CustomTapirPlatformSpecific extends SttpClientInterpreter: - self: CustomTapir => - - type Backend = SttpBackend[Future, WebSockets] - - def makeClient[I, E, O]( - endpoint: PublicEndpoint[I, E, O, Any] - )(using - baseUri: BaseUri, - backend: Backend, - wsToPipe: WebSocketToPipe[Any] - ): I => Future[O] = - toClientThrowErrors(endpoint, baseUri.toUri, backend) diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 2545fbd..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,5 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.ztapir.ZTapir - -trait CustomTapirPlatformSpecific extends ZTapir diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala deleted file mode 100644 index 55e4ca1..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala +++ /dev/null @@ -1,7 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.server.http4s.ztapir.ZHttp4sServerInterpreter - -trait Http4sCustomTapir[Env] - extends CustomTapir - with ZHttp4sServerInterpreter[Env] diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala deleted file mode 100644 index dd52e9b..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala +++ /dev/null @@ -1,14 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.Tapir -import sttp.tapir.json.zio.TapirJsonZio -import sttp.tapir.TapirAliases - -trait CustomTapir - extends Tapir - with TapirJsonZio - with TapirAliases - with CustomTapirPlatformSpecific: - given Schema[ServerError] = Schema.derived - -object CustomTapir extends CustomTapir diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala deleted file mode 100644 index 715591d..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.tapir - -import zio.json.* - -sealed trait ServerError -case class InternalServerError(msg: String) extends ServerError -object InternalServerError: - def fromThrowable(t: Throwable): ServerError = InternalServerError( - t.getMessage - ) - -object ServerError: - given JsonCodec[ServerError] = DeriveJsonCodec.gen diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/File.scala deleted file mode 100644 index 09f6c07..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala +++ /dev/null @@ -1,3 +0,0 @@ -package works.iterative.services.files - -case class File(url: String, name: String) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala deleted file mode 100644 index 46633a5..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala +++ /dev/null @@ -1,25 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.Renderable - -given Renderable[File] with - extension (m: File) - def toHtml: HtmlElement = - li( - cls("pl-3 pr-4 py-3 flex items-center justify-between text-sm"), - div( - cls("w-0 flex-1 flex items-center"), - Icons.solid - .paperclip() - .amend(svg.cls := "flex-shrink-0 text-gray-400"), - span(cls("ml-2 flex-1 w-0 truncate"), m.name) - ), - a( - href(m.url), - cls("font-medium text-indigo-600 hover:text-indigo-500"), - "Otevřít" - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala deleted file mode 100644 index ac59db8..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import io.laminext.syntax.core.{*, given} - -def FileList(files: List[File]): HtmlElement = - ul( - role("list"), - cls("border border-gray-200 rounded-md divide-y divide-gray-200"), - files.map(_.toHtml) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala deleted file mode 100644 index 2e8f690..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala +++ /dev/null @@ -1,81 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import io.laminext.syntax.core.{*, given} - -object FilePicker: - sealed trait Event - sealed trait DoneEvent extends Event - case class SelectionUpdated(files: Set[File]) extends DoneEvent - case object SelectionCancelled extends DoneEvent - case object AvailableFilesRequested extends Event - - def apply( - currentFiles: Signal[List[File]], - availableFilesStream: EventStream[List[File]] - )(selectionUpdates: Observer[Event]): HtmlElement = - val (updatesStream, updatesObserver) = EventStream.withObserver[Event] - val selectorOpen = Var[Boolean](false) - - // This sequence tricks browser into displaying modal content centered - // Inspired by modal in headless ui playground - // https://github.com/tailwindlabs/headlessui/blob/fdd26297953080d5ec905dda0bf5ec9607897d86/packages/playground-react/pages/transitions/component-examples/modal.tsx#L78-L79 - inline def browserCenteringModalTrick: Modifier[HtmlElement] = - Seq[Modifier[HtmlElement]]( - span(cls("hidden sm:inline-block sm:h-screen sm:align-middle")), - "​" // Zero width space - ) - - inline def overlay: Modifier[HtmlElement] = - // Page overlay - /* TODO: transition - enter="ease-out duration-300" - enterFrom="opacity-0" - enterTo="opacity-100" - leave="ease-in duration-200" - leaveFrom="opacity-100" - leaveTo="opacity-0" - */ - div( - div( - onClick.mapTo(false) --> selectorOpen.writer, - cls("fixed inset-0 transition-opacity"), - div(cls("absolute inset-0 bg-gray-500 opacity-75")) - ) - ) - - inline def modalSelector: HtmlElement = - div( - cls("fixed inset-0 z-10 overflow-y-auto"), - div( - cls("text-center sm:block sm:p-0"), - overlay, - browserCenteringModalTrick, - child <-- currentFiles.map( - FileSelector(_, availableFilesStream)(updatesObserver) - ) - ) - ) - - div( - updatesStream --> selectionUpdates, - updatesStream.collect { case _: DoneEvent => - false - } --> selectorOpen.writer, - child.maybe <-- selectorOpen.signal.map(isOpen => - if isOpen then Some(modalSelector) else None - ), - div( - cls("flex flex-col space-y-5"), - child.maybe <-- currentFiles.map(files => - if files.isEmpty then None else Some(FileList(files)) - ), - button( - tpe := "button", - cls := "bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500", - "Zvolit soubory", - onClick.mapTo(true) --> selectorOpen.writer - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala deleted file mode 100644 index b43e07a..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala +++ /dev/null @@ -1,74 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.Loading -import io.laminext.syntax.core.{*, given} -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -object FileSelector: - import FilePicker._ - - def apply( - initialFiles: List[File], - availableFilesStream: EventStream[List[File]] - )(selectionUpdates: Observer[Event]): HtmlElement = - val selectedFiles = Var[Set[File]](initialFiles.to(Set)) - val availableFiles = availableFilesStream.startWithNone - // Request the files to display - selectionUpdates.onNext(AvailableFilesRequested) - div( - cls( - "inline-block transform overflow-hidden rounded-lg bg-white text-left align-bottom shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:align-middle" - ), - role("dialog"), - customHtmlAttr("aria.modal", BooleanAsTrueFalseStringCodec)(true), - aria.labelledBy("modal-headline"), - div( - cls("bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"), - div( - cls("sm:flex sm:items-start"), - div( - cls("mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"), - h3( - cls("text-lg font-medium leading-6 text-gray-900"), - idAttr("modal-headline"), - "Výběr souborů" - ) - ) - ), - child <-- availableFiles - .split(_ => ())((_, _, af) => FileTable(af, selectedFiles)) - .map(_.getOrElse(Loading)) - ), - div( - cls("bg-gray-50 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6"), - span( - cls("flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto"), - button( - typ("button"), - cls( - "focus:shadow-outline-green inline-flex w-full justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-base font-medium leading-6 text-white shadow-sm transition duration-150 ease-in-out hover:bg-indigo-500 focus:border-indigo-700 focus:outline-none sm:text-sm sm:leading-5" - ), - "Potvrdit", - composeEvents(onClick)( - _.sample(selectedFiles) - .map(SelectionUpdated(_)) - ) --> selectionUpdates - ) - ), - span( - cls("mt-3 flex w-full rounded-md shadow-sm sm:mt-0 sm:w-auto"), - button( - typ("button"), - cls( - "focus:shadow-outline-blue inline-flex w-full justify-center rounded-md border border-gray-300 bg-white px-4 py-2 text-base font-medium leading-6 text-gray-700 shadow-sm transition duration-150 ease-in-out hover:text-gray-500 focus:border-blue-300 focus:outline-none sm:text-sm sm:leading-5" - ), - "Zrušit", - onClick.mapTo(SelectionCancelled) --> selectionUpdates - ) - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala deleted file mode 100644 index aa11b2b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala +++ /dev/null @@ -1,91 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import works.iterative.ui.components.tailwind.Icons - -def FileTable( - files: Signal[List[File]], - selectedFiles: Var[Set[File]] -): HtmlElement = - val scope = customHtmlAttr("scope", StringAsIsCodec) - - def headerRow: HtmlElement = - val col = scope("col") - val baseM: Modifier[HtmlElement] = Seq(cls("px-6 py-3"), col) - val textH = cls( - "text-left text-xs font-medium text-gray-500 uppercase tracking-wider" - ) - tr( - th(baseM, span(cls("sr-only"), "Vybrat")), - th(baseM, textH, "Název"), - th(baseM, cls("relative"), span(cls("sr-only"), "Otevřít")) - ) - - def tableRow( - f: File, - idx: Int, - selected: Boolean - )(toggleSelection: Observer[Unit]): HtmlElement = - val baseC = cls("px-6 py-4 whitespace-nowrap text-sm") - tr( - cls(if idx % 2 == 0 then "bg-gray-50" else "bg-white"), - td( - cls("font-medium cursor-pointer"), - onClick.mapTo(()) --> toggleSelection, - cls(if selected then "text-green-900" else "text-gray-200"), - Icons.outline.`check-circle`().amend(svg.cls := "mx-auto"), - span(cls("sr-only"), if selected then "Vybráno" else "Nevybráno") - ), - td( - baseC, - cls("font-medium text-gray-900"), - f.name, - onClick.mapTo(()) --> toggleSelection - ), - td( - baseC, - cls("text-right font-medium"), - a( - href(f.url), - target("_blank"), - cls( - "flex items-center space-x-4 text-indigo-600 hover:text-indigo-900" - ), - Icons.outline.`external-link`(), - "Otevřít" - ) - ) - ) - - div( - cls("flex flex-col"), - div(cls("overflow-x-auto sm:-mx-6 lg:-mx-8")), - div( - cls("py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8"), - div( - cls("shadow overflow-hidden border-b border-gray-200 sm:rounded-lg"), - table( - cls("min-w-full divide-y divide-gray-200"), - thead( - cls("bg-gray-50"), - headerRow - ), - tbody( - children <-- files - .map(_.zipWithIndex) - .combineWithFn(selectedFiles)((f, sel) => - f.map((file, idx) => - val active = sel.contains(file) - tableRow(file, idx, active)( - selectedFiles.writer - .contramap(_ => if active then sel - file else sel + file) - ) - ) - ) - ) - ) - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala deleted file mode 100644 index 0fc8796..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala +++ /dev/null @@ -1,32 +0,0 @@ -package works.iterative.ui.components.tailwind - -import CustomAttrs.ariaHidden -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -// TODO: render icon or picture based on img signal -class Avatar($avatarImg: Signal[Option[String]]): - inline def avatarPlaceholder(size: Int): HtmlElement = - div( - cls := s"rounded-full text-indigo-200 bg-indigo-500 h-${size} w-${size} flex items-center justify-center", - Icons.outline.user(size - 2) - ) - - inline def avatarImage(size: Int): Signal[HtmlElement] = - $avatarImg.split(_ => ())((_, _, $url) => - img( - cls := s"w-$size h-$size rounded-full", - src <-- $url, - alt := "" - ) - ).map(_.getOrElse(avatarPlaceholder(size))) - - inline def avatar(size: Int): HtmlElement = - div( - cls := "relative", - child <-- avatarImage(size), - span( - cls := "absolute inset-0 shadow-inner rounded-full", - ariaHidden := true - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala deleted file mode 100644 index bcbed88..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala +++ /dev/null @@ -1,77 +0,0 @@ -package works.iterative.ui.components.tailwind - -enum ColorWeight(value: String): - inline def toCSS: String = value - // To be used for black and white until - // we support better mechanism via extension methods - case w__ extends ColorWeight("") - case w50 extends ColorWeight("50") - case w100 extends ColorWeight("100") - case w200 extends ColorWeight("200") - case w300 extends ColorWeight("300") - case w400 extends ColorWeight("400") - case w500 extends ColorWeight("500") - case w600 extends ColorWeight("600") - case w700 extends ColorWeight("700") - case w800 extends ColorWeight("800") - case w900 extends ColorWeight("900") - -enum Color(name: String): - import ColorWeight._ - - inline def toCSSNoColorWeight(prefix: String): String = - s"${prefix}-${name}" - inline def toCSSWithColorWeight( - prefix: String, - weight: ColorWeight - ): String = - s"${prefix}-${name}-${weight.toCSS}" - inline def toCSS(prefix: String)(weight: ColorWeight): String = - weight match { - case `w__` => toCSSNoColorWeight(prefix) - case _ => toCSSWithColorWeight(prefix, weight) - } - inline def bg: ColorWeight => String = toCSS("bg")(_) - inline def text: ColorWeight => String = toCSS("text")(_) - inline def decoration: ColorWeight => String = toCSS("decoration")(_) - inline def border: ColorWeight => String = toCSS("border")(_) - inline def outline: ColorWeight => String = toCSS("outline")(_) - inline def divide: ColorWeight => String = toCSS("divide")(_) - inline def ring: ColorWeight => String = toCSS("ring")(_) - inline def ringOffset: ColorWeight => String = toCSS("ring-offset")(_) - inline def shadow: ColorWeight => String = toCSS("shadow")(_) - inline def accent: ColorWeight => String = toCSS("accent")(_) - - // TODO: change the "stupid" methods to extension methods - // that will keep the invariants in comments lower - case current extends Color("current") - case inherit extends Color("inherit") - // Not present in for all methods - case transp extends Color("transparent") - // Seen in accent, not preset otherwise - case auto extends Color("auto") - // Black and white do not have weight - case black extends Color("black") - case white extends Color("white") - case slate extends Color("slate") - case gray extends Color("gray") - case zinc extends Color("zinc") - case neutral extends Color("neutral") - case stone extends Color("stone") - case red extends Color("red") - case orange extends Color("orange") - case amber extends Color("amber") - case yellow extends Color("yellow") - case lime extends Color("lime") - case green extends Color("green") - case emerald extends Color("emerald") - case teal extends Color("teal") - case cyan extends Color("cyan") - case sky extends Color("sky") - case blue extends Color("blue") - case indigo extends Color("indigo") - case violet extends Color("violet") - case purple extends Color("purple") - case fuchsia extends Color("fuchsia") - case pink extends Color("pink") - case rose extends Color("rose") diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala deleted file mode 100644 index 6fb453c..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -object CustomAttrs { - // Made a pull request to add aria-current to scala-dom-types, remove after - val ariaCurrent = customHtmlAttr("aria-current", StringAsIsCodec) - val ariaHidden = customHtmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - - val datetime = customHtmlAttr("datetime", StringAsIsCodec) - - object svg { - import com.raquo.laminar.api.L.svg.{*, given} - val ariaHidden = - customSvgAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - } -} diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala deleted file mode 100644 index 6cda48b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala +++ /dev/null @@ -1,28 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -object Display: - - enum Breakpoint: - case sm, md, lg, xl, `2xl` - - enum DisplayClass: - case block, `inline-block`, `inline`, flex, `inline-flex`, table, - `inline-table`, `table-caption` - - object ShowUpFrom: - inline def apply( - br: Breakpoint, - dc: DisplayClass = DisplayClass.block - ): HtmlElement = - div( - cls := "hidden", - cls := s"${br}:${dc}" - ) - - object HideUpTo: - inline def apply(br: Breakpoint): HtmlElement = - div( - cls := s"${br}:hidden" - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala deleted file mode 100644 index 4a8b065..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala +++ /dev/null @@ -1,270 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec -import com.raquo.domtypes.generic.defs.attrs.AriaAttrs -import com.raquo.laminar.api.L.svg.{*, given} -import com.raquo.laminar.builders.SvgBuilders -import com.raquo.laminar.keys.ReactiveSvgAttr - -// TODO: fix sizes, colors, hover and stuff, normalize and amend on call site -object Icons: - object aria: - inline def hidden = CustomAttrs.svg.ariaHidden - - object outline: - val defaultSize: Int = 6 - - inline def bell(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" - ) - ) - - inline def `check-circle`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" - ) - ) - - inline def `document-add`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M9 13h6m-3-3v6m5 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" - ) - ) - - inline def `external-link`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" - ) - ) - - inline def menu(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M4 6h16M4 12h16M4 18h16" - ) - ) - - inline def `status-offline`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M18.364 5.636a9 9 0 010 12.728m0 0l-2.829-2.829m2.829 2.829L21 21M15.536 8.464a5 5 0 010 7.072m0 0l-2.829-2.829m-4.243 2.829a4.978 4.978 0 01-1.414-2.83m-1.414 5.658a9 9 0 01-2.167-9.238m7.824 2.167a1 1 0 111.414 1.414m-1.414-1.414L3 3m8.293 8.293l1.414 1.414" - ) - ) - - inline def user(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" - ) - ) - - inline def x(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M6 18L18 6M6 6l12 12" - ) - ) - - end outline - - object solid: - val defaultSize: Int = 5 - - inline def users(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - d := "M9 6a3 3 0 11-6 0 3 3 0 016 0zM17 6a3 3 0 11-6 0 3 3 0 016 0zM12.93 17c.046-.327.07-.66.07-1a6.97 6.97 0 00-1.5-4.33A5 5 0 0119 16v1h-6.07zM6 11a5 5 0 015 5v1H1v-1a5 5 0 015-5z" - ) - ) - - inline def `location-marker`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M5.05 4.05a7 7 0 119.9 9.9L10 18.9l-4.95-4.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z", - clipRule := "evenodd" - ) - ) - - inline def calendar(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z", - clipRule := "evenodd" - ) - ) - - inline def `chevron-right`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z", - clipRule := "evenodd" - ) - ) - - inline def search(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z", - clipRule := "evenodd" - ) - ) - - inline def filter(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M3 3a1 1 0 011-1h12a1 1 0 011 1v3a1 1 0 01-.293.707L12 11.414V15a1 1 0 01-.293.707l-2 2A1 1 0 018 17v-5.586L3.293 6.707A1 1 0 013 6V3z", - clipRule := "evenodd" - ) - ) - - inline def `arrow-narrow-left`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M7.707 14.707a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 1.414L5.414 9H17a1 1 0 110 2H5.414l2.293 2.293a1 1 0 010 1.414z", - clipRule := "evenodd" - ) - ) - - inline def home(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - d := "M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z" - ) - ) - - inline def paperclip(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size} text-gray-400", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M8 4a3 3 0 00-3 3v4a5 5 0 0010 0V7a1 1 0 112 0v4a7 7 0 11-14 0V7a5 5 0 0110 0v4a3 3 0 11-6 0V7a1 1 0 012 0v4a1 1 0 102 0V7a3 3 0 00-3-3z", - clipRule := "evenodd" - ) - ) - - end solid -end Icons diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala deleted file mode 100644 index e7e6627..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.akka - -// Base class for all command-processing related exceptions from handlers -sealed abstract class CommandHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandNotAvailable[C, S](cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd není dostupný ve stavu $state" - ) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandRejected[C, S](reason: String, cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd byl ve stavu $state odmítnut s odůvodněním $reason" - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala deleted file mode 100644 index 72a5bf9..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.akka - -sealed abstract class EventHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -case class UnhandledEvent[Event, State](event: Event, state: State) - extends EventHandlerException( - s"Událost $event nastala ve stavu $state bez možnosti zpracování" - ) diff --git a/iw/bom.sc b/iw/bom.sc deleted file mode 100644 index e9266b8..0000000 --- a/iw/bom.sc +++ /dev/null @@ -1,227 +0,0 @@ -import mill._, scalalib._ - -object IWMaterials { - - object Versions { - val akka = "2.6.16" - val akkaHttp = "10.2.4" - val cats = "2.7.0" - val elastic4s = "7.12.2" - val http4s = "0.23.10" - val http4sPac4J = "4.0.0" - val laminar = "0.14.2" - val laminext = laminar - val logbackClassic = "1.2.10" - val pac4j = "5.2.0" - val play = "2.8.8" - val playJson = "2.9.2" - val scalaTest = "3.2.9" - val slick = "3.3.3" - val sttpClient = "3.5.0" - val tapir = "0.20.1" - val urlDsl = "0.4.0" - val waypoint = "0.5.0" - val zio = "2.0.0-RC2" - val zioConfig = "3.0.0-RC2" - val zioInteropCats = "3.3.0-RC2" - val zioJson = "0.3.0-RC3" - val zioLogging = "2.0.0-RC5" - val zioPrelude = "1.0.0-RC10" - val zioZMX = "0.0.11" - } - - object Deps extends AkkaLibs with SlickLibs { - import IWMaterials.{Versions => V} - - val zioOrg = "dev.zio" - - def zioLib(name: String, version: String): Dep = - ivy"$zioOrg::zio-$name::$version" - - lazy val zio: Dep = ivy"$zioOrg::zio:${V.zio}" - - lazy val zioTest: Dep = zioLib("test", V.zio) - lazy val zioTestSbt: Dep = zioLib("test", V.zio) - - lazy val zioConfig: Dep = zioLib("config", V.zioConfig) - lazy val zioConfigTypesafe: Dep = - zioLib("config-typesafe", V.zioConfig) - lazy val zioConfigMagnolia: Dep = - zioLib("config-magnolia", V.zioConfig) - - lazy val zioJson: Dep = zioLib("json", V.zioJson) - lazy val zioLogging: Dep = zioLib("logging", V.zioLogging) - lazy val zioLoggingSlf4j: Dep = - zioLib("logging-slf4j", V.zioLogging) - lazy val zioPrelude: Dep = zioLib("prelude", V.zioPrelude) - lazy val zioStreams: Dep = zioLib("streams", V.zio) - lazy val zioZMX: Dep = zioLib("zmx", V.zioZMX) - lazy val zioInteropCats: Dep = - zioLib("interop-cats", V.zioInteropCats) - - /* What is the equivalent? ZIOModule with prepared test config? - def useZIO(testConf: Configuration*): Agg[Dep] = Agg( - zio, - zioTest, - zioTestSbt, - testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework") - ) - */ - - /* - def useZIOAll(testConf: Configuration*): Seq[Def.Setting[_]] = - useZIO(testConf: _*) ++ Seq( - zioStreams, - zioConfig, - zioConfigTypesafe, - zioConfigMagnolia, - zioJson, - zioZMX, - zioLogging, - zioPrelude - ) - */ - - private val tapirOrg = "com.softwaremill.sttp.tapir" - def tapirLib(name: String): Dep = - ivy"${tapirOrg}::tapir-$name::${V.tapir}" - - lazy val tapirCore: Dep = tapirLib("core") - lazy val tapirZIO: Dep = tapirLib("zio") - lazy val tapirZIOJson: Dep = tapirLib("json-zio") - lazy val tapirSttpClient: Dep = tapirLib("sttp-client") - lazy val tapirCats: Dep = tapirLib("cats") - lazy val tapirZIOHttp4sServer: Dep = tapirLib("zio-http4s-server") - - private val sttpClientOrg = "com.softwaremill.sttp.client3" - def sttpClientLib(name: String): Dep = - ivy"${sttpClientOrg}::${name}:${V.sttpClient}" - - lazy val sttpClientCore: Dep = sttpClientLib("core") - - lazy val http4sBlazeServer: Dep = - ivy"org.http4s::http4s-blaze-server:${V.http4s}" - - lazy val http4sPac4J: Dep = - ivy"org.pac4j::http4s-pac4j:${V.http4sPac4J}" - lazy val pac4jOIDC: Dep = - ivy"org.pac4j:pac4j-oidc:${V.pac4j}" - - lazy val scalaTest: Dep = - ivy"org.scalatest::scalatest:${V.scalaTest}" - lazy val scalaTestPlusScalacheck: Dep = - ivy"org.scalatestplus::scalacheck-1-15:3.2.9.0" - lazy val playScalaTest: Dep = - ivy"org.scalatestplus.play::scalatestplus-play:5.1.0" - - private val playOrg = "com.typesafe.play" - lazy val playMailer: Dep = ivy"${playOrg}::play-mailer:8.0.1" - lazy val playServer: Dep = ivy"${playOrg}::play-server:${V.play}" - lazy val playAkkaServer: Dep = - ivy"${playOrg}::play-akka-http-server:${V.play}" - lazy val play: Dep = ivy"${playOrg}::play:${V.play}" - lazy val playAhcWs: Dep = ivy"${playOrg}::play-ahc-ws:${V.play}" - lazy val playJson: Dep = ivy"${playOrg}::play-json:${V.playJson}" - - private val elastic4sOrg = "com.sksamuel.elastic4s" - lazy val useElastic4S: Agg[Dep] = Agg( - ivy"${elastic4sOrg}::elastic4s-core:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-client-akka:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-http-streams:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-json-play:${V.elastic4s}" - ) - - lazy val laminar: Dep = ivy"com.raquo::laminar::${V.laminar}" - - private def laminext(name: String): Dep = - ivy"io.laminext::$name::${V.laminar}" - - lazy val laminextCore: Dep = laminext("core") - lazy val laminextTailwind: Dep = laminext("tailwind") - lazy val laminextFetch: Dep = laminext("fetch") - lazy val laminextValidationCore: Dep = laminext("validation-core") - lazy val laminextUI: Dep = laminext("ui") - - lazy val waypoint: Dep = - ivy"com.raquo::waypoint::${V.waypoint}" - - lazy val urlDsl: Dep = - ivy"be.doeraene::url-dsl::${V.urlDsl}" - - lazy val scalaJavaTime: Dep = - ivy"io.github.cquiroz::scala-java-time::2.3.0" - - lazy val scalaJavaLocales: Dep = - ivy"io.github.cquiroz::scala-java-locales::1.2.1" - - lazy val logbackClassic: Dep = - ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" - } - - trait AkkaLibs { - - self: SlickLibs => - - object akka { - val V = Versions.akka - val tOrg = "com.typesafe.akka" - val lOrg = "com.lightbend.akka" - - def akkaMod(name: String): Dep = - ivy"$tOrg::akka-$name:$V" - - lazy val actor: Dep = akkaMod("actor") - lazy val actorTyped: Dep = akkaMod("actor-typed") - lazy val stream: Dep = akkaMod("stream") - lazy val persistence: Dep = akkaMod("persistence-typed") - lazy val persistenceQuery: Dep = akkaMod("persistence-query") - lazy val persistenceJdbc: Dep = - ivy"$lOrg::akka-persistence-jdbc:5.0.4" - val persistenceTestKit: Dep = ivy"$tOrg::akka-persistence-testkit:$V" - - object http { - val V = Versions.akkaHttp - - lazy val http: Dep = ivy"$tOrg::akka-http:$V" - lazy val sprayJson: Dep = ivy"$tOrg::akka-http-spray-json:$V" - - } - - object projection { - val V = "1.2.2" - - lazy val core: Dep = - ivy"$lOrg::akka-projection-core:$V" - lazy val eventsourced: Dep = - ivy"$lOrg::akka-projection-eventsourced:$V" - lazy val slick: Dep = - ivy"$lOrg::akka-projection-slick:$V" - lazy val jdbc: Dep = - ivy"$lOrg::akka-projection-jdbc:$V" - } - - object profiles { - // TODO: deal with cross-version for Scala3 (for3Use2_13) - lazy val eventsourcedJdbcProjection: Agg[Dep] = Agg( - persistenceQuery, - projection.core, - projection.eventsourced, - projection.slick, - persistenceJdbc - ) ++ slick.default - } - } - } - - trait SlickLibs { - object slick { - val V = IWMaterials.Versions.slick - val org = "com.typesafe.slick" - - lazy val slick: Dep = ivy"$org::slick:$V" - lazy val hikaricp: Dep = ivy"$org::slick-hikaricp:$V" - lazy val default: Agg[Dep] = Agg(slick, hikaricp) - } - } - -} diff --git a/iw/build.sc b/iw/build.sc deleted file mode 100644 index fbbc3b8..0000000 --- a/iw/build.sc +++ /dev/null @@ -1,144 +0,0 @@ -import mill._, scalalib._, scalajslib._ - -import $file.bom - -object support { - val Deps = bom.IWMaterials.Deps - val Versions = bom.IWMaterials.Versions - - trait CommonModule extends ScalaModule { - def scalaVersion = "3.1.1" - def scalacOptions = Seq( - "-encoding", - "utf8", - "-deprecation", - "-explain-types", - "-explain", - "-feature", - "-language:experimental.macros", - "-language:higherKinds", - "-language:implicitConversions", - "-unchecked", - "-Xfatal-warnings", - "-Ykind-projector" - ) - } - - trait CommonJSModule extends CommonModule with ScalaJSModule { - def scalaJSVersion = "1.8.0" - } - - trait CrossPlatformModule extends Module { outer => - def ivyDeps: T[Agg[Dep]] = Agg[Dep]() - def jsDeps: T[Agg[Dep]] = Agg[Dep]() - def jvmDeps: T[Agg[Dep]] = Agg[Dep]() - def moduleDeps: Seq[Module] = Seq[Module]() - - trait PlatformModule extends CommonModule { - def platform: String - override def millSourcePath = outer.millSourcePath - override def ivyDeps = outer.ivyDeps() ++ (platform match { - case "js" => jsDeps() - case _ => jvmDeps() - }) - override def moduleDeps = outer.moduleDeps.collect { - case m: CrossPlatformModule => - platform match { - case "js" => m.js - case _ => m.jvm - } - case m: JavaModule => m - } - } - - trait JsModule extends PlatformModule with CommonJSModule { - def platform = "js" - } - - trait JvmModule extends PlatformModule { - def platform = "jvm" - } - - val js: JsModule - val jvm: JvmModule - } - - trait PureCrossModule extends CrossPlatformModule { - override object js extends JsModule - override object jvm extends JvmModule - } - - trait PureCrossSbtModule extends CrossPlatformModule { - override object js extends JsModule with SbtModule - override object jvm extends JvmModule with SbtModule - } - - trait FullCrossSbtModule extends CrossPlatformModule { - trait FullSources extends JavaModule { self: PlatformModule => - override def sources = T.sources( - millSourcePath / platform / "src" / "main" / "scala", - millSourcePath / "shared" / "src" / "main" / "scala" - ) - - override def resources = T.sources( - millSourcePath / platform / "src" / "main" / "resources", - millSourcePath / "shared" / "src" / "main" / "resources" - ) - } - - override object js extends JsModule with FullSources - override object jvm extends JvmModule with FullSources - } - -} - -import support._ - -object mongo extends CommonModule { - def ivyDeps = Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - ivy"org.mongodb.scala::mongo-scala-driver:4.2.3".withDottyCompat( - scalaVersion() - ) - ) -} - -object tapir extends FullCrossSbtModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson, Deps.zioJson) - def jvmDeps = Agg(Deps.tapirZIO, Deps.tapirZIOHttp4sServer) - def jsDeps = Agg(Deps.tapirSttpClient) -} - -object akkaPersistence extends CommonModule { - def millSourcePath = build.millSourcePath / "akka-persistence" - def ivyDeps = (Agg( - Deps.akka.persistenceQuery, - Deps.akka.persistenceJdbc, - Deps.akka.projection.core, - Deps.akka.projection.eventsourced, - Deps.akka.projection.slick - ) ++ Deps.slick.default).map(_.withDottyCompat(scalaVersion())) ++ Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - Deps.akka.persistence.withDottyCompat(scalaVersion()), - ivy"com.typesafe.akka::akka-cluster-sharding-typed:${Deps.akka.V}" - .withDottyCompat(scalaVersion()) - ) -} - -object ui extends CommonJSModule { - def ivyDeps = Agg( - Deps.zio, - Deps.laminar, - Deps.zioJson, - Deps.waypoint, - Deps.urlDsl, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) -} diff --git a/iw/domain.sc b/iw/domain.sc deleted file mode 100644 index b09c56b..0000000 --- a/iw/domain.sc +++ /dev/null @@ -1,78 +0,0 @@ -import mill._, scalalib._ - -import $file.{build => ff}, ff.support._ - -trait DomainModule extends Module { - // General extra model deps - def modelModules: Seq[Module] = Seq.empty - // General extra codecs deps - def codecsModules: Seq[Module] = Seq.empty - // General extra endpoints deps - def endpointsModules: Seq[Module] = Seq.empty - - // Implementation deps for repo - def repoModules: Seq[JavaModule] = Seq.empty - - object shared extends Module { - object model extends PureCrossModule { - def moduleDeps = modelModules - def ivyDeps = Agg(Deps.zioPrelude) - } - object codecs extends PureCrossModule { - def moduleDeps = Seq(model) ++ codecsModules - def ivyDeps = Agg(Deps.zioJson) - } - } - - trait CommonProjects extends Module { - object model extends PureCrossModule { - def ivyDeps = Agg(Deps.zioPrelude) - def moduleDeps = Seq(shared.model) - } - object codecs extends PureCrossModule { - def moduleDeps = - Seq(model, shared.model, shared.codecs, ff.tapir) - } - object endpoints extends PureCrossModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson) - def moduleDeps = Seq(model, codecs) ++ endpointsModules - } - object client extends CommonJSModule { - def moduleDeps = Seq(endpoints.js) - } - object components extends CommonJSModule { - def ivyDeps = Agg( - Deps.laminar, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) - def moduleDeps = Seq(model.js, codecs.js, client, ff.ui) - } - } - - object query extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(repo, query.endpoints.jvm) - } - object repo extends CommonModule { - def ivyDeps = Agg(Deps.zio) - def moduleDeps = Seq(model.jvm, codecs.jvm) ++ repoModules - } - object projection extends CommonModule { - def moduleDeps = Seq(repo, ff.akkaPersistence) - } - } - - object command extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(entity, command.endpoints.jvm) - } - object entity extends CommonModule { - def moduleDeps = Seq(model.jvm, codecs.jvm, ff.akkaPersistence) - } - } -} diff --git a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala b/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala deleted file mode 100644 index 0fa3493..0000000 --- a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala +++ /dev/null @@ -1,53 +0,0 @@ -package works.iterative.mongo - -import zio.* -import zio.json.* -import zio.config.* -import org.mongodb.scala.* -import org.mongodb.scala.model.Filters.* -import org.bson.json.JsonObject -import org.mongodb.scala.model.ReplaceOptions -import org.mongodb.scala.bson.conversions.Bson - -case class MongoConfig(uri: String) - -object MongoConfig: - val configDesc = - import ConfigDescriptor.* - nested("MONGO")(string("URI").default("mongodb://localhost:27017")) - .to[MongoConfig] - val fromEnv = ZConfig.fromSystemEnv(configDesc) - -extension (m: MongoClient.type) - def layer: RLayer[MongoConfig, MongoClient] = - ZIO - .serviceWithZIO[MongoConfig](c => Task.attempt(MongoClient(c.uri))) - .toLayer - -class MongoJsonRepository[Elem, Key, Criteria]( - collection: MongoCollection[JsonObject], - toFilter: Criteria => Bson, - idFilter: Elem => (String, Key) -)(using JsonCodec[Elem]) { - def matching(criteria: Criteria): Task[Seq[Elem]] = - val filter = toFilter(criteria) - val query = collection.find(filter) - - for - result <- ZIO.fromFuture(_ => query.toFuture) - proof <- ZIO.collect(result)(j => - ZIO.fromOption(j.getJson.fromJson[Elem].toOption) - ) - yield proof - - def put(elem: Elem): Task[Unit] = - Task.async(cb => - collection - .replaceOne( - equal.tupled(idFilter(elem)), - JsonObject(elem.toJson), - ReplaceOptions().upsert(true) - ) - .subscribe(_ => cb(Task.unit), t => cb(Task.fail(t))) - ) -} diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala deleted file mode 100644 index 68d7196..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala +++ /dev/null @@ -1,12 +0,0 @@ -package works.iterative.tapir - -import sttp.model.Uri - -opaque type BaseUri = Option[Uri] - -object BaseUri: - - def apply(optU: Option[Uri]): BaseUri = optU - def apply(u: Uri): BaseUri = Some(u) - - extension (v: BaseUri) def toUri: Option[Uri] = v diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 653bc51..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,22 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.client.sttp.SttpClientInterpreter -import sttp.tapir.PublicEndpoint -import sttp.tapir.client.sttp.WebSocketToPipe -import scala.concurrent.Future -import sttp.client3.SttpBackend -import sttp.capabilities.WebSockets - -trait CustomTapirPlatformSpecific extends SttpClientInterpreter: - self: CustomTapir => - - type Backend = SttpBackend[Future, WebSockets] - - def makeClient[I, E, O]( - endpoint: PublicEndpoint[I, E, O, Any] - )(using - baseUri: BaseUri, - backend: Backend, - wsToPipe: WebSocketToPipe[Any] - ): I => Future[O] = - toClientThrowErrors(endpoint, baseUri.toUri, backend) diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 2545fbd..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,5 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.ztapir.ZTapir - -trait CustomTapirPlatformSpecific extends ZTapir diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala deleted file mode 100644 index 55e4ca1..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala +++ /dev/null @@ -1,7 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.server.http4s.ztapir.ZHttp4sServerInterpreter - -trait Http4sCustomTapir[Env] - extends CustomTapir - with ZHttp4sServerInterpreter[Env] diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala deleted file mode 100644 index dd52e9b..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala +++ /dev/null @@ -1,14 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.Tapir -import sttp.tapir.json.zio.TapirJsonZio -import sttp.tapir.TapirAliases - -trait CustomTapir - extends Tapir - with TapirJsonZio - with TapirAliases - with CustomTapirPlatformSpecific: - given Schema[ServerError] = Schema.derived - -object CustomTapir extends CustomTapir diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala deleted file mode 100644 index 715591d..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.tapir - -import zio.json.* - -sealed trait ServerError -case class InternalServerError(msg: String) extends ServerError -object InternalServerError: - def fromThrowable(t: Throwable): ServerError = InternalServerError( - t.getMessage - ) - -object ServerError: - given JsonCodec[ServerError] = DeriveJsonCodec.gen diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/File.scala deleted file mode 100644 index 09f6c07..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala +++ /dev/null @@ -1,3 +0,0 @@ -package works.iterative.services.files - -case class File(url: String, name: String) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala deleted file mode 100644 index 46633a5..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala +++ /dev/null @@ -1,25 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.Renderable - -given Renderable[File] with - extension (m: File) - def toHtml: HtmlElement = - li( - cls("pl-3 pr-4 py-3 flex items-center justify-between text-sm"), - div( - cls("w-0 flex-1 flex items-center"), - Icons.solid - .paperclip() - .amend(svg.cls := "flex-shrink-0 text-gray-400"), - span(cls("ml-2 flex-1 w-0 truncate"), m.name) - ), - a( - href(m.url), - cls("font-medium text-indigo-600 hover:text-indigo-500"), - "Otevřít" - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala deleted file mode 100644 index ac59db8..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import io.laminext.syntax.core.{*, given} - -def FileList(files: List[File]): HtmlElement = - ul( - role("list"), - cls("border border-gray-200 rounded-md divide-y divide-gray-200"), - files.map(_.toHtml) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala deleted file mode 100644 index 2e8f690..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala +++ /dev/null @@ -1,81 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import io.laminext.syntax.core.{*, given} - -object FilePicker: - sealed trait Event - sealed trait DoneEvent extends Event - case class SelectionUpdated(files: Set[File]) extends DoneEvent - case object SelectionCancelled extends DoneEvent - case object AvailableFilesRequested extends Event - - def apply( - currentFiles: Signal[List[File]], - availableFilesStream: EventStream[List[File]] - )(selectionUpdates: Observer[Event]): HtmlElement = - val (updatesStream, updatesObserver) = EventStream.withObserver[Event] - val selectorOpen = Var[Boolean](false) - - // This sequence tricks browser into displaying modal content centered - // Inspired by modal in headless ui playground - // https://github.com/tailwindlabs/headlessui/blob/fdd26297953080d5ec905dda0bf5ec9607897d86/packages/playground-react/pages/transitions/component-examples/modal.tsx#L78-L79 - inline def browserCenteringModalTrick: Modifier[HtmlElement] = - Seq[Modifier[HtmlElement]]( - span(cls("hidden sm:inline-block sm:h-screen sm:align-middle")), - "​" // Zero width space - ) - - inline def overlay: Modifier[HtmlElement] = - // Page overlay - /* TODO: transition - enter="ease-out duration-300" - enterFrom="opacity-0" - enterTo="opacity-100" - leave="ease-in duration-200" - leaveFrom="opacity-100" - leaveTo="opacity-0" - */ - div( - div( - onClick.mapTo(false) --> selectorOpen.writer, - cls("fixed inset-0 transition-opacity"), - div(cls("absolute inset-0 bg-gray-500 opacity-75")) - ) - ) - - inline def modalSelector: HtmlElement = - div( - cls("fixed inset-0 z-10 overflow-y-auto"), - div( - cls("text-center sm:block sm:p-0"), - overlay, - browserCenteringModalTrick, - child <-- currentFiles.map( - FileSelector(_, availableFilesStream)(updatesObserver) - ) - ) - ) - - div( - updatesStream --> selectionUpdates, - updatesStream.collect { case _: DoneEvent => - false - } --> selectorOpen.writer, - child.maybe <-- selectorOpen.signal.map(isOpen => - if isOpen then Some(modalSelector) else None - ), - div( - cls("flex flex-col space-y-5"), - child.maybe <-- currentFiles.map(files => - if files.isEmpty then None else Some(FileList(files)) - ), - button( - tpe := "button", - cls := "bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500", - "Zvolit soubory", - onClick.mapTo(true) --> selectorOpen.writer - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala deleted file mode 100644 index b43e07a..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala +++ /dev/null @@ -1,74 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.Loading -import io.laminext.syntax.core.{*, given} -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -object FileSelector: - import FilePicker._ - - def apply( - initialFiles: List[File], - availableFilesStream: EventStream[List[File]] - )(selectionUpdates: Observer[Event]): HtmlElement = - val selectedFiles = Var[Set[File]](initialFiles.to(Set)) - val availableFiles = availableFilesStream.startWithNone - // Request the files to display - selectionUpdates.onNext(AvailableFilesRequested) - div( - cls( - "inline-block transform overflow-hidden rounded-lg bg-white text-left align-bottom shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:align-middle" - ), - role("dialog"), - customHtmlAttr("aria.modal", BooleanAsTrueFalseStringCodec)(true), - aria.labelledBy("modal-headline"), - div( - cls("bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"), - div( - cls("sm:flex sm:items-start"), - div( - cls("mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"), - h3( - cls("text-lg font-medium leading-6 text-gray-900"), - idAttr("modal-headline"), - "Výběr souborů" - ) - ) - ), - child <-- availableFiles - .split(_ => ())((_, _, af) => FileTable(af, selectedFiles)) - .map(_.getOrElse(Loading)) - ), - div( - cls("bg-gray-50 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6"), - span( - cls("flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto"), - button( - typ("button"), - cls( - "focus:shadow-outline-green inline-flex w-full justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-base font-medium leading-6 text-white shadow-sm transition duration-150 ease-in-out hover:bg-indigo-500 focus:border-indigo-700 focus:outline-none sm:text-sm sm:leading-5" - ), - "Potvrdit", - composeEvents(onClick)( - _.sample(selectedFiles) - .map(SelectionUpdated(_)) - ) --> selectionUpdates - ) - ), - span( - cls("mt-3 flex w-full rounded-md shadow-sm sm:mt-0 sm:w-auto"), - button( - typ("button"), - cls( - "focus:shadow-outline-blue inline-flex w-full justify-center rounded-md border border-gray-300 bg-white px-4 py-2 text-base font-medium leading-6 text-gray-700 shadow-sm transition duration-150 ease-in-out hover:text-gray-500 focus:border-blue-300 focus:outline-none sm:text-sm sm:leading-5" - ), - "Zrušit", - onClick.mapTo(SelectionCancelled) --> selectionUpdates - ) - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala deleted file mode 100644 index aa11b2b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala +++ /dev/null @@ -1,91 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import works.iterative.ui.components.tailwind.Icons - -def FileTable( - files: Signal[List[File]], - selectedFiles: Var[Set[File]] -): HtmlElement = - val scope = customHtmlAttr("scope", StringAsIsCodec) - - def headerRow: HtmlElement = - val col = scope("col") - val baseM: Modifier[HtmlElement] = Seq(cls("px-6 py-3"), col) - val textH = cls( - "text-left text-xs font-medium text-gray-500 uppercase tracking-wider" - ) - tr( - th(baseM, span(cls("sr-only"), "Vybrat")), - th(baseM, textH, "Název"), - th(baseM, cls("relative"), span(cls("sr-only"), "Otevřít")) - ) - - def tableRow( - f: File, - idx: Int, - selected: Boolean - )(toggleSelection: Observer[Unit]): HtmlElement = - val baseC = cls("px-6 py-4 whitespace-nowrap text-sm") - tr( - cls(if idx % 2 == 0 then "bg-gray-50" else "bg-white"), - td( - cls("font-medium cursor-pointer"), - onClick.mapTo(()) --> toggleSelection, - cls(if selected then "text-green-900" else "text-gray-200"), - Icons.outline.`check-circle`().amend(svg.cls := "mx-auto"), - span(cls("sr-only"), if selected then "Vybráno" else "Nevybráno") - ), - td( - baseC, - cls("font-medium text-gray-900"), - f.name, - onClick.mapTo(()) --> toggleSelection - ), - td( - baseC, - cls("text-right font-medium"), - a( - href(f.url), - target("_blank"), - cls( - "flex items-center space-x-4 text-indigo-600 hover:text-indigo-900" - ), - Icons.outline.`external-link`(), - "Otevřít" - ) - ) - ) - - div( - cls("flex flex-col"), - div(cls("overflow-x-auto sm:-mx-6 lg:-mx-8")), - div( - cls("py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8"), - div( - cls("shadow overflow-hidden border-b border-gray-200 sm:rounded-lg"), - table( - cls("min-w-full divide-y divide-gray-200"), - thead( - cls("bg-gray-50"), - headerRow - ), - tbody( - children <-- files - .map(_.zipWithIndex) - .combineWithFn(selectedFiles)((f, sel) => - f.map((file, idx) => - val active = sel.contains(file) - tableRow(file, idx, active)( - selectedFiles.writer - .contramap(_ => if active then sel - file else sel + file) - ) - ) - ) - ) - ) - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala deleted file mode 100644 index 0fc8796..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala +++ /dev/null @@ -1,32 +0,0 @@ -package works.iterative.ui.components.tailwind - -import CustomAttrs.ariaHidden -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -// TODO: render icon or picture based on img signal -class Avatar($avatarImg: Signal[Option[String]]): - inline def avatarPlaceholder(size: Int): HtmlElement = - div( - cls := s"rounded-full text-indigo-200 bg-indigo-500 h-${size} w-${size} flex items-center justify-center", - Icons.outline.user(size - 2) - ) - - inline def avatarImage(size: Int): Signal[HtmlElement] = - $avatarImg.split(_ => ())((_, _, $url) => - img( - cls := s"w-$size h-$size rounded-full", - src <-- $url, - alt := "" - ) - ).map(_.getOrElse(avatarPlaceholder(size))) - - inline def avatar(size: Int): HtmlElement = - div( - cls := "relative", - child <-- avatarImage(size), - span( - cls := "absolute inset-0 shadow-inner rounded-full", - ariaHidden := true - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala deleted file mode 100644 index bcbed88..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala +++ /dev/null @@ -1,77 +0,0 @@ -package works.iterative.ui.components.tailwind - -enum ColorWeight(value: String): - inline def toCSS: String = value - // To be used for black and white until - // we support better mechanism via extension methods - case w__ extends ColorWeight("") - case w50 extends ColorWeight("50") - case w100 extends ColorWeight("100") - case w200 extends ColorWeight("200") - case w300 extends ColorWeight("300") - case w400 extends ColorWeight("400") - case w500 extends ColorWeight("500") - case w600 extends ColorWeight("600") - case w700 extends ColorWeight("700") - case w800 extends ColorWeight("800") - case w900 extends ColorWeight("900") - -enum Color(name: String): - import ColorWeight._ - - inline def toCSSNoColorWeight(prefix: String): String = - s"${prefix}-${name}" - inline def toCSSWithColorWeight( - prefix: String, - weight: ColorWeight - ): String = - s"${prefix}-${name}-${weight.toCSS}" - inline def toCSS(prefix: String)(weight: ColorWeight): String = - weight match { - case `w__` => toCSSNoColorWeight(prefix) - case _ => toCSSWithColorWeight(prefix, weight) - } - inline def bg: ColorWeight => String = toCSS("bg")(_) - inline def text: ColorWeight => String = toCSS("text")(_) - inline def decoration: ColorWeight => String = toCSS("decoration")(_) - inline def border: ColorWeight => String = toCSS("border")(_) - inline def outline: ColorWeight => String = toCSS("outline")(_) - inline def divide: ColorWeight => String = toCSS("divide")(_) - inline def ring: ColorWeight => String = toCSS("ring")(_) - inline def ringOffset: ColorWeight => String = toCSS("ring-offset")(_) - inline def shadow: ColorWeight => String = toCSS("shadow")(_) - inline def accent: ColorWeight => String = toCSS("accent")(_) - - // TODO: change the "stupid" methods to extension methods - // that will keep the invariants in comments lower - case current extends Color("current") - case inherit extends Color("inherit") - // Not present in for all methods - case transp extends Color("transparent") - // Seen in accent, not preset otherwise - case auto extends Color("auto") - // Black and white do not have weight - case black extends Color("black") - case white extends Color("white") - case slate extends Color("slate") - case gray extends Color("gray") - case zinc extends Color("zinc") - case neutral extends Color("neutral") - case stone extends Color("stone") - case red extends Color("red") - case orange extends Color("orange") - case amber extends Color("amber") - case yellow extends Color("yellow") - case lime extends Color("lime") - case green extends Color("green") - case emerald extends Color("emerald") - case teal extends Color("teal") - case cyan extends Color("cyan") - case sky extends Color("sky") - case blue extends Color("blue") - case indigo extends Color("indigo") - case violet extends Color("violet") - case purple extends Color("purple") - case fuchsia extends Color("fuchsia") - case pink extends Color("pink") - case rose extends Color("rose") diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala deleted file mode 100644 index 6fb453c..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -object CustomAttrs { - // Made a pull request to add aria-current to scala-dom-types, remove after - val ariaCurrent = customHtmlAttr("aria-current", StringAsIsCodec) - val ariaHidden = customHtmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - - val datetime = customHtmlAttr("datetime", StringAsIsCodec) - - object svg { - import com.raquo.laminar.api.L.svg.{*, given} - val ariaHidden = - customSvgAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - } -} diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala deleted file mode 100644 index 6cda48b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala +++ /dev/null @@ -1,28 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -object Display: - - enum Breakpoint: - case sm, md, lg, xl, `2xl` - - enum DisplayClass: - case block, `inline-block`, `inline`, flex, `inline-flex`, table, - `inline-table`, `table-caption` - - object ShowUpFrom: - inline def apply( - br: Breakpoint, - dc: DisplayClass = DisplayClass.block - ): HtmlElement = - div( - cls := "hidden", - cls := s"${br}:${dc}" - ) - - object HideUpTo: - inline def apply(br: Breakpoint): HtmlElement = - div( - cls := s"${br}:hidden" - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala deleted file mode 100644 index 4a8b065..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala +++ /dev/null @@ -1,270 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec -import com.raquo.domtypes.generic.defs.attrs.AriaAttrs -import com.raquo.laminar.api.L.svg.{*, given} -import com.raquo.laminar.builders.SvgBuilders -import com.raquo.laminar.keys.ReactiveSvgAttr - -// TODO: fix sizes, colors, hover and stuff, normalize and amend on call site -object Icons: - object aria: - inline def hidden = CustomAttrs.svg.ariaHidden - - object outline: - val defaultSize: Int = 6 - - inline def bell(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" - ) - ) - - inline def `check-circle`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" - ) - ) - - inline def `document-add`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M9 13h6m-3-3v6m5 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" - ) - ) - - inline def `external-link`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" - ) - ) - - inline def menu(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M4 6h16M4 12h16M4 18h16" - ) - ) - - inline def `status-offline`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M18.364 5.636a9 9 0 010 12.728m0 0l-2.829-2.829m2.829 2.829L21 21M15.536 8.464a5 5 0 010 7.072m0 0l-2.829-2.829m-4.243 2.829a4.978 4.978 0 01-1.414-2.83m-1.414 5.658a9 9 0 01-2.167-9.238m7.824 2.167a1 1 0 111.414 1.414m-1.414-1.414L3 3m8.293 8.293l1.414 1.414" - ) - ) - - inline def user(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" - ) - ) - - inline def x(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M6 18L18 6M6 6l12 12" - ) - ) - - end outline - - object solid: - val defaultSize: Int = 5 - - inline def users(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - d := "M9 6a3 3 0 11-6 0 3 3 0 016 0zM17 6a3 3 0 11-6 0 3 3 0 016 0zM12.93 17c.046-.327.07-.66.07-1a6.97 6.97 0 00-1.5-4.33A5 5 0 0119 16v1h-6.07zM6 11a5 5 0 015 5v1H1v-1a5 5 0 015-5z" - ) - ) - - inline def `location-marker`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M5.05 4.05a7 7 0 119.9 9.9L10 18.9l-4.95-4.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z", - clipRule := "evenodd" - ) - ) - - inline def calendar(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z", - clipRule := "evenodd" - ) - ) - - inline def `chevron-right`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z", - clipRule := "evenodd" - ) - ) - - inline def search(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z", - clipRule := "evenodd" - ) - ) - - inline def filter(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M3 3a1 1 0 011-1h12a1 1 0 011 1v3a1 1 0 01-.293.707L12 11.414V15a1 1 0 01-.293.707l-2 2A1 1 0 018 17v-5.586L3.293 6.707A1 1 0 013 6V3z", - clipRule := "evenodd" - ) - ) - - inline def `arrow-narrow-left`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M7.707 14.707a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 1.414L5.414 9H17a1 1 0 110 2H5.414l2.293 2.293a1 1 0 010 1.414z", - clipRule := "evenodd" - ) - ) - - inline def home(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - d := "M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z" - ) - ) - - inline def paperclip(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size} text-gray-400", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M8 4a3 3 0 00-3 3v4a5 5 0 0010 0V7a1 1 0 112 0v4a7 7 0 11-14 0V7a5 5 0 0110 0v4a3 3 0 11-6 0V7a1 1 0 012 0v4a1 1 0 102 0V7a3 3 0 00-3-3z", - clipRule := "evenodd" - ) - ) - - end solid -end Icons diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/LinkSupport.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/LinkSupport.scala deleted file mode 100644 index 88f0b9a..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/LinkSupport.scala +++ /dev/null @@ -1,12 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.jsdom.defs.events.TypedTargetMouseEvent - -object LinkSupport: - - extension [El <: org.scalajs.dom.EventTarget]( - ep: EventProcessor[TypedTargetMouseEvent[El], TypedTargetMouseEvent[El]] - ) - def noKeyMod = - ep.filter(ev => !(ev.ctrlKey || ev.metaKey || ev.shiftKey || ev.altKey)) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala deleted file mode 100644 index e7e6627..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.akka - -// Base class for all command-processing related exceptions from handlers -sealed abstract class CommandHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandNotAvailable[C, S](cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd není dostupný ve stavu $state" - ) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandRejected[C, S](reason: String, cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd byl ve stavu $state odmítnut s odůvodněním $reason" - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala deleted file mode 100644 index 72a5bf9..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.akka - -sealed abstract class EventHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -case class UnhandledEvent[Event, State](event: Event, state: State) - extends EventHandlerException( - s"Událost $event nastala ve stavu $state bez možnosti zpracování" - ) diff --git a/iw/bom.sc b/iw/bom.sc deleted file mode 100644 index e9266b8..0000000 --- a/iw/bom.sc +++ /dev/null @@ -1,227 +0,0 @@ -import mill._, scalalib._ - -object IWMaterials { - - object Versions { - val akka = "2.6.16" - val akkaHttp = "10.2.4" - val cats = "2.7.0" - val elastic4s = "7.12.2" - val http4s = "0.23.10" - val http4sPac4J = "4.0.0" - val laminar = "0.14.2" - val laminext = laminar - val logbackClassic = "1.2.10" - val pac4j = "5.2.0" - val play = "2.8.8" - val playJson = "2.9.2" - val scalaTest = "3.2.9" - val slick = "3.3.3" - val sttpClient = "3.5.0" - val tapir = "0.20.1" - val urlDsl = "0.4.0" - val waypoint = "0.5.0" - val zio = "2.0.0-RC2" - val zioConfig = "3.0.0-RC2" - val zioInteropCats = "3.3.0-RC2" - val zioJson = "0.3.0-RC3" - val zioLogging = "2.0.0-RC5" - val zioPrelude = "1.0.0-RC10" - val zioZMX = "0.0.11" - } - - object Deps extends AkkaLibs with SlickLibs { - import IWMaterials.{Versions => V} - - val zioOrg = "dev.zio" - - def zioLib(name: String, version: String): Dep = - ivy"$zioOrg::zio-$name::$version" - - lazy val zio: Dep = ivy"$zioOrg::zio:${V.zio}" - - lazy val zioTest: Dep = zioLib("test", V.zio) - lazy val zioTestSbt: Dep = zioLib("test", V.zio) - - lazy val zioConfig: Dep = zioLib("config", V.zioConfig) - lazy val zioConfigTypesafe: Dep = - zioLib("config-typesafe", V.zioConfig) - lazy val zioConfigMagnolia: Dep = - zioLib("config-magnolia", V.zioConfig) - - lazy val zioJson: Dep = zioLib("json", V.zioJson) - lazy val zioLogging: Dep = zioLib("logging", V.zioLogging) - lazy val zioLoggingSlf4j: Dep = - zioLib("logging-slf4j", V.zioLogging) - lazy val zioPrelude: Dep = zioLib("prelude", V.zioPrelude) - lazy val zioStreams: Dep = zioLib("streams", V.zio) - lazy val zioZMX: Dep = zioLib("zmx", V.zioZMX) - lazy val zioInteropCats: Dep = - zioLib("interop-cats", V.zioInteropCats) - - /* What is the equivalent? ZIOModule with prepared test config? - def useZIO(testConf: Configuration*): Agg[Dep] = Agg( - zio, - zioTest, - zioTestSbt, - testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework") - ) - */ - - /* - def useZIOAll(testConf: Configuration*): Seq[Def.Setting[_]] = - useZIO(testConf: _*) ++ Seq( - zioStreams, - zioConfig, - zioConfigTypesafe, - zioConfigMagnolia, - zioJson, - zioZMX, - zioLogging, - zioPrelude - ) - */ - - private val tapirOrg = "com.softwaremill.sttp.tapir" - def tapirLib(name: String): Dep = - ivy"${tapirOrg}::tapir-$name::${V.tapir}" - - lazy val tapirCore: Dep = tapirLib("core") - lazy val tapirZIO: Dep = tapirLib("zio") - lazy val tapirZIOJson: Dep = tapirLib("json-zio") - lazy val tapirSttpClient: Dep = tapirLib("sttp-client") - lazy val tapirCats: Dep = tapirLib("cats") - lazy val tapirZIOHttp4sServer: Dep = tapirLib("zio-http4s-server") - - private val sttpClientOrg = "com.softwaremill.sttp.client3" - def sttpClientLib(name: String): Dep = - ivy"${sttpClientOrg}::${name}:${V.sttpClient}" - - lazy val sttpClientCore: Dep = sttpClientLib("core") - - lazy val http4sBlazeServer: Dep = - ivy"org.http4s::http4s-blaze-server:${V.http4s}" - - lazy val http4sPac4J: Dep = - ivy"org.pac4j::http4s-pac4j:${V.http4sPac4J}" - lazy val pac4jOIDC: Dep = - ivy"org.pac4j:pac4j-oidc:${V.pac4j}" - - lazy val scalaTest: Dep = - ivy"org.scalatest::scalatest:${V.scalaTest}" - lazy val scalaTestPlusScalacheck: Dep = - ivy"org.scalatestplus::scalacheck-1-15:3.2.9.0" - lazy val playScalaTest: Dep = - ivy"org.scalatestplus.play::scalatestplus-play:5.1.0" - - private val playOrg = "com.typesafe.play" - lazy val playMailer: Dep = ivy"${playOrg}::play-mailer:8.0.1" - lazy val playServer: Dep = ivy"${playOrg}::play-server:${V.play}" - lazy val playAkkaServer: Dep = - ivy"${playOrg}::play-akka-http-server:${V.play}" - lazy val play: Dep = ivy"${playOrg}::play:${V.play}" - lazy val playAhcWs: Dep = ivy"${playOrg}::play-ahc-ws:${V.play}" - lazy val playJson: Dep = ivy"${playOrg}::play-json:${V.playJson}" - - private val elastic4sOrg = "com.sksamuel.elastic4s" - lazy val useElastic4S: Agg[Dep] = Agg( - ivy"${elastic4sOrg}::elastic4s-core:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-client-akka:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-http-streams:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-json-play:${V.elastic4s}" - ) - - lazy val laminar: Dep = ivy"com.raquo::laminar::${V.laminar}" - - private def laminext(name: String): Dep = - ivy"io.laminext::$name::${V.laminar}" - - lazy val laminextCore: Dep = laminext("core") - lazy val laminextTailwind: Dep = laminext("tailwind") - lazy val laminextFetch: Dep = laminext("fetch") - lazy val laminextValidationCore: Dep = laminext("validation-core") - lazy val laminextUI: Dep = laminext("ui") - - lazy val waypoint: Dep = - ivy"com.raquo::waypoint::${V.waypoint}" - - lazy val urlDsl: Dep = - ivy"be.doeraene::url-dsl::${V.urlDsl}" - - lazy val scalaJavaTime: Dep = - ivy"io.github.cquiroz::scala-java-time::2.3.0" - - lazy val scalaJavaLocales: Dep = - ivy"io.github.cquiroz::scala-java-locales::1.2.1" - - lazy val logbackClassic: Dep = - ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" - } - - trait AkkaLibs { - - self: SlickLibs => - - object akka { - val V = Versions.akka - val tOrg = "com.typesafe.akka" - val lOrg = "com.lightbend.akka" - - def akkaMod(name: String): Dep = - ivy"$tOrg::akka-$name:$V" - - lazy val actor: Dep = akkaMod("actor") - lazy val actorTyped: Dep = akkaMod("actor-typed") - lazy val stream: Dep = akkaMod("stream") - lazy val persistence: Dep = akkaMod("persistence-typed") - lazy val persistenceQuery: Dep = akkaMod("persistence-query") - lazy val persistenceJdbc: Dep = - ivy"$lOrg::akka-persistence-jdbc:5.0.4" - val persistenceTestKit: Dep = ivy"$tOrg::akka-persistence-testkit:$V" - - object http { - val V = Versions.akkaHttp - - lazy val http: Dep = ivy"$tOrg::akka-http:$V" - lazy val sprayJson: Dep = ivy"$tOrg::akka-http-spray-json:$V" - - } - - object projection { - val V = "1.2.2" - - lazy val core: Dep = - ivy"$lOrg::akka-projection-core:$V" - lazy val eventsourced: Dep = - ivy"$lOrg::akka-projection-eventsourced:$V" - lazy val slick: Dep = - ivy"$lOrg::akka-projection-slick:$V" - lazy val jdbc: Dep = - ivy"$lOrg::akka-projection-jdbc:$V" - } - - object profiles { - // TODO: deal with cross-version for Scala3 (for3Use2_13) - lazy val eventsourcedJdbcProjection: Agg[Dep] = Agg( - persistenceQuery, - projection.core, - projection.eventsourced, - projection.slick, - persistenceJdbc - ) ++ slick.default - } - } - } - - trait SlickLibs { - object slick { - val V = IWMaterials.Versions.slick - val org = "com.typesafe.slick" - - lazy val slick: Dep = ivy"$org::slick:$V" - lazy val hikaricp: Dep = ivy"$org::slick-hikaricp:$V" - lazy val default: Agg[Dep] = Agg(slick, hikaricp) - } - } - -} diff --git a/iw/build.sc b/iw/build.sc deleted file mode 100644 index fbbc3b8..0000000 --- a/iw/build.sc +++ /dev/null @@ -1,144 +0,0 @@ -import mill._, scalalib._, scalajslib._ - -import $file.bom - -object support { - val Deps = bom.IWMaterials.Deps - val Versions = bom.IWMaterials.Versions - - trait CommonModule extends ScalaModule { - def scalaVersion = "3.1.1" - def scalacOptions = Seq( - "-encoding", - "utf8", - "-deprecation", - "-explain-types", - "-explain", - "-feature", - "-language:experimental.macros", - "-language:higherKinds", - "-language:implicitConversions", - "-unchecked", - "-Xfatal-warnings", - "-Ykind-projector" - ) - } - - trait CommonJSModule extends CommonModule with ScalaJSModule { - def scalaJSVersion = "1.8.0" - } - - trait CrossPlatformModule extends Module { outer => - def ivyDeps: T[Agg[Dep]] = Agg[Dep]() - def jsDeps: T[Agg[Dep]] = Agg[Dep]() - def jvmDeps: T[Agg[Dep]] = Agg[Dep]() - def moduleDeps: Seq[Module] = Seq[Module]() - - trait PlatformModule extends CommonModule { - def platform: String - override def millSourcePath = outer.millSourcePath - override def ivyDeps = outer.ivyDeps() ++ (platform match { - case "js" => jsDeps() - case _ => jvmDeps() - }) - override def moduleDeps = outer.moduleDeps.collect { - case m: CrossPlatformModule => - platform match { - case "js" => m.js - case _ => m.jvm - } - case m: JavaModule => m - } - } - - trait JsModule extends PlatformModule with CommonJSModule { - def platform = "js" - } - - trait JvmModule extends PlatformModule { - def platform = "jvm" - } - - val js: JsModule - val jvm: JvmModule - } - - trait PureCrossModule extends CrossPlatformModule { - override object js extends JsModule - override object jvm extends JvmModule - } - - trait PureCrossSbtModule extends CrossPlatformModule { - override object js extends JsModule with SbtModule - override object jvm extends JvmModule with SbtModule - } - - trait FullCrossSbtModule extends CrossPlatformModule { - trait FullSources extends JavaModule { self: PlatformModule => - override def sources = T.sources( - millSourcePath / platform / "src" / "main" / "scala", - millSourcePath / "shared" / "src" / "main" / "scala" - ) - - override def resources = T.sources( - millSourcePath / platform / "src" / "main" / "resources", - millSourcePath / "shared" / "src" / "main" / "resources" - ) - } - - override object js extends JsModule with FullSources - override object jvm extends JvmModule with FullSources - } - -} - -import support._ - -object mongo extends CommonModule { - def ivyDeps = Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - ivy"org.mongodb.scala::mongo-scala-driver:4.2.3".withDottyCompat( - scalaVersion() - ) - ) -} - -object tapir extends FullCrossSbtModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson, Deps.zioJson) - def jvmDeps = Agg(Deps.tapirZIO, Deps.tapirZIOHttp4sServer) - def jsDeps = Agg(Deps.tapirSttpClient) -} - -object akkaPersistence extends CommonModule { - def millSourcePath = build.millSourcePath / "akka-persistence" - def ivyDeps = (Agg( - Deps.akka.persistenceQuery, - Deps.akka.persistenceJdbc, - Deps.akka.projection.core, - Deps.akka.projection.eventsourced, - Deps.akka.projection.slick - ) ++ Deps.slick.default).map(_.withDottyCompat(scalaVersion())) ++ Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - Deps.akka.persistence.withDottyCompat(scalaVersion()), - ivy"com.typesafe.akka::akka-cluster-sharding-typed:${Deps.akka.V}" - .withDottyCompat(scalaVersion()) - ) -} - -object ui extends CommonJSModule { - def ivyDeps = Agg( - Deps.zio, - Deps.laminar, - Deps.zioJson, - Deps.waypoint, - Deps.urlDsl, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) -} diff --git a/iw/domain.sc b/iw/domain.sc deleted file mode 100644 index b09c56b..0000000 --- a/iw/domain.sc +++ /dev/null @@ -1,78 +0,0 @@ -import mill._, scalalib._ - -import $file.{build => ff}, ff.support._ - -trait DomainModule extends Module { - // General extra model deps - def modelModules: Seq[Module] = Seq.empty - // General extra codecs deps - def codecsModules: Seq[Module] = Seq.empty - // General extra endpoints deps - def endpointsModules: Seq[Module] = Seq.empty - - // Implementation deps for repo - def repoModules: Seq[JavaModule] = Seq.empty - - object shared extends Module { - object model extends PureCrossModule { - def moduleDeps = modelModules - def ivyDeps = Agg(Deps.zioPrelude) - } - object codecs extends PureCrossModule { - def moduleDeps = Seq(model) ++ codecsModules - def ivyDeps = Agg(Deps.zioJson) - } - } - - trait CommonProjects extends Module { - object model extends PureCrossModule { - def ivyDeps = Agg(Deps.zioPrelude) - def moduleDeps = Seq(shared.model) - } - object codecs extends PureCrossModule { - def moduleDeps = - Seq(model, shared.model, shared.codecs, ff.tapir) - } - object endpoints extends PureCrossModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson) - def moduleDeps = Seq(model, codecs) ++ endpointsModules - } - object client extends CommonJSModule { - def moduleDeps = Seq(endpoints.js) - } - object components extends CommonJSModule { - def ivyDeps = Agg( - Deps.laminar, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) - def moduleDeps = Seq(model.js, codecs.js, client, ff.ui) - } - } - - object query extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(repo, query.endpoints.jvm) - } - object repo extends CommonModule { - def ivyDeps = Agg(Deps.zio) - def moduleDeps = Seq(model.jvm, codecs.jvm) ++ repoModules - } - object projection extends CommonModule { - def moduleDeps = Seq(repo, ff.akkaPersistence) - } - } - - object command extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(entity, command.endpoints.jvm) - } - object entity extends CommonModule { - def moduleDeps = Seq(model.jvm, codecs.jvm, ff.akkaPersistence) - } - } -} diff --git a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala b/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala deleted file mode 100644 index 0fa3493..0000000 --- a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala +++ /dev/null @@ -1,53 +0,0 @@ -package works.iterative.mongo - -import zio.* -import zio.json.* -import zio.config.* -import org.mongodb.scala.* -import org.mongodb.scala.model.Filters.* -import org.bson.json.JsonObject -import org.mongodb.scala.model.ReplaceOptions -import org.mongodb.scala.bson.conversions.Bson - -case class MongoConfig(uri: String) - -object MongoConfig: - val configDesc = - import ConfigDescriptor.* - nested("MONGO")(string("URI").default("mongodb://localhost:27017")) - .to[MongoConfig] - val fromEnv = ZConfig.fromSystemEnv(configDesc) - -extension (m: MongoClient.type) - def layer: RLayer[MongoConfig, MongoClient] = - ZIO - .serviceWithZIO[MongoConfig](c => Task.attempt(MongoClient(c.uri))) - .toLayer - -class MongoJsonRepository[Elem, Key, Criteria]( - collection: MongoCollection[JsonObject], - toFilter: Criteria => Bson, - idFilter: Elem => (String, Key) -)(using JsonCodec[Elem]) { - def matching(criteria: Criteria): Task[Seq[Elem]] = - val filter = toFilter(criteria) - val query = collection.find(filter) - - for - result <- ZIO.fromFuture(_ => query.toFuture) - proof <- ZIO.collect(result)(j => - ZIO.fromOption(j.getJson.fromJson[Elem].toOption) - ) - yield proof - - def put(elem: Elem): Task[Unit] = - Task.async(cb => - collection - .replaceOne( - equal.tupled(idFilter(elem)), - JsonObject(elem.toJson), - ReplaceOptions().upsert(true) - ) - .subscribe(_ => cb(Task.unit), t => cb(Task.fail(t))) - ) -} diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala deleted file mode 100644 index 68d7196..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala +++ /dev/null @@ -1,12 +0,0 @@ -package works.iterative.tapir - -import sttp.model.Uri - -opaque type BaseUri = Option[Uri] - -object BaseUri: - - def apply(optU: Option[Uri]): BaseUri = optU - def apply(u: Uri): BaseUri = Some(u) - - extension (v: BaseUri) def toUri: Option[Uri] = v diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 653bc51..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,22 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.client.sttp.SttpClientInterpreter -import sttp.tapir.PublicEndpoint -import sttp.tapir.client.sttp.WebSocketToPipe -import scala.concurrent.Future -import sttp.client3.SttpBackend -import sttp.capabilities.WebSockets - -trait CustomTapirPlatformSpecific extends SttpClientInterpreter: - self: CustomTapir => - - type Backend = SttpBackend[Future, WebSockets] - - def makeClient[I, E, O]( - endpoint: PublicEndpoint[I, E, O, Any] - )(using - baseUri: BaseUri, - backend: Backend, - wsToPipe: WebSocketToPipe[Any] - ): I => Future[O] = - toClientThrowErrors(endpoint, baseUri.toUri, backend) diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 2545fbd..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,5 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.ztapir.ZTapir - -trait CustomTapirPlatformSpecific extends ZTapir diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala deleted file mode 100644 index 55e4ca1..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala +++ /dev/null @@ -1,7 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.server.http4s.ztapir.ZHttp4sServerInterpreter - -trait Http4sCustomTapir[Env] - extends CustomTapir - with ZHttp4sServerInterpreter[Env] diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala deleted file mode 100644 index dd52e9b..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala +++ /dev/null @@ -1,14 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.Tapir -import sttp.tapir.json.zio.TapirJsonZio -import sttp.tapir.TapirAliases - -trait CustomTapir - extends Tapir - with TapirJsonZio - with TapirAliases - with CustomTapirPlatformSpecific: - given Schema[ServerError] = Schema.derived - -object CustomTapir extends CustomTapir diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala deleted file mode 100644 index 715591d..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.tapir - -import zio.json.* - -sealed trait ServerError -case class InternalServerError(msg: String) extends ServerError -object InternalServerError: - def fromThrowable(t: Throwable): ServerError = InternalServerError( - t.getMessage - ) - -object ServerError: - given JsonCodec[ServerError] = DeriveJsonCodec.gen diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/File.scala deleted file mode 100644 index 09f6c07..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala +++ /dev/null @@ -1,3 +0,0 @@ -package works.iterative.services.files - -case class File(url: String, name: String) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala deleted file mode 100644 index 46633a5..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala +++ /dev/null @@ -1,25 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.Renderable - -given Renderable[File] with - extension (m: File) - def toHtml: HtmlElement = - li( - cls("pl-3 pr-4 py-3 flex items-center justify-between text-sm"), - div( - cls("w-0 flex-1 flex items-center"), - Icons.solid - .paperclip() - .amend(svg.cls := "flex-shrink-0 text-gray-400"), - span(cls("ml-2 flex-1 w-0 truncate"), m.name) - ), - a( - href(m.url), - cls("font-medium text-indigo-600 hover:text-indigo-500"), - "Otevřít" - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala deleted file mode 100644 index ac59db8..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import io.laminext.syntax.core.{*, given} - -def FileList(files: List[File]): HtmlElement = - ul( - role("list"), - cls("border border-gray-200 rounded-md divide-y divide-gray-200"), - files.map(_.toHtml) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala deleted file mode 100644 index 2e8f690..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala +++ /dev/null @@ -1,81 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import io.laminext.syntax.core.{*, given} - -object FilePicker: - sealed trait Event - sealed trait DoneEvent extends Event - case class SelectionUpdated(files: Set[File]) extends DoneEvent - case object SelectionCancelled extends DoneEvent - case object AvailableFilesRequested extends Event - - def apply( - currentFiles: Signal[List[File]], - availableFilesStream: EventStream[List[File]] - )(selectionUpdates: Observer[Event]): HtmlElement = - val (updatesStream, updatesObserver) = EventStream.withObserver[Event] - val selectorOpen = Var[Boolean](false) - - // This sequence tricks browser into displaying modal content centered - // Inspired by modal in headless ui playground - // https://github.com/tailwindlabs/headlessui/blob/fdd26297953080d5ec905dda0bf5ec9607897d86/packages/playground-react/pages/transitions/component-examples/modal.tsx#L78-L79 - inline def browserCenteringModalTrick: Modifier[HtmlElement] = - Seq[Modifier[HtmlElement]]( - span(cls("hidden sm:inline-block sm:h-screen sm:align-middle")), - "​" // Zero width space - ) - - inline def overlay: Modifier[HtmlElement] = - // Page overlay - /* TODO: transition - enter="ease-out duration-300" - enterFrom="opacity-0" - enterTo="opacity-100" - leave="ease-in duration-200" - leaveFrom="opacity-100" - leaveTo="opacity-0" - */ - div( - div( - onClick.mapTo(false) --> selectorOpen.writer, - cls("fixed inset-0 transition-opacity"), - div(cls("absolute inset-0 bg-gray-500 opacity-75")) - ) - ) - - inline def modalSelector: HtmlElement = - div( - cls("fixed inset-0 z-10 overflow-y-auto"), - div( - cls("text-center sm:block sm:p-0"), - overlay, - browserCenteringModalTrick, - child <-- currentFiles.map( - FileSelector(_, availableFilesStream)(updatesObserver) - ) - ) - ) - - div( - updatesStream --> selectionUpdates, - updatesStream.collect { case _: DoneEvent => - false - } --> selectorOpen.writer, - child.maybe <-- selectorOpen.signal.map(isOpen => - if isOpen then Some(modalSelector) else None - ), - div( - cls("flex flex-col space-y-5"), - child.maybe <-- currentFiles.map(files => - if files.isEmpty then None else Some(FileList(files)) - ), - button( - tpe := "button", - cls := "bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500", - "Zvolit soubory", - onClick.mapTo(true) --> selectorOpen.writer - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala deleted file mode 100644 index b43e07a..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala +++ /dev/null @@ -1,74 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.Loading -import io.laminext.syntax.core.{*, given} -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -object FileSelector: - import FilePicker._ - - def apply( - initialFiles: List[File], - availableFilesStream: EventStream[List[File]] - )(selectionUpdates: Observer[Event]): HtmlElement = - val selectedFiles = Var[Set[File]](initialFiles.to(Set)) - val availableFiles = availableFilesStream.startWithNone - // Request the files to display - selectionUpdates.onNext(AvailableFilesRequested) - div( - cls( - "inline-block transform overflow-hidden rounded-lg bg-white text-left align-bottom shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:align-middle" - ), - role("dialog"), - customHtmlAttr("aria.modal", BooleanAsTrueFalseStringCodec)(true), - aria.labelledBy("modal-headline"), - div( - cls("bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"), - div( - cls("sm:flex sm:items-start"), - div( - cls("mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"), - h3( - cls("text-lg font-medium leading-6 text-gray-900"), - idAttr("modal-headline"), - "Výběr souborů" - ) - ) - ), - child <-- availableFiles - .split(_ => ())((_, _, af) => FileTable(af, selectedFiles)) - .map(_.getOrElse(Loading)) - ), - div( - cls("bg-gray-50 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6"), - span( - cls("flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto"), - button( - typ("button"), - cls( - "focus:shadow-outline-green inline-flex w-full justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-base font-medium leading-6 text-white shadow-sm transition duration-150 ease-in-out hover:bg-indigo-500 focus:border-indigo-700 focus:outline-none sm:text-sm sm:leading-5" - ), - "Potvrdit", - composeEvents(onClick)( - _.sample(selectedFiles) - .map(SelectionUpdated(_)) - ) --> selectionUpdates - ) - ), - span( - cls("mt-3 flex w-full rounded-md shadow-sm sm:mt-0 sm:w-auto"), - button( - typ("button"), - cls( - "focus:shadow-outline-blue inline-flex w-full justify-center rounded-md border border-gray-300 bg-white px-4 py-2 text-base font-medium leading-6 text-gray-700 shadow-sm transition duration-150 ease-in-out hover:text-gray-500 focus:border-blue-300 focus:outline-none sm:text-sm sm:leading-5" - ), - "Zrušit", - onClick.mapTo(SelectionCancelled) --> selectionUpdates - ) - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala deleted file mode 100644 index aa11b2b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala +++ /dev/null @@ -1,91 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import works.iterative.ui.components.tailwind.Icons - -def FileTable( - files: Signal[List[File]], - selectedFiles: Var[Set[File]] -): HtmlElement = - val scope = customHtmlAttr("scope", StringAsIsCodec) - - def headerRow: HtmlElement = - val col = scope("col") - val baseM: Modifier[HtmlElement] = Seq(cls("px-6 py-3"), col) - val textH = cls( - "text-left text-xs font-medium text-gray-500 uppercase tracking-wider" - ) - tr( - th(baseM, span(cls("sr-only"), "Vybrat")), - th(baseM, textH, "Název"), - th(baseM, cls("relative"), span(cls("sr-only"), "Otevřít")) - ) - - def tableRow( - f: File, - idx: Int, - selected: Boolean - )(toggleSelection: Observer[Unit]): HtmlElement = - val baseC = cls("px-6 py-4 whitespace-nowrap text-sm") - tr( - cls(if idx % 2 == 0 then "bg-gray-50" else "bg-white"), - td( - cls("font-medium cursor-pointer"), - onClick.mapTo(()) --> toggleSelection, - cls(if selected then "text-green-900" else "text-gray-200"), - Icons.outline.`check-circle`().amend(svg.cls := "mx-auto"), - span(cls("sr-only"), if selected then "Vybráno" else "Nevybráno") - ), - td( - baseC, - cls("font-medium text-gray-900"), - f.name, - onClick.mapTo(()) --> toggleSelection - ), - td( - baseC, - cls("text-right font-medium"), - a( - href(f.url), - target("_blank"), - cls( - "flex items-center space-x-4 text-indigo-600 hover:text-indigo-900" - ), - Icons.outline.`external-link`(), - "Otevřít" - ) - ) - ) - - div( - cls("flex flex-col"), - div(cls("overflow-x-auto sm:-mx-6 lg:-mx-8")), - div( - cls("py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8"), - div( - cls("shadow overflow-hidden border-b border-gray-200 sm:rounded-lg"), - table( - cls("min-w-full divide-y divide-gray-200"), - thead( - cls("bg-gray-50"), - headerRow - ), - tbody( - children <-- files - .map(_.zipWithIndex) - .combineWithFn(selectedFiles)((f, sel) => - f.map((file, idx) => - val active = sel.contains(file) - tableRow(file, idx, active)( - selectedFiles.writer - .contramap(_ => if active then sel - file else sel + file) - ) - ) - ) - ) - ) - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala deleted file mode 100644 index 0fc8796..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala +++ /dev/null @@ -1,32 +0,0 @@ -package works.iterative.ui.components.tailwind - -import CustomAttrs.ariaHidden -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -// TODO: render icon or picture based on img signal -class Avatar($avatarImg: Signal[Option[String]]): - inline def avatarPlaceholder(size: Int): HtmlElement = - div( - cls := s"rounded-full text-indigo-200 bg-indigo-500 h-${size} w-${size} flex items-center justify-center", - Icons.outline.user(size - 2) - ) - - inline def avatarImage(size: Int): Signal[HtmlElement] = - $avatarImg.split(_ => ())((_, _, $url) => - img( - cls := s"w-$size h-$size rounded-full", - src <-- $url, - alt := "" - ) - ).map(_.getOrElse(avatarPlaceholder(size))) - - inline def avatar(size: Int): HtmlElement = - div( - cls := "relative", - child <-- avatarImage(size), - span( - cls := "absolute inset-0 shadow-inner rounded-full", - ariaHidden := true - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala deleted file mode 100644 index bcbed88..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala +++ /dev/null @@ -1,77 +0,0 @@ -package works.iterative.ui.components.tailwind - -enum ColorWeight(value: String): - inline def toCSS: String = value - // To be used for black and white until - // we support better mechanism via extension methods - case w__ extends ColorWeight("") - case w50 extends ColorWeight("50") - case w100 extends ColorWeight("100") - case w200 extends ColorWeight("200") - case w300 extends ColorWeight("300") - case w400 extends ColorWeight("400") - case w500 extends ColorWeight("500") - case w600 extends ColorWeight("600") - case w700 extends ColorWeight("700") - case w800 extends ColorWeight("800") - case w900 extends ColorWeight("900") - -enum Color(name: String): - import ColorWeight._ - - inline def toCSSNoColorWeight(prefix: String): String = - s"${prefix}-${name}" - inline def toCSSWithColorWeight( - prefix: String, - weight: ColorWeight - ): String = - s"${prefix}-${name}-${weight.toCSS}" - inline def toCSS(prefix: String)(weight: ColorWeight): String = - weight match { - case `w__` => toCSSNoColorWeight(prefix) - case _ => toCSSWithColorWeight(prefix, weight) - } - inline def bg: ColorWeight => String = toCSS("bg")(_) - inline def text: ColorWeight => String = toCSS("text")(_) - inline def decoration: ColorWeight => String = toCSS("decoration")(_) - inline def border: ColorWeight => String = toCSS("border")(_) - inline def outline: ColorWeight => String = toCSS("outline")(_) - inline def divide: ColorWeight => String = toCSS("divide")(_) - inline def ring: ColorWeight => String = toCSS("ring")(_) - inline def ringOffset: ColorWeight => String = toCSS("ring-offset")(_) - inline def shadow: ColorWeight => String = toCSS("shadow")(_) - inline def accent: ColorWeight => String = toCSS("accent")(_) - - // TODO: change the "stupid" methods to extension methods - // that will keep the invariants in comments lower - case current extends Color("current") - case inherit extends Color("inherit") - // Not present in for all methods - case transp extends Color("transparent") - // Seen in accent, not preset otherwise - case auto extends Color("auto") - // Black and white do not have weight - case black extends Color("black") - case white extends Color("white") - case slate extends Color("slate") - case gray extends Color("gray") - case zinc extends Color("zinc") - case neutral extends Color("neutral") - case stone extends Color("stone") - case red extends Color("red") - case orange extends Color("orange") - case amber extends Color("amber") - case yellow extends Color("yellow") - case lime extends Color("lime") - case green extends Color("green") - case emerald extends Color("emerald") - case teal extends Color("teal") - case cyan extends Color("cyan") - case sky extends Color("sky") - case blue extends Color("blue") - case indigo extends Color("indigo") - case violet extends Color("violet") - case purple extends Color("purple") - case fuchsia extends Color("fuchsia") - case pink extends Color("pink") - case rose extends Color("rose") diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala deleted file mode 100644 index 6fb453c..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -object CustomAttrs { - // Made a pull request to add aria-current to scala-dom-types, remove after - val ariaCurrent = customHtmlAttr("aria-current", StringAsIsCodec) - val ariaHidden = customHtmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - - val datetime = customHtmlAttr("datetime", StringAsIsCodec) - - object svg { - import com.raquo.laminar.api.L.svg.{*, given} - val ariaHidden = - customSvgAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - } -} diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala deleted file mode 100644 index 6cda48b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala +++ /dev/null @@ -1,28 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -object Display: - - enum Breakpoint: - case sm, md, lg, xl, `2xl` - - enum DisplayClass: - case block, `inline-block`, `inline`, flex, `inline-flex`, table, - `inline-table`, `table-caption` - - object ShowUpFrom: - inline def apply( - br: Breakpoint, - dc: DisplayClass = DisplayClass.block - ): HtmlElement = - div( - cls := "hidden", - cls := s"${br}:${dc}" - ) - - object HideUpTo: - inline def apply(br: Breakpoint): HtmlElement = - div( - cls := s"${br}:hidden" - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala deleted file mode 100644 index 4a8b065..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala +++ /dev/null @@ -1,270 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec -import com.raquo.domtypes.generic.defs.attrs.AriaAttrs -import com.raquo.laminar.api.L.svg.{*, given} -import com.raquo.laminar.builders.SvgBuilders -import com.raquo.laminar.keys.ReactiveSvgAttr - -// TODO: fix sizes, colors, hover and stuff, normalize and amend on call site -object Icons: - object aria: - inline def hidden = CustomAttrs.svg.ariaHidden - - object outline: - val defaultSize: Int = 6 - - inline def bell(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" - ) - ) - - inline def `check-circle`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" - ) - ) - - inline def `document-add`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M9 13h6m-3-3v6m5 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" - ) - ) - - inline def `external-link`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" - ) - ) - - inline def menu(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M4 6h16M4 12h16M4 18h16" - ) - ) - - inline def `status-offline`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M18.364 5.636a9 9 0 010 12.728m0 0l-2.829-2.829m2.829 2.829L21 21M15.536 8.464a5 5 0 010 7.072m0 0l-2.829-2.829m-4.243 2.829a4.978 4.978 0 01-1.414-2.83m-1.414 5.658a9 9 0 01-2.167-9.238m7.824 2.167a1 1 0 111.414 1.414m-1.414-1.414L3 3m8.293 8.293l1.414 1.414" - ) - ) - - inline def user(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" - ) - ) - - inline def x(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M6 18L18 6M6 6l12 12" - ) - ) - - end outline - - object solid: - val defaultSize: Int = 5 - - inline def users(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - d := "M9 6a3 3 0 11-6 0 3 3 0 016 0zM17 6a3 3 0 11-6 0 3 3 0 016 0zM12.93 17c.046-.327.07-.66.07-1a6.97 6.97 0 00-1.5-4.33A5 5 0 0119 16v1h-6.07zM6 11a5 5 0 015 5v1H1v-1a5 5 0 015-5z" - ) - ) - - inline def `location-marker`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M5.05 4.05a7 7 0 119.9 9.9L10 18.9l-4.95-4.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z", - clipRule := "evenodd" - ) - ) - - inline def calendar(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z", - clipRule := "evenodd" - ) - ) - - inline def `chevron-right`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z", - clipRule := "evenodd" - ) - ) - - inline def search(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z", - clipRule := "evenodd" - ) - ) - - inline def filter(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M3 3a1 1 0 011-1h12a1 1 0 011 1v3a1 1 0 01-.293.707L12 11.414V15a1 1 0 01-.293.707l-2 2A1 1 0 018 17v-5.586L3.293 6.707A1 1 0 013 6V3z", - clipRule := "evenodd" - ) - ) - - inline def `arrow-narrow-left`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M7.707 14.707a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 1.414L5.414 9H17a1 1 0 110 2H5.414l2.293 2.293a1 1 0 010 1.414z", - clipRule := "evenodd" - ) - ) - - inline def home(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - d := "M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z" - ) - ) - - inline def paperclip(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size} text-gray-400", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M8 4a3 3 0 00-3 3v4a5 5 0 0010 0V7a1 1 0 112 0v4a7 7 0 11-14 0V7a5 5 0 0110 0v4a3 3 0 11-6 0V7a1 1 0 012 0v4a1 1 0 102 0V7a3 3 0 00-3-3z", - clipRule := "evenodd" - ) - ) - - end solid -end Icons diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/LinkSupport.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/LinkSupport.scala deleted file mode 100644 index 88f0b9a..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/LinkSupport.scala +++ /dev/null @@ -1,12 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.jsdom.defs.events.TypedTargetMouseEvent - -object LinkSupport: - - extension [El <: org.scalajs.dom.EventTarget]( - ep: EventProcessor[TypedTargetMouseEvent[El], TypedTargetMouseEvent[El]] - ) - def noKeyMod = - ep.filter(ev => !(ev.ctrlKey || ev.metaKey || ev.shiftKey || ev.altKey)) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Loader.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Loader.scala deleted file mode 100644 index 5399933..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Loader.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -// TODO: proper loader -def Loading = - div( - cls := "bg-gray-50 overflow-hidden rounded-lg", - div( - cls := "px-4 py-5 sm:p-6", - "Loading..." - ) - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala deleted file mode 100644 index e7e6627..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.akka - -// Base class for all command-processing related exceptions from handlers -sealed abstract class CommandHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandNotAvailable[C, S](cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd není dostupný ve stavu $state" - ) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandRejected[C, S](reason: String, cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd byl ve stavu $state odmítnut s odůvodněním $reason" - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala deleted file mode 100644 index 72a5bf9..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.akka - -sealed abstract class EventHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -case class UnhandledEvent[Event, State](event: Event, state: State) - extends EventHandlerException( - s"Událost $event nastala ve stavu $state bez možnosti zpracování" - ) diff --git a/iw/bom.sc b/iw/bom.sc deleted file mode 100644 index e9266b8..0000000 --- a/iw/bom.sc +++ /dev/null @@ -1,227 +0,0 @@ -import mill._, scalalib._ - -object IWMaterials { - - object Versions { - val akka = "2.6.16" - val akkaHttp = "10.2.4" - val cats = "2.7.0" - val elastic4s = "7.12.2" - val http4s = "0.23.10" - val http4sPac4J = "4.0.0" - val laminar = "0.14.2" - val laminext = laminar - val logbackClassic = "1.2.10" - val pac4j = "5.2.0" - val play = "2.8.8" - val playJson = "2.9.2" - val scalaTest = "3.2.9" - val slick = "3.3.3" - val sttpClient = "3.5.0" - val tapir = "0.20.1" - val urlDsl = "0.4.0" - val waypoint = "0.5.0" - val zio = "2.0.0-RC2" - val zioConfig = "3.0.0-RC2" - val zioInteropCats = "3.3.0-RC2" - val zioJson = "0.3.0-RC3" - val zioLogging = "2.0.0-RC5" - val zioPrelude = "1.0.0-RC10" - val zioZMX = "0.0.11" - } - - object Deps extends AkkaLibs with SlickLibs { - import IWMaterials.{Versions => V} - - val zioOrg = "dev.zio" - - def zioLib(name: String, version: String): Dep = - ivy"$zioOrg::zio-$name::$version" - - lazy val zio: Dep = ivy"$zioOrg::zio:${V.zio}" - - lazy val zioTest: Dep = zioLib("test", V.zio) - lazy val zioTestSbt: Dep = zioLib("test", V.zio) - - lazy val zioConfig: Dep = zioLib("config", V.zioConfig) - lazy val zioConfigTypesafe: Dep = - zioLib("config-typesafe", V.zioConfig) - lazy val zioConfigMagnolia: Dep = - zioLib("config-magnolia", V.zioConfig) - - lazy val zioJson: Dep = zioLib("json", V.zioJson) - lazy val zioLogging: Dep = zioLib("logging", V.zioLogging) - lazy val zioLoggingSlf4j: Dep = - zioLib("logging-slf4j", V.zioLogging) - lazy val zioPrelude: Dep = zioLib("prelude", V.zioPrelude) - lazy val zioStreams: Dep = zioLib("streams", V.zio) - lazy val zioZMX: Dep = zioLib("zmx", V.zioZMX) - lazy val zioInteropCats: Dep = - zioLib("interop-cats", V.zioInteropCats) - - /* What is the equivalent? ZIOModule with prepared test config? - def useZIO(testConf: Configuration*): Agg[Dep] = Agg( - zio, - zioTest, - zioTestSbt, - testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework") - ) - */ - - /* - def useZIOAll(testConf: Configuration*): Seq[Def.Setting[_]] = - useZIO(testConf: _*) ++ Seq( - zioStreams, - zioConfig, - zioConfigTypesafe, - zioConfigMagnolia, - zioJson, - zioZMX, - zioLogging, - zioPrelude - ) - */ - - private val tapirOrg = "com.softwaremill.sttp.tapir" - def tapirLib(name: String): Dep = - ivy"${tapirOrg}::tapir-$name::${V.tapir}" - - lazy val tapirCore: Dep = tapirLib("core") - lazy val tapirZIO: Dep = tapirLib("zio") - lazy val tapirZIOJson: Dep = tapirLib("json-zio") - lazy val tapirSttpClient: Dep = tapirLib("sttp-client") - lazy val tapirCats: Dep = tapirLib("cats") - lazy val tapirZIOHttp4sServer: Dep = tapirLib("zio-http4s-server") - - private val sttpClientOrg = "com.softwaremill.sttp.client3" - def sttpClientLib(name: String): Dep = - ivy"${sttpClientOrg}::${name}:${V.sttpClient}" - - lazy val sttpClientCore: Dep = sttpClientLib("core") - - lazy val http4sBlazeServer: Dep = - ivy"org.http4s::http4s-blaze-server:${V.http4s}" - - lazy val http4sPac4J: Dep = - ivy"org.pac4j::http4s-pac4j:${V.http4sPac4J}" - lazy val pac4jOIDC: Dep = - ivy"org.pac4j:pac4j-oidc:${V.pac4j}" - - lazy val scalaTest: Dep = - ivy"org.scalatest::scalatest:${V.scalaTest}" - lazy val scalaTestPlusScalacheck: Dep = - ivy"org.scalatestplus::scalacheck-1-15:3.2.9.0" - lazy val playScalaTest: Dep = - ivy"org.scalatestplus.play::scalatestplus-play:5.1.0" - - private val playOrg = "com.typesafe.play" - lazy val playMailer: Dep = ivy"${playOrg}::play-mailer:8.0.1" - lazy val playServer: Dep = ivy"${playOrg}::play-server:${V.play}" - lazy val playAkkaServer: Dep = - ivy"${playOrg}::play-akka-http-server:${V.play}" - lazy val play: Dep = ivy"${playOrg}::play:${V.play}" - lazy val playAhcWs: Dep = ivy"${playOrg}::play-ahc-ws:${V.play}" - lazy val playJson: Dep = ivy"${playOrg}::play-json:${V.playJson}" - - private val elastic4sOrg = "com.sksamuel.elastic4s" - lazy val useElastic4S: Agg[Dep] = Agg( - ivy"${elastic4sOrg}::elastic4s-core:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-client-akka:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-http-streams:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-json-play:${V.elastic4s}" - ) - - lazy val laminar: Dep = ivy"com.raquo::laminar::${V.laminar}" - - private def laminext(name: String): Dep = - ivy"io.laminext::$name::${V.laminar}" - - lazy val laminextCore: Dep = laminext("core") - lazy val laminextTailwind: Dep = laminext("tailwind") - lazy val laminextFetch: Dep = laminext("fetch") - lazy val laminextValidationCore: Dep = laminext("validation-core") - lazy val laminextUI: Dep = laminext("ui") - - lazy val waypoint: Dep = - ivy"com.raquo::waypoint::${V.waypoint}" - - lazy val urlDsl: Dep = - ivy"be.doeraene::url-dsl::${V.urlDsl}" - - lazy val scalaJavaTime: Dep = - ivy"io.github.cquiroz::scala-java-time::2.3.0" - - lazy val scalaJavaLocales: Dep = - ivy"io.github.cquiroz::scala-java-locales::1.2.1" - - lazy val logbackClassic: Dep = - ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" - } - - trait AkkaLibs { - - self: SlickLibs => - - object akka { - val V = Versions.akka - val tOrg = "com.typesafe.akka" - val lOrg = "com.lightbend.akka" - - def akkaMod(name: String): Dep = - ivy"$tOrg::akka-$name:$V" - - lazy val actor: Dep = akkaMod("actor") - lazy val actorTyped: Dep = akkaMod("actor-typed") - lazy val stream: Dep = akkaMod("stream") - lazy val persistence: Dep = akkaMod("persistence-typed") - lazy val persistenceQuery: Dep = akkaMod("persistence-query") - lazy val persistenceJdbc: Dep = - ivy"$lOrg::akka-persistence-jdbc:5.0.4" - val persistenceTestKit: Dep = ivy"$tOrg::akka-persistence-testkit:$V" - - object http { - val V = Versions.akkaHttp - - lazy val http: Dep = ivy"$tOrg::akka-http:$V" - lazy val sprayJson: Dep = ivy"$tOrg::akka-http-spray-json:$V" - - } - - object projection { - val V = "1.2.2" - - lazy val core: Dep = - ivy"$lOrg::akka-projection-core:$V" - lazy val eventsourced: Dep = - ivy"$lOrg::akka-projection-eventsourced:$V" - lazy val slick: Dep = - ivy"$lOrg::akka-projection-slick:$V" - lazy val jdbc: Dep = - ivy"$lOrg::akka-projection-jdbc:$V" - } - - object profiles { - // TODO: deal with cross-version for Scala3 (for3Use2_13) - lazy val eventsourcedJdbcProjection: Agg[Dep] = Agg( - persistenceQuery, - projection.core, - projection.eventsourced, - projection.slick, - persistenceJdbc - ) ++ slick.default - } - } - } - - trait SlickLibs { - object slick { - val V = IWMaterials.Versions.slick - val org = "com.typesafe.slick" - - lazy val slick: Dep = ivy"$org::slick:$V" - lazy val hikaricp: Dep = ivy"$org::slick-hikaricp:$V" - lazy val default: Agg[Dep] = Agg(slick, hikaricp) - } - } - -} diff --git a/iw/build.sc b/iw/build.sc deleted file mode 100644 index fbbc3b8..0000000 --- a/iw/build.sc +++ /dev/null @@ -1,144 +0,0 @@ -import mill._, scalalib._, scalajslib._ - -import $file.bom - -object support { - val Deps = bom.IWMaterials.Deps - val Versions = bom.IWMaterials.Versions - - trait CommonModule extends ScalaModule { - def scalaVersion = "3.1.1" - def scalacOptions = Seq( - "-encoding", - "utf8", - "-deprecation", - "-explain-types", - "-explain", - "-feature", - "-language:experimental.macros", - "-language:higherKinds", - "-language:implicitConversions", - "-unchecked", - "-Xfatal-warnings", - "-Ykind-projector" - ) - } - - trait CommonJSModule extends CommonModule with ScalaJSModule { - def scalaJSVersion = "1.8.0" - } - - trait CrossPlatformModule extends Module { outer => - def ivyDeps: T[Agg[Dep]] = Agg[Dep]() - def jsDeps: T[Agg[Dep]] = Agg[Dep]() - def jvmDeps: T[Agg[Dep]] = Agg[Dep]() - def moduleDeps: Seq[Module] = Seq[Module]() - - trait PlatformModule extends CommonModule { - def platform: String - override def millSourcePath = outer.millSourcePath - override def ivyDeps = outer.ivyDeps() ++ (platform match { - case "js" => jsDeps() - case _ => jvmDeps() - }) - override def moduleDeps = outer.moduleDeps.collect { - case m: CrossPlatformModule => - platform match { - case "js" => m.js - case _ => m.jvm - } - case m: JavaModule => m - } - } - - trait JsModule extends PlatformModule with CommonJSModule { - def platform = "js" - } - - trait JvmModule extends PlatformModule { - def platform = "jvm" - } - - val js: JsModule - val jvm: JvmModule - } - - trait PureCrossModule extends CrossPlatformModule { - override object js extends JsModule - override object jvm extends JvmModule - } - - trait PureCrossSbtModule extends CrossPlatformModule { - override object js extends JsModule with SbtModule - override object jvm extends JvmModule with SbtModule - } - - trait FullCrossSbtModule extends CrossPlatformModule { - trait FullSources extends JavaModule { self: PlatformModule => - override def sources = T.sources( - millSourcePath / platform / "src" / "main" / "scala", - millSourcePath / "shared" / "src" / "main" / "scala" - ) - - override def resources = T.sources( - millSourcePath / platform / "src" / "main" / "resources", - millSourcePath / "shared" / "src" / "main" / "resources" - ) - } - - override object js extends JsModule with FullSources - override object jvm extends JvmModule with FullSources - } - -} - -import support._ - -object mongo extends CommonModule { - def ivyDeps = Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - ivy"org.mongodb.scala::mongo-scala-driver:4.2.3".withDottyCompat( - scalaVersion() - ) - ) -} - -object tapir extends FullCrossSbtModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson, Deps.zioJson) - def jvmDeps = Agg(Deps.tapirZIO, Deps.tapirZIOHttp4sServer) - def jsDeps = Agg(Deps.tapirSttpClient) -} - -object akkaPersistence extends CommonModule { - def millSourcePath = build.millSourcePath / "akka-persistence" - def ivyDeps = (Agg( - Deps.akka.persistenceQuery, - Deps.akka.persistenceJdbc, - Deps.akka.projection.core, - Deps.akka.projection.eventsourced, - Deps.akka.projection.slick - ) ++ Deps.slick.default).map(_.withDottyCompat(scalaVersion())) ++ Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - Deps.akka.persistence.withDottyCompat(scalaVersion()), - ivy"com.typesafe.akka::akka-cluster-sharding-typed:${Deps.akka.V}" - .withDottyCompat(scalaVersion()) - ) -} - -object ui extends CommonJSModule { - def ivyDeps = Agg( - Deps.zio, - Deps.laminar, - Deps.zioJson, - Deps.waypoint, - Deps.urlDsl, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) -} diff --git a/iw/domain.sc b/iw/domain.sc deleted file mode 100644 index b09c56b..0000000 --- a/iw/domain.sc +++ /dev/null @@ -1,78 +0,0 @@ -import mill._, scalalib._ - -import $file.{build => ff}, ff.support._ - -trait DomainModule extends Module { - // General extra model deps - def modelModules: Seq[Module] = Seq.empty - // General extra codecs deps - def codecsModules: Seq[Module] = Seq.empty - // General extra endpoints deps - def endpointsModules: Seq[Module] = Seq.empty - - // Implementation deps for repo - def repoModules: Seq[JavaModule] = Seq.empty - - object shared extends Module { - object model extends PureCrossModule { - def moduleDeps = modelModules - def ivyDeps = Agg(Deps.zioPrelude) - } - object codecs extends PureCrossModule { - def moduleDeps = Seq(model) ++ codecsModules - def ivyDeps = Agg(Deps.zioJson) - } - } - - trait CommonProjects extends Module { - object model extends PureCrossModule { - def ivyDeps = Agg(Deps.zioPrelude) - def moduleDeps = Seq(shared.model) - } - object codecs extends PureCrossModule { - def moduleDeps = - Seq(model, shared.model, shared.codecs, ff.tapir) - } - object endpoints extends PureCrossModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson) - def moduleDeps = Seq(model, codecs) ++ endpointsModules - } - object client extends CommonJSModule { - def moduleDeps = Seq(endpoints.js) - } - object components extends CommonJSModule { - def ivyDeps = Agg( - Deps.laminar, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) - def moduleDeps = Seq(model.js, codecs.js, client, ff.ui) - } - } - - object query extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(repo, query.endpoints.jvm) - } - object repo extends CommonModule { - def ivyDeps = Agg(Deps.zio) - def moduleDeps = Seq(model.jvm, codecs.jvm) ++ repoModules - } - object projection extends CommonModule { - def moduleDeps = Seq(repo, ff.akkaPersistence) - } - } - - object command extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(entity, command.endpoints.jvm) - } - object entity extends CommonModule { - def moduleDeps = Seq(model.jvm, codecs.jvm, ff.akkaPersistence) - } - } -} diff --git a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala b/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala deleted file mode 100644 index 0fa3493..0000000 --- a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala +++ /dev/null @@ -1,53 +0,0 @@ -package works.iterative.mongo - -import zio.* -import zio.json.* -import zio.config.* -import org.mongodb.scala.* -import org.mongodb.scala.model.Filters.* -import org.bson.json.JsonObject -import org.mongodb.scala.model.ReplaceOptions -import org.mongodb.scala.bson.conversions.Bson - -case class MongoConfig(uri: String) - -object MongoConfig: - val configDesc = - import ConfigDescriptor.* - nested("MONGO")(string("URI").default("mongodb://localhost:27017")) - .to[MongoConfig] - val fromEnv = ZConfig.fromSystemEnv(configDesc) - -extension (m: MongoClient.type) - def layer: RLayer[MongoConfig, MongoClient] = - ZIO - .serviceWithZIO[MongoConfig](c => Task.attempt(MongoClient(c.uri))) - .toLayer - -class MongoJsonRepository[Elem, Key, Criteria]( - collection: MongoCollection[JsonObject], - toFilter: Criteria => Bson, - idFilter: Elem => (String, Key) -)(using JsonCodec[Elem]) { - def matching(criteria: Criteria): Task[Seq[Elem]] = - val filter = toFilter(criteria) - val query = collection.find(filter) - - for - result <- ZIO.fromFuture(_ => query.toFuture) - proof <- ZIO.collect(result)(j => - ZIO.fromOption(j.getJson.fromJson[Elem].toOption) - ) - yield proof - - def put(elem: Elem): Task[Unit] = - Task.async(cb => - collection - .replaceOne( - equal.tupled(idFilter(elem)), - JsonObject(elem.toJson), - ReplaceOptions().upsert(true) - ) - .subscribe(_ => cb(Task.unit), t => cb(Task.fail(t))) - ) -} diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala deleted file mode 100644 index 68d7196..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala +++ /dev/null @@ -1,12 +0,0 @@ -package works.iterative.tapir - -import sttp.model.Uri - -opaque type BaseUri = Option[Uri] - -object BaseUri: - - def apply(optU: Option[Uri]): BaseUri = optU - def apply(u: Uri): BaseUri = Some(u) - - extension (v: BaseUri) def toUri: Option[Uri] = v diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 653bc51..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,22 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.client.sttp.SttpClientInterpreter -import sttp.tapir.PublicEndpoint -import sttp.tapir.client.sttp.WebSocketToPipe -import scala.concurrent.Future -import sttp.client3.SttpBackend -import sttp.capabilities.WebSockets - -trait CustomTapirPlatformSpecific extends SttpClientInterpreter: - self: CustomTapir => - - type Backend = SttpBackend[Future, WebSockets] - - def makeClient[I, E, O]( - endpoint: PublicEndpoint[I, E, O, Any] - )(using - baseUri: BaseUri, - backend: Backend, - wsToPipe: WebSocketToPipe[Any] - ): I => Future[O] = - toClientThrowErrors(endpoint, baseUri.toUri, backend) diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 2545fbd..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,5 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.ztapir.ZTapir - -trait CustomTapirPlatformSpecific extends ZTapir diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala deleted file mode 100644 index 55e4ca1..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala +++ /dev/null @@ -1,7 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.server.http4s.ztapir.ZHttp4sServerInterpreter - -trait Http4sCustomTapir[Env] - extends CustomTapir - with ZHttp4sServerInterpreter[Env] diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala deleted file mode 100644 index dd52e9b..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala +++ /dev/null @@ -1,14 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.Tapir -import sttp.tapir.json.zio.TapirJsonZio -import sttp.tapir.TapirAliases - -trait CustomTapir - extends Tapir - with TapirJsonZio - with TapirAliases - with CustomTapirPlatformSpecific: - given Schema[ServerError] = Schema.derived - -object CustomTapir extends CustomTapir diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala deleted file mode 100644 index 715591d..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.tapir - -import zio.json.* - -sealed trait ServerError -case class InternalServerError(msg: String) extends ServerError -object InternalServerError: - def fromThrowable(t: Throwable): ServerError = InternalServerError( - t.getMessage - ) - -object ServerError: - given JsonCodec[ServerError] = DeriveJsonCodec.gen diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/File.scala deleted file mode 100644 index 09f6c07..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala +++ /dev/null @@ -1,3 +0,0 @@ -package works.iterative.services.files - -case class File(url: String, name: String) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala deleted file mode 100644 index 46633a5..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala +++ /dev/null @@ -1,25 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.Renderable - -given Renderable[File] with - extension (m: File) - def toHtml: HtmlElement = - li( - cls("pl-3 pr-4 py-3 flex items-center justify-between text-sm"), - div( - cls("w-0 flex-1 flex items-center"), - Icons.solid - .paperclip() - .amend(svg.cls := "flex-shrink-0 text-gray-400"), - span(cls("ml-2 flex-1 w-0 truncate"), m.name) - ), - a( - href(m.url), - cls("font-medium text-indigo-600 hover:text-indigo-500"), - "Otevřít" - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala deleted file mode 100644 index ac59db8..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import io.laminext.syntax.core.{*, given} - -def FileList(files: List[File]): HtmlElement = - ul( - role("list"), - cls("border border-gray-200 rounded-md divide-y divide-gray-200"), - files.map(_.toHtml) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala deleted file mode 100644 index 2e8f690..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala +++ /dev/null @@ -1,81 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import io.laminext.syntax.core.{*, given} - -object FilePicker: - sealed trait Event - sealed trait DoneEvent extends Event - case class SelectionUpdated(files: Set[File]) extends DoneEvent - case object SelectionCancelled extends DoneEvent - case object AvailableFilesRequested extends Event - - def apply( - currentFiles: Signal[List[File]], - availableFilesStream: EventStream[List[File]] - )(selectionUpdates: Observer[Event]): HtmlElement = - val (updatesStream, updatesObserver) = EventStream.withObserver[Event] - val selectorOpen = Var[Boolean](false) - - // This sequence tricks browser into displaying modal content centered - // Inspired by modal in headless ui playground - // https://github.com/tailwindlabs/headlessui/blob/fdd26297953080d5ec905dda0bf5ec9607897d86/packages/playground-react/pages/transitions/component-examples/modal.tsx#L78-L79 - inline def browserCenteringModalTrick: Modifier[HtmlElement] = - Seq[Modifier[HtmlElement]]( - span(cls("hidden sm:inline-block sm:h-screen sm:align-middle")), - "​" // Zero width space - ) - - inline def overlay: Modifier[HtmlElement] = - // Page overlay - /* TODO: transition - enter="ease-out duration-300" - enterFrom="opacity-0" - enterTo="opacity-100" - leave="ease-in duration-200" - leaveFrom="opacity-100" - leaveTo="opacity-0" - */ - div( - div( - onClick.mapTo(false) --> selectorOpen.writer, - cls("fixed inset-0 transition-opacity"), - div(cls("absolute inset-0 bg-gray-500 opacity-75")) - ) - ) - - inline def modalSelector: HtmlElement = - div( - cls("fixed inset-0 z-10 overflow-y-auto"), - div( - cls("text-center sm:block sm:p-0"), - overlay, - browserCenteringModalTrick, - child <-- currentFiles.map( - FileSelector(_, availableFilesStream)(updatesObserver) - ) - ) - ) - - div( - updatesStream --> selectionUpdates, - updatesStream.collect { case _: DoneEvent => - false - } --> selectorOpen.writer, - child.maybe <-- selectorOpen.signal.map(isOpen => - if isOpen then Some(modalSelector) else None - ), - div( - cls("flex flex-col space-y-5"), - child.maybe <-- currentFiles.map(files => - if files.isEmpty then None else Some(FileList(files)) - ), - button( - tpe := "button", - cls := "bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500", - "Zvolit soubory", - onClick.mapTo(true) --> selectorOpen.writer - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala deleted file mode 100644 index b43e07a..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala +++ /dev/null @@ -1,74 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.Loading -import io.laminext.syntax.core.{*, given} -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -object FileSelector: - import FilePicker._ - - def apply( - initialFiles: List[File], - availableFilesStream: EventStream[List[File]] - )(selectionUpdates: Observer[Event]): HtmlElement = - val selectedFiles = Var[Set[File]](initialFiles.to(Set)) - val availableFiles = availableFilesStream.startWithNone - // Request the files to display - selectionUpdates.onNext(AvailableFilesRequested) - div( - cls( - "inline-block transform overflow-hidden rounded-lg bg-white text-left align-bottom shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:align-middle" - ), - role("dialog"), - customHtmlAttr("aria.modal", BooleanAsTrueFalseStringCodec)(true), - aria.labelledBy("modal-headline"), - div( - cls("bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"), - div( - cls("sm:flex sm:items-start"), - div( - cls("mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"), - h3( - cls("text-lg font-medium leading-6 text-gray-900"), - idAttr("modal-headline"), - "Výběr souborů" - ) - ) - ), - child <-- availableFiles - .split(_ => ())((_, _, af) => FileTable(af, selectedFiles)) - .map(_.getOrElse(Loading)) - ), - div( - cls("bg-gray-50 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6"), - span( - cls("flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto"), - button( - typ("button"), - cls( - "focus:shadow-outline-green inline-flex w-full justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-base font-medium leading-6 text-white shadow-sm transition duration-150 ease-in-out hover:bg-indigo-500 focus:border-indigo-700 focus:outline-none sm:text-sm sm:leading-5" - ), - "Potvrdit", - composeEvents(onClick)( - _.sample(selectedFiles) - .map(SelectionUpdated(_)) - ) --> selectionUpdates - ) - ), - span( - cls("mt-3 flex w-full rounded-md shadow-sm sm:mt-0 sm:w-auto"), - button( - typ("button"), - cls( - "focus:shadow-outline-blue inline-flex w-full justify-center rounded-md border border-gray-300 bg-white px-4 py-2 text-base font-medium leading-6 text-gray-700 shadow-sm transition duration-150 ease-in-out hover:text-gray-500 focus:border-blue-300 focus:outline-none sm:text-sm sm:leading-5" - ), - "Zrušit", - onClick.mapTo(SelectionCancelled) --> selectionUpdates - ) - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala deleted file mode 100644 index aa11b2b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala +++ /dev/null @@ -1,91 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import works.iterative.ui.components.tailwind.Icons - -def FileTable( - files: Signal[List[File]], - selectedFiles: Var[Set[File]] -): HtmlElement = - val scope = customHtmlAttr("scope", StringAsIsCodec) - - def headerRow: HtmlElement = - val col = scope("col") - val baseM: Modifier[HtmlElement] = Seq(cls("px-6 py-3"), col) - val textH = cls( - "text-left text-xs font-medium text-gray-500 uppercase tracking-wider" - ) - tr( - th(baseM, span(cls("sr-only"), "Vybrat")), - th(baseM, textH, "Název"), - th(baseM, cls("relative"), span(cls("sr-only"), "Otevřít")) - ) - - def tableRow( - f: File, - idx: Int, - selected: Boolean - )(toggleSelection: Observer[Unit]): HtmlElement = - val baseC = cls("px-6 py-4 whitespace-nowrap text-sm") - tr( - cls(if idx % 2 == 0 then "bg-gray-50" else "bg-white"), - td( - cls("font-medium cursor-pointer"), - onClick.mapTo(()) --> toggleSelection, - cls(if selected then "text-green-900" else "text-gray-200"), - Icons.outline.`check-circle`().amend(svg.cls := "mx-auto"), - span(cls("sr-only"), if selected then "Vybráno" else "Nevybráno") - ), - td( - baseC, - cls("font-medium text-gray-900"), - f.name, - onClick.mapTo(()) --> toggleSelection - ), - td( - baseC, - cls("text-right font-medium"), - a( - href(f.url), - target("_blank"), - cls( - "flex items-center space-x-4 text-indigo-600 hover:text-indigo-900" - ), - Icons.outline.`external-link`(), - "Otevřít" - ) - ) - ) - - div( - cls("flex flex-col"), - div(cls("overflow-x-auto sm:-mx-6 lg:-mx-8")), - div( - cls("py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8"), - div( - cls("shadow overflow-hidden border-b border-gray-200 sm:rounded-lg"), - table( - cls("min-w-full divide-y divide-gray-200"), - thead( - cls("bg-gray-50"), - headerRow - ), - tbody( - children <-- files - .map(_.zipWithIndex) - .combineWithFn(selectedFiles)((f, sel) => - f.map((file, idx) => - val active = sel.contains(file) - tableRow(file, idx, active)( - selectedFiles.writer - .contramap(_ => if active then sel - file else sel + file) - ) - ) - ) - ) - ) - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala deleted file mode 100644 index 0fc8796..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala +++ /dev/null @@ -1,32 +0,0 @@ -package works.iterative.ui.components.tailwind - -import CustomAttrs.ariaHidden -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -// TODO: render icon or picture based on img signal -class Avatar($avatarImg: Signal[Option[String]]): - inline def avatarPlaceholder(size: Int): HtmlElement = - div( - cls := s"rounded-full text-indigo-200 bg-indigo-500 h-${size} w-${size} flex items-center justify-center", - Icons.outline.user(size - 2) - ) - - inline def avatarImage(size: Int): Signal[HtmlElement] = - $avatarImg.split(_ => ())((_, _, $url) => - img( - cls := s"w-$size h-$size rounded-full", - src <-- $url, - alt := "" - ) - ).map(_.getOrElse(avatarPlaceholder(size))) - - inline def avatar(size: Int): HtmlElement = - div( - cls := "relative", - child <-- avatarImage(size), - span( - cls := "absolute inset-0 shadow-inner rounded-full", - ariaHidden := true - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala deleted file mode 100644 index bcbed88..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala +++ /dev/null @@ -1,77 +0,0 @@ -package works.iterative.ui.components.tailwind - -enum ColorWeight(value: String): - inline def toCSS: String = value - // To be used for black and white until - // we support better mechanism via extension methods - case w__ extends ColorWeight("") - case w50 extends ColorWeight("50") - case w100 extends ColorWeight("100") - case w200 extends ColorWeight("200") - case w300 extends ColorWeight("300") - case w400 extends ColorWeight("400") - case w500 extends ColorWeight("500") - case w600 extends ColorWeight("600") - case w700 extends ColorWeight("700") - case w800 extends ColorWeight("800") - case w900 extends ColorWeight("900") - -enum Color(name: String): - import ColorWeight._ - - inline def toCSSNoColorWeight(prefix: String): String = - s"${prefix}-${name}" - inline def toCSSWithColorWeight( - prefix: String, - weight: ColorWeight - ): String = - s"${prefix}-${name}-${weight.toCSS}" - inline def toCSS(prefix: String)(weight: ColorWeight): String = - weight match { - case `w__` => toCSSNoColorWeight(prefix) - case _ => toCSSWithColorWeight(prefix, weight) - } - inline def bg: ColorWeight => String = toCSS("bg")(_) - inline def text: ColorWeight => String = toCSS("text")(_) - inline def decoration: ColorWeight => String = toCSS("decoration")(_) - inline def border: ColorWeight => String = toCSS("border")(_) - inline def outline: ColorWeight => String = toCSS("outline")(_) - inline def divide: ColorWeight => String = toCSS("divide")(_) - inline def ring: ColorWeight => String = toCSS("ring")(_) - inline def ringOffset: ColorWeight => String = toCSS("ring-offset")(_) - inline def shadow: ColorWeight => String = toCSS("shadow")(_) - inline def accent: ColorWeight => String = toCSS("accent")(_) - - // TODO: change the "stupid" methods to extension methods - // that will keep the invariants in comments lower - case current extends Color("current") - case inherit extends Color("inherit") - // Not present in for all methods - case transp extends Color("transparent") - // Seen in accent, not preset otherwise - case auto extends Color("auto") - // Black and white do not have weight - case black extends Color("black") - case white extends Color("white") - case slate extends Color("slate") - case gray extends Color("gray") - case zinc extends Color("zinc") - case neutral extends Color("neutral") - case stone extends Color("stone") - case red extends Color("red") - case orange extends Color("orange") - case amber extends Color("amber") - case yellow extends Color("yellow") - case lime extends Color("lime") - case green extends Color("green") - case emerald extends Color("emerald") - case teal extends Color("teal") - case cyan extends Color("cyan") - case sky extends Color("sky") - case blue extends Color("blue") - case indigo extends Color("indigo") - case violet extends Color("violet") - case purple extends Color("purple") - case fuchsia extends Color("fuchsia") - case pink extends Color("pink") - case rose extends Color("rose") diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala deleted file mode 100644 index 6fb453c..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -object CustomAttrs { - // Made a pull request to add aria-current to scala-dom-types, remove after - val ariaCurrent = customHtmlAttr("aria-current", StringAsIsCodec) - val ariaHidden = customHtmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - - val datetime = customHtmlAttr("datetime", StringAsIsCodec) - - object svg { - import com.raquo.laminar.api.L.svg.{*, given} - val ariaHidden = - customSvgAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - } -} diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala deleted file mode 100644 index 6cda48b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala +++ /dev/null @@ -1,28 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -object Display: - - enum Breakpoint: - case sm, md, lg, xl, `2xl` - - enum DisplayClass: - case block, `inline-block`, `inline`, flex, `inline-flex`, table, - `inline-table`, `table-caption` - - object ShowUpFrom: - inline def apply( - br: Breakpoint, - dc: DisplayClass = DisplayClass.block - ): HtmlElement = - div( - cls := "hidden", - cls := s"${br}:${dc}" - ) - - object HideUpTo: - inline def apply(br: Breakpoint): HtmlElement = - div( - cls := s"${br}:hidden" - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala deleted file mode 100644 index 4a8b065..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala +++ /dev/null @@ -1,270 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec -import com.raquo.domtypes.generic.defs.attrs.AriaAttrs -import com.raquo.laminar.api.L.svg.{*, given} -import com.raquo.laminar.builders.SvgBuilders -import com.raquo.laminar.keys.ReactiveSvgAttr - -// TODO: fix sizes, colors, hover and stuff, normalize and amend on call site -object Icons: - object aria: - inline def hidden = CustomAttrs.svg.ariaHidden - - object outline: - val defaultSize: Int = 6 - - inline def bell(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" - ) - ) - - inline def `check-circle`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" - ) - ) - - inline def `document-add`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M9 13h6m-3-3v6m5 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" - ) - ) - - inline def `external-link`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" - ) - ) - - inline def menu(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M4 6h16M4 12h16M4 18h16" - ) - ) - - inline def `status-offline`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M18.364 5.636a9 9 0 010 12.728m0 0l-2.829-2.829m2.829 2.829L21 21M15.536 8.464a5 5 0 010 7.072m0 0l-2.829-2.829m-4.243 2.829a4.978 4.978 0 01-1.414-2.83m-1.414 5.658a9 9 0 01-2.167-9.238m7.824 2.167a1 1 0 111.414 1.414m-1.414-1.414L3 3m8.293 8.293l1.414 1.414" - ) - ) - - inline def user(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" - ) - ) - - inline def x(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M6 18L18 6M6 6l12 12" - ) - ) - - end outline - - object solid: - val defaultSize: Int = 5 - - inline def users(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - d := "M9 6a3 3 0 11-6 0 3 3 0 016 0zM17 6a3 3 0 11-6 0 3 3 0 016 0zM12.93 17c.046-.327.07-.66.07-1a6.97 6.97 0 00-1.5-4.33A5 5 0 0119 16v1h-6.07zM6 11a5 5 0 015 5v1H1v-1a5 5 0 015-5z" - ) - ) - - inline def `location-marker`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M5.05 4.05a7 7 0 119.9 9.9L10 18.9l-4.95-4.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z", - clipRule := "evenodd" - ) - ) - - inline def calendar(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z", - clipRule := "evenodd" - ) - ) - - inline def `chevron-right`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z", - clipRule := "evenodd" - ) - ) - - inline def search(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z", - clipRule := "evenodd" - ) - ) - - inline def filter(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M3 3a1 1 0 011-1h12a1 1 0 011 1v3a1 1 0 01-.293.707L12 11.414V15a1 1 0 01-.293.707l-2 2A1 1 0 018 17v-5.586L3.293 6.707A1 1 0 013 6V3z", - clipRule := "evenodd" - ) - ) - - inline def `arrow-narrow-left`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M7.707 14.707a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 1.414L5.414 9H17a1 1 0 110 2H5.414l2.293 2.293a1 1 0 010 1.414z", - clipRule := "evenodd" - ) - ) - - inline def home(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - d := "M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z" - ) - ) - - inline def paperclip(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size} text-gray-400", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M8 4a3 3 0 00-3 3v4a5 5 0 0010 0V7a1 1 0 112 0v4a7 7 0 11-14 0V7a5 5 0 0110 0v4a3 3 0 11-6 0V7a1 1 0 012 0v4a1 1 0 102 0V7a3 3 0 00-3-3z", - clipRule := "evenodd" - ) - ) - - end solid -end Icons diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/LinkSupport.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/LinkSupport.scala deleted file mode 100644 index 88f0b9a..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/LinkSupport.scala +++ /dev/null @@ -1,12 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.jsdom.defs.events.TypedTargetMouseEvent - -object LinkSupport: - - extension [El <: org.scalajs.dom.EventTarget]( - ep: EventProcessor[TypedTargetMouseEvent[El], TypedTargetMouseEvent[El]] - ) - def noKeyMod = - ep.filter(ev => !(ev.ctrlKey || ev.metaKey || ev.shiftKey || ev.altKey)) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Loader.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Loader.scala deleted file mode 100644 index 5399933..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Loader.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -// TODO: proper loader -def Loading = - div( - cls := "bg-gray-50 overflow-hidden rounded-lg", - div( - cls := "px-4 py-5 sm:p-6", - "Loading..." - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Renderable.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Renderable.scala deleted file mode 100644 index a0fcc5b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Renderable.scala +++ /dev/null @@ -1,6 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -trait Renderable[A]: - extension (a: A) def toHtml: HtmlElement diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala deleted file mode 100644 index e7e6627..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.akka - -// Base class for all command-processing related exceptions from handlers -sealed abstract class CommandHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandNotAvailable[C, S](cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd není dostupný ve stavu $state" - ) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandRejected[C, S](reason: String, cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd byl ve stavu $state odmítnut s odůvodněním $reason" - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala deleted file mode 100644 index 72a5bf9..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.akka - -sealed abstract class EventHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -case class UnhandledEvent[Event, State](event: Event, state: State) - extends EventHandlerException( - s"Událost $event nastala ve stavu $state bez možnosti zpracování" - ) diff --git a/iw/bom.sc b/iw/bom.sc deleted file mode 100644 index e9266b8..0000000 --- a/iw/bom.sc +++ /dev/null @@ -1,227 +0,0 @@ -import mill._, scalalib._ - -object IWMaterials { - - object Versions { - val akka = "2.6.16" - val akkaHttp = "10.2.4" - val cats = "2.7.0" - val elastic4s = "7.12.2" - val http4s = "0.23.10" - val http4sPac4J = "4.0.0" - val laminar = "0.14.2" - val laminext = laminar - val logbackClassic = "1.2.10" - val pac4j = "5.2.0" - val play = "2.8.8" - val playJson = "2.9.2" - val scalaTest = "3.2.9" - val slick = "3.3.3" - val sttpClient = "3.5.0" - val tapir = "0.20.1" - val urlDsl = "0.4.0" - val waypoint = "0.5.0" - val zio = "2.0.0-RC2" - val zioConfig = "3.0.0-RC2" - val zioInteropCats = "3.3.0-RC2" - val zioJson = "0.3.0-RC3" - val zioLogging = "2.0.0-RC5" - val zioPrelude = "1.0.0-RC10" - val zioZMX = "0.0.11" - } - - object Deps extends AkkaLibs with SlickLibs { - import IWMaterials.{Versions => V} - - val zioOrg = "dev.zio" - - def zioLib(name: String, version: String): Dep = - ivy"$zioOrg::zio-$name::$version" - - lazy val zio: Dep = ivy"$zioOrg::zio:${V.zio}" - - lazy val zioTest: Dep = zioLib("test", V.zio) - lazy val zioTestSbt: Dep = zioLib("test", V.zio) - - lazy val zioConfig: Dep = zioLib("config", V.zioConfig) - lazy val zioConfigTypesafe: Dep = - zioLib("config-typesafe", V.zioConfig) - lazy val zioConfigMagnolia: Dep = - zioLib("config-magnolia", V.zioConfig) - - lazy val zioJson: Dep = zioLib("json", V.zioJson) - lazy val zioLogging: Dep = zioLib("logging", V.zioLogging) - lazy val zioLoggingSlf4j: Dep = - zioLib("logging-slf4j", V.zioLogging) - lazy val zioPrelude: Dep = zioLib("prelude", V.zioPrelude) - lazy val zioStreams: Dep = zioLib("streams", V.zio) - lazy val zioZMX: Dep = zioLib("zmx", V.zioZMX) - lazy val zioInteropCats: Dep = - zioLib("interop-cats", V.zioInteropCats) - - /* What is the equivalent? ZIOModule with prepared test config? - def useZIO(testConf: Configuration*): Agg[Dep] = Agg( - zio, - zioTest, - zioTestSbt, - testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework") - ) - */ - - /* - def useZIOAll(testConf: Configuration*): Seq[Def.Setting[_]] = - useZIO(testConf: _*) ++ Seq( - zioStreams, - zioConfig, - zioConfigTypesafe, - zioConfigMagnolia, - zioJson, - zioZMX, - zioLogging, - zioPrelude - ) - */ - - private val tapirOrg = "com.softwaremill.sttp.tapir" - def tapirLib(name: String): Dep = - ivy"${tapirOrg}::tapir-$name::${V.tapir}" - - lazy val tapirCore: Dep = tapirLib("core") - lazy val tapirZIO: Dep = tapirLib("zio") - lazy val tapirZIOJson: Dep = tapirLib("json-zio") - lazy val tapirSttpClient: Dep = tapirLib("sttp-client") - lazy val tapirCats: Dep = tapirLib("cats") - lazy val tapirZIOHttp4sServer: Dep = tapirLib("zio-http4s-server") - - private val sttpClientOrg = "com.softwaremill.sttp.client3" - def sttpClientLib(name: String): Dep = - ivy"${sttpClientOrg}::${name}:${V.sttpClient}" - - lazy val sttpClientCore: Dep = sttpClientLib("core") - - lazy val http4sBlazeServer: Dep = - ivy"org.http4s::http4s-blaze-server:${V.http4s}" - - lazy val http4sPac4J: Dep = - ivy"org.pac4j::http4s-pac4j:${V.http4sPac4J}" - lazy val pac4jOIDC: Dep = - ivy"org.pac4j:pac4j-oidc:${V.pac4j}" - - lazy val scalaTest: Dep = - ivy"org.scalatest::scalatest:${V.scalaTest}" - lazy val scalaTestPlusScalacheck: Dep = - ivy"org.scalatestplus::scalacheck-1-15:3.2.9.0" - lazy val playScalaTest: Dep = - ivy"org.scalatestplus.play::scalatestplus-play:5.1.0" - - private val playOrg = "com.typesafe.play" - lazy val playMailer: Dep = ivy"${playOrg}::play-mailer:8.0.1" - lazy val playServer: Dep = ivy"${playOrg}::play-server:${V.play}" - lazy val playAkkaServer: Dep = - ivy"${playOrg}::play-akka-http-server:${V.play}" - lazy val play: Dep = ivy"${playOrg}::play:${V.play}" - lazy val playAhcWs: Dep = ivy"${playOrg}::play-ahc-ws:${V.play}" - lazy val playJson: Dep = ivy"${playOrg}::play-json:${V.playJson}" - - private val elastic4sOrg = "com.sksamuel.elastic4s" - lazy val useElastic4S: Agg[Dep] = Agg( - ivy"${elastic4sOrg}::elastic4s-core:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-client-akka:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-http-streams:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-json-play:${V.elastic4s}" - ) - - lazy val laminar: Dep = ivy"com.raquo::laminar::${V.laminar}" - - private def laminext(name: String): Dep = - ivy"io.laminext::$name::${V.laminar}" - - lazy val laminextCore: Dep = laminext("core") - lazy val laminextTailwind: Dep = laminext("tailwind") - lazy val laminextFetch: Dep = laminext("fetch") - lazy val laminextValidationCore: Dep = laminext("validation-core") - lazy val laminextUI: Dep = laminext("ui") - - lazy val waypoint: Dep = - ivy"com.raquo::waypoint::${V.waypoint}" - - lazy val urlDsl: Dep = - ivy"be.doeraene::url-dsl::${V.urlDsl}" - - lazy val scalaJavaTime: Dep = - ivy"io.github.cquiroz::scala-java-time::2.3.0" - - lazy val scalaJavaLocales: Dep = - ivy"io.github.cquiroz::scala-java-locales::1.2.1" - - lazy val logbackClassic: Dep = - ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" - } - - trait AkkaLibs { - - self: SlickLibs => - - object akka { - val V = Versions.akka - val tOrg = "com.typesafe.akka" - val lOrg = "com.lightbend.akka" - - def akkaMod(name: String): Dep = - ivy"$tOrg::akka-$name:$V" - - lazy val actor: Dep = akkaMod("actor") - lazy val actorTyped: Dep = akkaMod("actor-typed") - lazy val stream: Dep = akkaMod("stream") - lazy val persistence: Dep = akkaMod("persistence-typed") - lazy val persistenceQuery: Dep = akkaMod("persistence-query") - lazy val persistenceJdbc: Dep = - ivy"$lOrg::akka-persistence-jdbc:5.0.4" - val persistenceTestKit: Dep = ivy"$tOrg::akka-persistence-testkit:$V" - - object http { - val V = Versions.akkaHttp - - lazy val http: Dep = ivy"$tOrg::akka-http:$V" - lazy val sprayJson: Dep = ivy"$tOrg::akka-http-spray-json:$V" - - } - - object projection { - val V = "1.2.2" - - lazy val core: Dep = - ivy"$lOrg::akka-projection-core:$V" - lazy val eventsourced: Dep = - ivy"$lOrg::akka-projection-eventsourced:$V" - lazy val slick: Dep = - ivy"$lOrg::akka-projection-slick:$V" - lazy val jdbc: Dep = - ivy"$lOrg::akka-projection-jdbc:$V" - } - - object profiles { - // TODO: deal with cross-version for Scala3 (for3Use2_13) - lazy val eventsourcedJdbcProjection: Agg[Dep] = Agg( - persistenceQuery, - projection.core, - projection.eventsourced, - projection.slick, - persistenceJdbc - ) ++ slick.default - } - } - } - - trait SlickLibs { - object slick { - val V = IWMaterials.Versions.slick - val org = "com.typesafe.slick" - - lazy val slick: Dep = ivy"$org::slick:$V" - lazy val hikaricp: Dep = ivy"$org::slick-hikaricp:$V" - lazy val default: Agg[Dep] = Agg(slick, hikaricp) - } - } - -} diff --git a/iw/build.sc b/iw/build.sc deleted file mode 100644 index fbbc3b8..0000000 --- a/iw/build.sc +++ /dev/null @@ -1,144 +0,0 @@ -import mill._, scalalib._, scalajslib._ - -import $file.bom - -object support { - val Deps = bom.IWMaterials.Deps - val Versions = bom.IWMaterials.Versions - - trait CommonModule extends ScalaModule { - def scalaVersion = "3.1.1" - def scalacOptions = Seq( - "-encoding", - "utf8", - "-deprecation", - "-explain-types", - "-explain", - "-feature", - "-language:experimental.macros", - "-language:higherKinds", - "-language:implicitConversions", - "-unchecked", - "-Xfatal-warnings", - "-Ykind-projector" - ) - } - - trait CommonJSModule extends CommonModule with ScalaJSModule { - def scalaJSVersion = "1.8.0" - } - - trait CrossPlatformModule extends Module { outer => - def ivyDeps: T[Agg[Dep]] = Agg[Dep]() - def jsDeps: T[Agg[Dep]] = Agg[Dep]() - def jvmDeps: T[Agg[Dep]] = Agg[Dep]() - def moduleDeps: Seq[Module] = Seq[Module]() - - trait PlatformModule extends CommonModule { - def platform: String - override def millSourcePath = outer.millSourcePath - override def ivyDeps = outer.ivyDeps() ++ (platform match { - case "js" => jsDeps() - case _ => jvmDeps() - }) - override def moduleDeps = outer.moduleDeps.collect { - case m: CrossPlatformModule => - platform match { - case "js" => m.js - case _ => m.jvm - } - case m: JavaModule => m - } - } - - trait JsModule extends PlatformModule with CommonJSModule { - def platform = "js" - } - - trait JvmModule extends PlatformModule { - def platform = "jvm" - } - - val js: JsModule - val jvm: JvmModule - } - - trait PureCrossModule extends CrossPlatformModule { - override object js extends JsModule - override object jvm extends JvmModule - } - - trait PureCrossSbtModule extends CrossPlatformModule { - override object js extends JsModule with SbtModule - override object jvm extends JvmModule with SbtModule - } - - trait FullCrossSbtModule extends CrossPlatformModule { - trait FullSources extends JavaModule { self: PlatformModule => - override def sources = T.sources( - millSourcePath / platform / "src" / "main" / "scala", - millSourcePath / "shared" / "src" / "main" / "scala" - ) - - override def resources = T.sources( - millSourcePath / platform / "src" / "main" / "resources", - millSourcePath / "shared" / "src" / "main" / "resources" - ) - } - - override object js extends JsModule with FullSources - override object jvm extends JvmModule with FullSources - } - -} - -import support._ - -object mongo extends CommonModule { - def ivyDeps = Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - ivy"org.mongodb.scala::mongo-scala-driver:4.2.3".withDottyCompat( - scalaVersion() - ) - ) -} - -object tapir extends FullCrossSbtModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson, Deps.zioJson) - def jvmDeps = Agg(Deps.tapirZIO, Deps.tapirZIOHttp4sServer) - def jsDeps = Agg(Deps.tapirSttpClient) -} - -object akkaPersistence extends CommonModule { - def millSourcePath = build.millSourcePath / "akka-persistence" - def ivyDeps = (Agg( - Deps.akka.persistenceQuery, - Deps.akka.persistenceJdbc, - Deps.akka.projection.core, - Deps.akka.projection.eventsourced, - Deps.akka.projection.slick - ) ++ Deps.slick.default).map(_.withDottyCompat(scalaVersion())) ++ Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - Deps.akka.persistence.withDottyCompat(scalaVersion()), - ivy"com.typesafe.akka::akka-cluster-sharding-typed:${Deps.akka.V}" - .withDottyCompat(scalaVersion()) - ) -} - -object ui extends CommonJSModule { - def ivyDeps = Agg( - Deps.zio, - Deps.laminar, - Deps.zioJson, - Deps.waypoint, - Deps.urlDsl, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) -} diff --git a/iw/domain.sc b/iw/domain.sc deleted file mode 100644 index b09c56b..0000000 --- a/iw/domain.sc +++ /dev/null @@ -1,78 +0,0 @@ -import mill._, scalalib._ - -import $file.{build => ff}, ff.support._ - -trait DomainModule extends Module { - // General extra model deps - def modelModules: Seq[Module] = Seq.empty - // General extra codecs deps - def codecsModules: Seq[Module] = Seq.empty - // General extra endpoints deps - def endpointsModules: Seq[Module] = Seq.empty - - // Implementation deps for repo - def repoModules: Seq[JavaModule] = Seq.empty - - object shared extends Module { - object model extends PureCrossModule { - def moduleDeps = modelModules - def ivyDeps = Agg(Deps.zioPrelude) - } - object codecs extends PureCrossModule { - def moduleDeps = Seq(model) ++ codecsModules - def ivyDeps = Agg(Deps.zioJson) - } - } - - trait CommonProjects extends Module { - object model extends PureCrossModule { - def ivyDeps = Agg(Deps.zioPrelude) - def moduleDeps = Seq(shared.model) - } - object codecs extends PureCrossModule { - def moduleDeps = - Seq(model, shared.model, shared.codecs, ff.tapir) - } - object endpoints extends PureCrossModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson) - def moduleDeps = Seq(model, codecs) ++ endpointsModules - } - object client extends CommonJSModule { - def moduleDeps = Seq(endpoints.js) - } - object components extends CommonJSModule { - def ivyDeps = Agg( - Deps.laminar, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) - def moduleDeps = Seq(model.js, codecs.js, client, ff.ui) - } - } - - object query extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(repo, query.endpoints.jvm) - } - object repo extends CommonModule { - def ivyDeps = Agg(Deps.zio) - def moduleDeps = Seq(model.jvm, codecs.jvm) ++ repoModules - } - object projection extends CommonModule { - def moduleDeps = Seq(repo, ff.akkaPersistence) - } - } - - object command extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(entity, command.endpoints.jvm) - } - object entity extends CommonModule { - def moduleDeps = Seq(model.jvm, codecs.jvm, ff.akkaPersistence) - } - } -} diff --git a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala b/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala deleted file mode 100644 index 0fa3493..0000000 --- a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala +++ /dev/null @@ -1,53 +0,0 @@ -package works.iterative.mongo - -import zio.* -import zio.json.* -import zio.config.* -import org.mongodb.scala.* -import org.mongodb.scala.model.Filters.* -import org.bson.json.JsonObject -import org.mongodb.scala.model.ReplaceOptions -import org.mongodb.scala.bson.conversions.Bson - -case class MongoConfig(uri: String) - -object MongoConfig: - val configDesc = - import ConfigDescriptor.* - nested("MONGO")(string("URI").default("mongodb://localhost:27017")) - .to[MongoConfig] - val fromEnv = ZConfig.fromSystemEnv(configDesc) - -extension (m: MongoClient.type) - def layer: RLayer[MongoConfig, MongoClient] = - ZIO - .serviceWithZIO[MongoConfig](c => Task.attempt(MongoClient(c.uri))) - .toLayer - -class MongoJsonRepository[Elem, Key, Criteria]( - collection: MongoCollection[JsonObject], - toFilter: Criteria => Bson, - idFilter: Elem => (String, Key) -)(using JsonCodec[Elem]) { - def matching(criteria: Criteria): Task[Seq[Elem]] = - val filter = toFilter(criteria) - val query = collection.find(filter) - - for - result <- ZIO.fromFuture(_ => query.toFuture) - proof <- ZIO.collect(result)(j => - ZIO.fromOption(j.getJson.fromJson[Elem].toOption) - ) - yield proof - - def put(elem: Elem): Task[Unit] = - Task.async(cb => - collection - .replaceOne( - equal.tupled(idFilter(elem)), - JsonObject(elem.toJson), - ReplaceOptions().upsert(true) - ) - .subscribe(_ => cb(Task.unit), t => cb(Task.fail(t))) - ) -} diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala deleted file mode 100644 index 68d7196..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala +++ /dev/null @@ -1,12 +0,0 @@ -package works.iterative.tapir - -import sttp.model.Uri - -opaque type BaseUri = Option[Uri] - -object BaseUri: - - def apply(optU: Option[Uri]): BaseUri = optU - def apply(u: Uri): BaseUri = Some(u) - - extension (v: BaseUri) def toUri: Option[Uri] = v diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 653bc51..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,22 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.client.sttp.SttpClientInterpreter -import sttp.tapir.PublicEndpoint -import sttp.tapir.client.sttp.WebSocketToPipe -import scala.concurrent.Future -import sttp.client3.SttpBackend -import sttp.capabilities.WebSockets - -trait CustomTapirPlatformSpecific extends SttpClientInterpreter: - self: CustomTapir => - - type Backend = SttpBackend[Future, WebSockets] - - def makeClient[I, E, O]( - endpoint: PublicEndpoint[I, E, O, Any] - )(using - baseUri: BaseUri, - backend: Backend, - wsToPipe: WebSocketToPipe[Any] - ): I => Future[O] = - toClientThrowErrors(endpoint, baseUri.toUri, backend) diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 2545fbd..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,5 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.ztapir.ZTapir - -trait CustomTapirPlatformSpecific extends ZTapir diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala deleted file mode 100644 index 55e4ca1..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala +++ /dev/null @@ -1,7 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.server.http4s.ztapir.ZHttp4sServerInterpreter - -trait Http4sCustomTapir[Env] - extends CustomTapir - with ZHttp4sServerInterpreter[Env] diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala deleted file mode 100644 index dd52e9b..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala +++ /dev/null @@ -1,14 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.Tapir -import sttp.tapir.json.zio.TapirJsonZio -import sttp.tapir.TapirAliases - -trait CustomTapir - extends Tapir - with TapirJsonZio - with TapirAliases - with CustomTapirPlatformSpecific: - given Schema[ServerError] = Schema.derived - -object CustomTapir extends CustomTapir diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala deleted file mode 100644 index 715591d..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.tapir - -import zio.json.* - -sealed trait ServerError -case class InternalServerError(msg: String) extends ServerError -object InternalServerError: - def fromThrowable(t: Throwable): ServerError = InternalServerError( - t.getMessage - ) - -object ServerError: - given JsonCodec[ServerError] = DeriveJsonCodec.gen diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/File.scala deleted file mode 100644 index 09f6c07..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala +++ /dev/null @@ -1,3 +0,0 @@ -package works.iterative.services.files - -case class File(url: String, name: String) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala deleted file mode 100644 index 46633a5..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala +++ /dev/null @@ -1,25 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.Renderable - -given Renderable[File] with - extension (m: File) - def toHtml: HtmlElement = - li( - cls("pl-3 pr-4 py-3 flex items-center justify-between text-sm"), - div( - cls("w-0 flex-1 flex items-center"), - Icons.solid - .paperclip() - .amend(svg.cls := "flex-shrink-0 text-gray-400"), - span(cls("ml-2 flex-1 w-0 truncate"), m.name) - ), - a( - href(m.url), - cls("font-medium text-indigo-600 hover:text-indigo-500"), - "Otevřít" - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala deleted file mode 100644 index ac59db8..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import io.laminext.syntax.core.{*, given} - -def FileList(files: List[File]): HtmlElement = - ul( - role("list"), - cls("border border-gray-200 rounded-md divide-y divide-gray-200"), - files.map(_.toHtml) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala deleted file mode 100644 index 2e8f690..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala +++ /dev/null @@ -1,81 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import io.laminext.syntax.core.{*, given} - -object FilePicker: - sealed trait Event - sealed trait DoneEvent extends Event - case class SelectionUpdated(files: Set[File]) extends DoneEvent - case object SelectionCancelled extends DoneEvent - case object AvailableFilesRequested extends Event - - def apply( - currentFiles: Signal[List[File]], - availableFilesStream: EventStream[List[File]] - )(selectionUpdates: Observer[Event]): HtmlElement = - val (updatesStream, updatesObserver) = EventStream.withObserver[Event] - val selectorOpen = Var[Boolean](false) - - // This sequence tricks browser into displaying modal content centered - // Inspired by modal in headless ui playground - // https://github.com/tailwindlabs/headlessui/blob/fdd26297953080d5ec905dda0bf5ec9607897d86/packages/playground-react/pages/transitions/component-examples/modal.tsx#L78-L79 - inline def browserCenteringModalTrick: Modifier[HtmlElement] = - Seq[Modifier[HtmlElement]]( - span(cls("hidden sm:inline-block sm:h-screen sm:align-middle")), - "​" // Zero width space - ) - - inline def overlay: Modifier[HtmlElement] = - // Page overlay - /* TODO: transition - enter="ease-out duration-300" - enterFrom="opacity-0" - enterTo="opacity-100" - leave="ease-in duration-200" - leaveFrom="opacity-100" - leaveTo="opacity-0" - */ - div( - div( - onClick.mapTo(false) --> selectorOpen.writer, - cls("fixed inset-0 transition-opacity"), - div(cls("absolute inset-0 bg-gray-500 opacity-75")) - ) - ) - - inline def modalSelector: HtmlElement = - div( - cls("fixed inset-0 z-10 overflow-y-auto"), - div( - cls("text-center sm:block sm:p-0"), - overlay, - browserCenteringModalTrick, - child <-- currentFiles.map( - FileSelector(_, availableFilesStream)(updatesObserver) - ) - ) - ) - - div( - updatesStream --> selectionUpdates, - updatesStream.collect { case _: DoneEvent => - false - } --> selectorOpen.writer, - child.maybe <-- selectorOpen.signal.map(isOpen => - if isOpen then Some(modalSelector) else None - ), - div( - cls("flex flex-col space-y-5"), - child.maybe <-- currentFiles.map(files => - if files.isEmpty then None else Some(FileList(files)) - ), - button( - tpe := "button", - cls := "bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500", - "Zvolit soubory", - onClick.mapTo(true) --> selectorOpen.writer - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala deleted file mode 100644 index b43e07a..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala +++ /dev/null @@ -1,74 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.Loading -import io.laminext.syntax.core.{*, given} -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -object FileSelector: - import FilePicker._ - - def apply( - initialFiles: List[File], - availableFilesStream: EventStream[List[File]] - )(selectionUpdates: Observer[Event]): HtmlElement = - val selectedFiles = Var[Set[File]](initialFiles.to(Set)) - val availableFiles = availableFilesStream.startWithNone - // Request the files to display - selectionUpdates.onNext(AvailableFilesRequested) - div( - cls( - "inline-block transform overflow-hidden rounded-lg bg-white text-left align-bottom shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:align-middle" - ), - role("dialog"), - customHtmlAttr("aria.modal", BooleanAsTrueFalseStringCodec)(true), - aria.labelledBy("modal-headline"), - div( - cls("bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"), - div( - cls("sm:flex sm:items-start"), - div( - cls("mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"), - h3( - cls("text-lg font-medium leading-6 text-gray-900"), - idAttr("modal-headline"), - "Výběr souborů" - ) - ) - ), - child <-- availableFiles - .split(_ => ())((_, _, af) => FileTable(af, selectedFiles)) - .map(_.getOrElse(Loading)) - ), - div( - cls("bg-gray-50 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6"), - span( - cls("flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto"), - button( - typ("button"), - cls( - "focus:shadow-outline-green inline-flex w-full justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-base font-medium leading-6 text-white shadow-sm transition duration-150 ease-in-out hover:bg-indigo-500 focus:border-indigo-700 focus:outline-none sm:text-sm sm:leading-5" - ), - "Potvrdit", - composeEvents(onClick)( - _.sample(selectedFiles) - .map(SelectionUpdated(_)) - ) --> selectionUpdates - ) - ), - span( - cls("mt-3 flex w-full rounded-md shadow-sm sm:mt-0 sm:w-auto"), - button( - typ("button"), - cls( - "focus:shadow-outline-blue inline-flex w-full justify-center rounded-md border border-gray-300 bg-white px-4 py-2 text-base font-medium leading-6 text-gray-700 shadow-sm transition duration-150 ease-in-out hover:text-gray-500 focus:border-blue-300 focus:outline-none sm:text-sm sm:leading-5" - ), - "Zrušit", - onClick.mapTo(SelectionCancelled) --> selectionUpdates - ) - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala deleted file mode 100644 index aa11b2b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala +++ /dev/null @@ -1,91 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import works.iterative.ui.components.tailwind.Icons - -def FileTable( - files: Signal[List[File]], - selectedFiles: Var[Set[File]] -): HtmlElement = - val scope = customHtmlAttr("scope", StringAsIsCodec) - - def headerRow: HtmlElement = - val col = scope("col") - val baseM: Modifier[HtmlElement] = Seq(cls("px-6 py-3"), col) - val textH = cls( - "text-left text-xs font-medium text-gray-500 uppercase tracking-wider" - ) - tr( - th(baseM, span(cls("sr-only"), "Vybrat")), - th(baseM, textH, "Název"), - th(baseM, cls("relative"), span(cls("sr-only"), "Otevřít")) - ) - - def tableRow( - f: File, - idx: Int, - selected: Boolean - )(toggleSelection: Observer[Unit]): HtmlElement = - val baseC = cls("px-6 py-4 whitespace-nowrap text-sm") - tr( - cls(if idx % 2 == 0 then "bg-gray-50" else "bg-white"), - td( - cls("font-medium cursor-pointer"), - onClick.mapTo(()) --> toggleSelection, - cls(if selected then "text-green-900" else "text-gray-200"), - Icons.outline.`check-circle`().amend(svg.cls := "mx-auto"), - span(cls("sr-only"), if selected then "Vybráno" else "Nevybráno") - ), - td( - baseC, - cls("font-medium text-gray-900"), - f.name, - onClick.mapTo(()) --> toggleSelection - ), - td( - baseC, - cls("text-right font-medium"), - a( - href(f.url), - target("_blank"), - cls( - "flex items-center space-x-4 text-indigo-600 hover:text-indigo-900" - ), - Icons.outline.`external-link`(), - "Otevřít" - ) - ) - ) - - div( - cls("flex flex-col"), - div(cls("overflow-x-auto sm:-mx-6 lg:-mx-8")), - div( - cls("py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8"), - div( - cls("shadow overflow-hidden border-b border-gray-200 sm:rounded-lg"), - table( - cls("min-w-full divide-y divide-gray-200"), - thead( - cls("bg-gray-50"), - headerRow - ), - tbody( - children <-- files - .map(_.zipWithIndex) - .combineWithFn(selectedFiles)((f, sel) => - f.map((file, idx) => - val active = sel.contains(file) - tableRow(file, idx, active)( - selectedFiles.writer - .contramap(_ => if active then sel - file else sel + file) - ) - ) - ) - ) - ) - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala deleted file mode 100644 index 0fc8796..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala +++ /dev/null @@ -1,32 +0,0 @@ -package works.iterative.ui.components.tailwind - -import CustomAttrs.ariaHidden -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -// TODO: render icon or picture based on img signal -class Avatar($avatarImg: Signal[Option[String]]): - inline def avatarPlaceholder(size: Int): HtmlElement = - div( - cls := s"rounded-full text-indigo-200 bg-indigo-500 h-${size} w-${size} flex items-center justify-center", - Icons.outline.user(size - 2) - ) - - inline def avatarImage(size: Int): Signal[HtmlElement] = - $avatarImg.split(_ => ())((_, _, $url) => - img( - cls := s"w-$size h-$size rounded-full", - src <-- $url, - alt := "" - ) - ).map(_.getOrElse(avatarPlaceholder(size))) - - inline def avatar(size: Int): HtmlElement = - div( - cls := "relative", - child <-- avatarImage(size), - span( - cls := "absolute inset-0 shadow-inner rounded-full", - ariaHidden := true - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala deleted file mode 100644 index bcbed88..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala +++ /dev/null @@ -1,77 +0,0 @@ -package works.iterative.ui.components.tailwind - -enum ColorWeight(value: String): - inline def toCSS: String = value - // To be used for black and white until - // we support better mechanism via extension methods - case w__ extends ColorWeight("") - case w50 extends ColorWeight("50") - case w100 extends ColorWeight("100") - case w200 extends ColorWeight("200") - case w300 extends ColorWeight("300") - case w400 extends ColorWeight("400") - case w500 extends ColorWeight("500") - case w600 extends ColorWeight("600") - case w700 extends ColorWeight("700") - case w800 extends ColorWeight("800") - case w900 extends ColorWeight("900") - -enum Color(name: String): - import ColorWeight._ - - inline def toCSSNoColorWeight(prefix: String): String = - s"${prefix}-${name}" - inline def toCSSWithColorWeight( - prefix: String, - weight: ColorWeight - ): String = - s"${prefix}-${name}-${weight.toCSS}" - inline def toCSS(prefix: String)(weight: ColorWeight): String = - weight match { - case `w__` => toCSSNoColorWeight(prefix) - case _ => toCSSWithColorWeight(prefix, weight) - } - inline def bg: ColorWeight => String = toCSS("bg")(_) - inline def text: ColorWeight => String = toCSS("text")(_) - inline def decoration: ColorWeight => String = toCSS("decoration")(_) - inline def border: ColorWeight => String = toCSS("border")(_) - inline def outline: ColorWeight => String = toCSS("outline")(_) - inline def divide: ColorWeight => String = toCSS("divide")(_) - inline def ring: ColorWeight => String = toCSS("ring")(_) - inline def ringOffset: ColorWeight => String = toCSS("ring-offset")(_) - inline def shadow: ColorWeight => String = toCSS("shadow")(_) - inline def accent: ColorWeight => String = toCSS("accent")(_) - - // TODO: change the "stupid" methods to extension methods - // that will keep the invariants in comments lower - case current extends Color("current") - case inherit extends Color("inherit") - // Not present in for all methods - case transp extends Color("transparent") - // Seen in accent, not preset otherwise - case auto extends Color("auto") - // Black and white do not have weight - case black extends Color("black") - case white extends Color("white") - case slate extends Color("slate") - case gray extends Color("gray") - case zinc extends Color("zinc") - case neutral extends Color("neutral") - case stone extends Color("stone") - case red extends Color("red") - case orange extends Color("orange") - case amber extends Color("amber") - case yellow extends Color("yellow") - case lime extends Color("lime") - case green extends Color("green") - case emerald extends Color("emerald") - case teal extends Color("teal") - case cyan extends Color("cyan") - case sky extends Color("sky") - case blue extends Color("blue") - case indigo extends Color("indigo") - case violet extends Color("violet") - case purple extends Color("purple") - case fuchsia extends Color("fuchsia") - case pink extends Color("pink") - case rose extends Color("rose") diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala deleted file mode 100644 index 6fb453c..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -object CustomAttrs { - // Made a pull request to add aria-current to scala-dom-types, remove after - val ariaCurrent = customHtmlAttr("aria-current", StringAsIsCodec) - val ariaHidden = customHtmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - - val datetime = customHtmlAttr("datetime", StringAsIsCodec) - - object svg { - import com.raquo.laminar.api.L.svg.{*, given} - val ariaHidden = - customSvgAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - } -} diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala deleted file mode 100644 index 6cda48b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala +++ /dev/null @@ -1,28 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -object Display: - - enum Breakpoint: - case sm, md, lg, xl, `2xl` - - enum DisplayClass: - case block, `inline-block`, `inline`, flex, `inline-flex`, table, - `inline-table`, `table-caption` - - object ShowUpFrom: - inline def apply( - br: Breakpoint, - dc: DisplayClass = DisplayClass.block - ): HtmlElement = - div( - cls := "hidden", - cls := s"${br}:${dc}" - ) - - object HideUpTo: - inline def apply(br: Breakpoint): HtmlElement = - div( - cls := s"${br}:hidden" - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala deleted file mode 100644 index 4a8b065..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala +++ /dev/null @@ -1,270 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec -import com.raquo.domtypes.generic.defs.attrs.AriaAttrs -import com.raquo.laminar.api.L.svg.{*, given} -import com.raquo.laminar.builders.SvgBuilders -import com.raquo.laminar.keys.ReactiveSvgAttr - -// TODO: fix sizes, colors, hover and stuff, normalize and amend on call site -object Icons: - object aria: - inline def hidden = CustomAttrs.svg.ariaHidden - - object outline: - val defaultSize: Int = 6 - - inline def bell(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" - ) - ) - - inline def `check-circle`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" - ) - ) - - inline def `document-add`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M9 13h6m-3-3v6m5 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" - ) - ) - - inline def `external-link`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" - ) - ) - - inline def menu(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M4 6h16M4 12h16M4 18h16" - ) - ) - - inline def `status-offline`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M18.364 5.636a9 9 0 010 12.728m0 0l-2.829-2.829m2.829 2.829L21 21M15.536 8.464a5 5 0 010 7.072m0 0l-2.829-2.829m-4.243 2.829a4.978 4.978 0 01-1.414-2.83m-1.414 5.658a9 9 0 01-2.167-9.238m7.824 2.167a1 1 0 111.414 1.414m-1.414-1.414L3 3m8.293 8.293l1.414 1.414" - ) - ) - - inline def user(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" - ) - ) - - inline def x(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M6 18L18 6M6 6l12 12" - ) - ) - - end outline - - object solid: - val defaultSize: Int = 5 - - inline def users(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - d := "M9 6a3 3 0 11-6 0 3 3 0 016 0zM17 6a3 3 0 11-6 0 3 3 0 016 0zM12.93 17c.046-.327.07-.66.07-1a6.97 6.97 0 00-1.5-4.33A5 5 0 0119 16v1h-6.07zM6 11a5 5 0 015 5v1H1v-1a5 5 0 015-5z" - ) - ) - - inline def `location-marker`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M5.05 4.05a7 7 0 119.9 9.9L10 18.9l-4.95-4.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z", - clipRule := "evenodd" - ) - ) - - inline def calendar(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z", - clipRule := "evenodd" - ) - ) - - inline def `chevron-right`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z", - clipRule := "evenodd" - ) - ) - - inline def search(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z", - clipRule := "evenodd" - ) - ) - - inline def filter(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M3 3a1 1 0 011-1h12a1 1 0 011 1v3a1 1 0 01-.293.707L12 11.414V15a1 1 0 01-.293.707l-2 2A1 1 0 018 17v-5.586L3.293 6.707A1 1 0 013 6V3z", - clipRule := "evenodd" - ) - ) - - inline def `arrow-narrow-left`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M7.707 14.707a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 1.414L5.414 9H17a1 1 0 110 2H5.414l2.293 2.293a1 1 0 010 1.414z", - clipRule := "evenodd" - ) - ) - - inline def home(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - d := "M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z" - ) - ) - - inline def paperclip(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size} text-gray-400", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M8 4a3 3 0 00-3 3v4a5 5 0 0010 0V7a1 1 0 112 0v4a7 7 0 11-14 0V7a5 5 0 0110 0v4a3 3 0 11-6 0V7a1 1 0 012 0v4a1 1 0 102 0V7a3 3 0 00-3-3z", - clipRule := "evenodd" - ) - ) - - end solid -end Icons diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/LinkSupport.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/LinkSupport.scala deleted file mode 100644 index 88f0b9a..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/LinkSupport.scala +++ /dev/null @@ -1,12 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.jsdom.defs.events.TypedTargetMouseEvent - -object LinkSupport: - - extension [El <: org.scalajs.dom.EventTarget]( - ep: EventProcessor[TypedTargetMouseEvent[El], TypedTargetMouseEvent[El]] - ) - def noKeyMod = - ep.filter(ev => !(ev.ctrlKey || ev.metaKey || ev.shiftKey || ev.altKey)) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Loader.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Loader.scala deleted file mode 100644 index 5399933..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Loader.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -// TODO: proper loader -def Loading = - div( - cls := "bg-gray-50 overflow-hidden rounded-lg", - div( - cls := "px-4 py-5 sm:p-6", - "Loading..." - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Renderable.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Renderable.scala deleted file mode 100644 index a0fcc5b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Renderable.scala +++ /dev/null @@ -1,6 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -trait Renderable[A]: - extension (a: A) def toHtml: HtmlElement diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/ComboBox.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/ComboBox.scala deleted file mode 100644 index 8c5b8a1..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/ComboBox.scala +++ /dev/null @@ -1,92 +0,0 @@ -package works.iterative.ui.components.tailwind -package form - -import com.raquo.laminar.api.L.{*, given} -import io.laminext.syntax.core.* - -case class ComboBox( - id: String, - options: Signal[List[ComboBox.Option]], - valueUpdates: Observer[List[String]] -) - -object ComboBox: - - extension (m: ComboBox) - def toHtml: HtmlElement = - val isOpen = Var(false) - div( - cls := "relative mt-1", - input( - idAttr := m.id, - tpe := "text", - cls := "w-full rounded-md border border-gray-300 bg-white py-2 pl-3 pr-12 shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 sm:text-sm", - role := "combobox", - aria.controls := "options", - aria.expanded := false, - onClick.mapTo(true) --> isOpen.writer - ), - button( - tpe := "button", - cls := "absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-none", { - import svg.* - svg( - cls := "h-5 w-5 text-gray-400", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - CustomAttrs.svg.ariaHidden := true, - path( - fillRule := "evenodd", - d := "M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z", - clipRule := "evenodd" - ) - ) - } - ), - ul( - cls <-- isOpen.signal.switch("", "hidden"), - cls := "absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm", - idAttr := "options", - role := "listbox", - children <-- m.options.map(_.map(_.toHtml)) - ) - ) - - case class Option(value: String, active: Boolean) - - object Option: - extension (m: Option) - def toHtml: HtmlElement = - li( - cls := "relative cursor-default select-none py-2 pl-8 pr-4", - cls := (if m.active then "text-white bg-indigo-600" - else "text-gray-900"), - idAttr := "option-0", - role := "option", - tabIndex := -1, - span( - cls := "block truncate", - m.value - ), - if m.active then - span( - cls := "absolute inset-y-0 left-0 flex items-center pl-1.5", - cls := (if m.active then "text-white" else "text-indigo-600"), { - import svg.* - svg( - cls := "h-5 w-5", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - CustomAttrs.svg.ariaHidden := true, - path( - fillRule := "evenodd", - d := "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", - clipRule := "evenodd" - ) - ) - } - ) - else emptyNode - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala deleted file mode 100644 index e7e6627..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.akka - -// Base class for all command-processing related exceptions from handlers -sealed abstract class CommandHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandNotAvailable[C, S](cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd není dostupný ve stavu $state" - ) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandRejected[C, S](reason: String, cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd byl ve stavu $state odmítnut s odůvodněním $reason" - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala deleted file mode 100644 index 72a5bf9..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.akka - -sealed abstract class EventHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -case class UnhandledEvent[Event, State](event: Event, state: State) - extends EventHandlerException( - s"Událost $event nastala ve stavu $state bez možnosti zpracování" - ) diff --git a/iw/bom.sc b/iw/bom.sc deleted file mode 100644 index e9266b8..0000000 --- a/iw/bom.sc +++ /dev/null @@ -1,227 +0,0 @@ -import mill._, scalalib._ - -object IWMaterials { - - object Versions { - val akka = "2.6.16" - val akkaHttp = "10.2.4" - val cats = "2.7.0" - val elastic4s = "7.12.2" - val http4s = "0.23.10" - val http4sPac4J = "4.0.0" - val laminar = "0.14.2" - val laminext = laminar - val logbackClassic = "1.2.10" - val pac4j = "5.2.0" - val play = "2.8.8" - val playJson = "2.9.2" - val scalaTest = "3.2.9" - val slick = "3.3.3" - val sttpClient = "3.5.0" - val tapir = "0.20.1" - val urlDsl = "0.4.0" - val waypoint = "0.5.0" - val zio = "2.0.0-RC2" - val zioConfig = "3.0.0-RC2" - val zioInteropCats = "3.3.0-RC2" - val zioJson = "0.3.0-RC3" - val zioLogging = "2.0.0-RC5" - val zioPrelude = "1.0.0-RC10" - val zioZMX = "0.0.11" - } - - object Deps extends AkkaLibs with SlickLibs { - import IWMaterials.{Versions => V} - - val zioOrg = "dev.zio" - - def zioLib(name: String, version: String): Dep = - ivy"$zioOrg::zio-$name::$version" - - lazy val zio: Dep = ivy"$zioOrg::zio:${V.zio}" - - lazy val zioTest: Dep = zioLib("test", V.zio) - lazy val zioTestSbt: Dep = zioLib("test", V.zio) - - lazy val zioConfig: Dep = zioLib("config", V.zioConfig) - lazy val zioConfigTypesafe: Dep = - zioLib("config-typesafe", V.zioConfig) - lazy val zioConfigMagnolia: Dep = - zioLib("config-magnolia", V.zioConfig) - - lazy val zioJson: Dep = zioLib("json", V.zioJson) - lazy val zioLogging: Dep = zioLib("logging", V.zioLogging) - lazy val zioLoggingSlf4j: Dep = - zioLib("logging-slf4j", V.zioLogging) - lazy val zioPrelude: Dep = zioLib("prelude", V.zioPrelude) - lazy val zioStreams: Dep = zioLib("streams", V.zio) - lazy val zioZMX: Dep = zioLib("zmx", V.zioZMX) - lazy val zioInteropCats: Dep = - zioLib("interop-cats", V.zioInteropCats) - - /* What is the equivalent? ZIOModule with prepared test config? - def useZIO(testConf: Configuration*): Agg[Dep] = Agg( - zio, - zioTest, - zioTestSbt, - testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework") - ) - */ - - /* - def useZIOAll(testConf: Configuration*): Seq[Def.Setting[_]] = - useZIO(testConf: _*) ++ Seq( - zioStreams, - zioConfig, - zioConfigTypesafe, - zioConfigMagnolia, - zioJson, - zioZMX, - zioLogging, - zioPrelude - ) - */ - - private val tapirOrg = "com.softwaremill.sttp.tapir" - def tapirLib(name: String): Dep = - ivy"${tapirOrg}::tapir-$name::${V.tapir}" - - lazy val tapirCore: Dep = tapirLib("core") - lazy val tapirZIO: Dep = tapirLib("zio") - lazy val tapirZIOJson: Dep = tapirLib("json-zio") - lazy val tapirSttpClient: Dep = tapirLib("sttp-client") - lazy val tapirCats: Dep = tapirLib("cats") - lazy val tapirZIOHttp4sServer: Dep = tapirLib("zio-http4s-server") - - private val sttpClientOrg = "com.softwaremill.sttp.client3" - def sttpClientLib(name: String): Dep = - ivy"${sttpClientOrg}::${name}:${V.sttpClient}" - - lazy val sttpClientCore: Dep = sttpClientLib("core") - - lazy val http4sBlazeServer: Dep = - ivy"org.http4s::http4s-blaze-server:${V.http4s}" - - lazy val http4sPac4J: Dep = - ivy"org.pac4j::http4s-pac4j:${V.http4sPac4J}" - lazy val pac4jOIDC: Dep = - ivy"org.pac4j:pac4j-oidc:${V.pac4j}" - - lazy val scalaTest: Dep = - ivy"org.scalatest::scalatest:${V.scalaTest}" - lazy val scalaTestPlusScalacheck: Dep = - ivy"org.scalatestplus::scalacheck-1-15:3.2.9.0" - lazy val playScalaTest: Dep = - ivy"org.scalatestplus.play::scalatestplus-play:5.1.0" - - private val playOrg = "com.typesafe.play" - lazy val playMailer: Dep = ivy"${playOrg}::play-mailer:8.0.1" - lazy val playServer: Dep = ivy"${playOrg}::play-server:${V.play}" - lazy val playAkkaServer: Dep = - ivy"${playOrg}::play-akka-http-server:${V.play}" - lazy val play: Dep = ivy"${playOrg}::play:${V.play}" - lazy val playAhcWs: Dep = ivy"${playOrg}::play-ahc-ws:${V.play}" - lazy val playJson: Dep = ivy"${playOrg}::play-json:${V.playJson}" - - private val elastic4sOrg = "com.sksamuel.elastic4s" - lazy val useElastic4S: Agg[Dep] = Agg( - ivy"${elastic4sOrg}::elastic4s-core:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-client-akka:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-http-streams:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-json-play:${V.elastic4s}" - ) - - lazy val laminar: Dep = ivy"com.raquo::laminar::${V.laminar}" - - private def laminext(name: String): Dep = - ivy"io.laminext::$name::${V.laminar}" - - lazy val laminextCore: Dep = laminext("core") - lazy val laminextTailwind: Dep = laminext("tailwind") - lazy val laminextFetch: Dep = laminext("fetch") - lazy val laminextValidationCore: Dep = laminext("validation-core") - lazy val laminextUI: Dep = laminext("ui") - - lazy val waypoint: Dep = - ivy"com.raquo::waypoint::${V.waypoint}" - - lazy val urlDsl: Dep = - ivy"be.doeraene::url-dsl::${V.urlDsl}" - - lazy val scalaJavaTime: Dep = - ivy"io.github.cquiroz::scala-java-time::2.3.0" - - lazy val scalaJavaLocales: Dep = - ivy"io.github.cquiroz::scala-java-locales::1.2.1" - - lazy val logbackClassic: Dep = - ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" - } - - trait AkkaLibs { - - self: SlickLibs => - - object akka { - val V = Versions.akka - val tOrg = "com.typesafe.akka" - val lOrg = "com.lightbend.akka" - - def akkaMod(name: String): Dep = - ivy"$tOrg::akka-$name:$V" - - lazy val actor: Dep = akkaMod("actor") - lazy val actorTyped: Dep = akkaMod("actor-typed") - lazy val stream: Dep = akkaMod("stream") - lazy val persistence: Dep = akkaMod("persistence-typed") - lazy val persistenceQuery: Dep = akkaMod("persistence-query") - lazy val persistenceJdbc: Dep = - ivy"$lOrg::akka-persistence-jdbc:5.0.4" - val persistenceTestKit: Dep = ivy"$tOrg::akka-persistence-testkit:$V" - - object http { - val V = Versions.akkaHttp - - lazy val http: Dep = ivy"$tOrg::akka-http:$V" - lazy val sprayJson: Dep = ivy"$tOrg::akka-http-spray-json:$V" - - } - - object projection { - val V = "1.2.2" - - lazy val core: Dep = - ivy"$lOrg::akka-projection-core:$V" - lazy val eventsourced: Dep = - ivy"$lOrg::akka-projection-eventsourced:$V" - lazy val slick: Dep = - ivy"$lOrg::akka-projection-slick:$V" - lazy val jdbc: Dep = - ivy"$lOrg::akka-projection-jdbc:$V" - } - - object profiles { - // TODO: deal with cross-version for Scala3 (for3Use2_13) - lazy val eventsourcedJdbcProjection: Agg[Dep] = Agg( - persistenceQuery, - projection.core, - projection.eventsourced, - projection.slick, - persistenceJdbc - ) ++ slick.default - } - } - } - - trait SlickLibs { - object slick { - val V = IWMaterials.Versions.slick - val org = "com.typesafe.slick" - - lazy val slick: Dep = ivy"$org::slick:$V" - lazy val hikaricp: Dep = ivy"$org::slick-hikaricp:$V" - lazy val default: Agg[Dep] = Agg(slick, hikaricp) - } - } - -} diff --git a/iw/build.sc b/iw/build.sc deleted file mode 100644 index fbbc3b8..0000000 --- a/iw/build.sc +++ /dev/null @@ -1,144 +0,0 @@ -import mill._, scalalib._, scalajslib._ - -import $file.bom - -object support { - val Deps = bom.IWMaterials.Deps - val Versions = bom.IWMaterials.Versions - - trait CommonModule extends ScalaModule { - def scalaVersion = "3.1.1" - def scalacOptions = Seq( - "-encoding", - "utf8", - "-deprecation", - "-explain-types", - "-explain", - "-feature", - "-language:experimental.macros", - "-language:higherKinds", - "-language:implicitConversions", - "-unchecked", - "-Xfatal-warnings", - "-Ykind-projector" - ) - } - - trait CommonJSModule extends CommonModule with ScalaJSModule { - def scalaJSVersion = "1.8.0" - } - - trait CrossPlatformModule extends Module { outer => - def ivyDeps: T[Agg[Dep]] = Agg[Dep]() - def jsDeps: T[Agg[Dep]] = Agg[Dep]() - def jvmDeps: T[Agg[Dep]] = Agg[Dep]() - def moduleDeps: Seq[Module] = Seq[Module]() - - trait PlatformModule extends CommonModule { - def platform: String - override def millSourcePath = outer.millSourcePath - override def ivyDeps = outer.ivyDeps() ++ (platform match { - case "js" => jsDeps() - case _ => jvmDeps() - }) - override def moduleDeps = outer.moduleDeps.collect { - case m: CrossPlatformModule => - platform match { - case "js" => m.js - case _ => m.jvm - } - case m: JavaModule => m - } - } - - trait JsModule extends PlatformModule with CommonJSModule { - def platform = "js" - } - - trait JvmModule extends PlatformModule { - def platform = "jvm" - } - - val js: JsModule - val jvm: JvmModule - } - - trait PureCrossModule extends CrossPlatformModule { - override object js extends JsModule - override object jvm extends JvmModule - } - - trait PureCrossSbtModule extends CrossPlatformModule { - override object js extends JsModule with SbtModule - override object jvm extends JvmModule with SbtModule - } - - trait FullCrossSbtModule extends CrossPlatformModule { - trait FullSources extends JavaModule { self: PlatformModule => - override def sources = T.sources( - millSourcePath / platform / "src" / "main" / "scala", - millSourcePath / "shared" / "src" / "main" / "scala" - ) - - override def resources = T.sources( - millSourcePath / platform / "src" / "main" / "resources", - millSourcePath / "shared" / "src" / "main" / "resources" - ) - } - - override object js extends JsModule with FullSources - override object jvm extends JvmModule with FullSources - } - -} - -import support._ - -object mongo extends CommonModule { - def ivyDeps = Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - ivy"org.mongodb.scala::mongo-scala-driver:4.2.3".withDottyCompat( - scalaVersion() - ) - ) -} - -object tapir extends FullCrossSbtModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson, Deps.zioJson) - def jvmDeps = Agg(Deps.tapirZIO, Deps.tapirZIOHttp4sServer) - def jsDeps = Agg(Deps.tapirSttpClient) -} - -object akkaPersistence extends CommonModule { - def millSourcePath = build.millSourcePath / "akka-persistence" - def ivyDeps = (Agg( - Deps.akka.persistenceQuery, - Deps.akka.persistenceJdbc, - Deps.akka.projection.core, - Deps.akka.projection.eventsourced, - Deps.akka.projection.slick - ) ++ Deps.slick.default).map(_.withDottyCompat(scalaVersion())) ++ Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - Deps.akka.persistence.withDottyCompat(scalaVersion()), - ivy"com.typesafe.akka::akka-cluster-sharding-typed:${Deps.akka.V}" - .withDottyCompat(scalaVersion()) - ) -} - -object ui extends CommonJSModule { - def ivyDeps = Agg( - Deps.zio, - Deps.laminar, - Deps.zioJson, - Deps.waypoint, - Deps.urlDsl, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) -} diff --git a/iw/domain.sc b/iw/domain.sc deleted file mode 100644 index b09c56b..0000000 --- a/iw/domain.sc +++ /dev/null @@ -1,78 +0,0 @@ -import mill._, scalalib._ - -import $file.{build => ff}, ff.support._ - -trait DomainModule extends Module { - // General extra model deps - def modelModules: Seq[Module] = Seq.empty - // General extra codecs deps - def codecsModules: Seq[Module] = Seq.empty - // General extra endpoints deps - def endpointsModules: Seq[Module] = Seq.empty - - // Implementation deps for repo - def repoModules: Seq[JavaModule] = Seq.empty - - object shared extends Module { - object model extends PureCrossModule { - def moduleDeps = modelModules - def ivyDeps = Agg(Deps.zioPrelude) - } - object codecs extends PureCrossModule { - def moduleDeps = Seq(model) ++ codecsModules - def ivyDeps = Agg(Deps.zioJson) - } - } - - trait CommonProjects extends Module { - object model extends PureCrossModule { - def ivyDeps = Agg(Deps.zioPrelude) - def moduleDeps = Seq(shared.model) - } - object codecs extends PureCrossModule { - def moduleDeps = - Seq(model, shared.model, shared.codecs, ff.tapir) - } - object endpoints extends PureCrossModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson) - def moduleDeps = Seq(model, codecs) ++ endpointsModules - } - object client extends CommonJSModule { - def moduleDeps = Seq(endpoints.js) - } - object components extends CommonJSModule { - def ivyDeps = Agg( - Deps.laminar, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) - def moduleDeps = Seq(model.js, codecs.js, client, ff.ui) - } - } - - object query extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(repo, query.endpoints.jvm) - } - object repo extends CommonModule { - def ivyDeps = Agg(Deps.zio) - def moduleDeps = Seq(model.jvm, codecs.jvm) ++ repoModules - } - object projection extends CommonModule { - def moduleDeps = Seq(repo, ff.akkaPersistence) - } - } - - object command extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(entity, command.endpoints.jvm) - } - object entity extends CommonModule { - def moduleDeps = Seq(model.jvm, codecs.jvm, ff.akkaPersistence) - } - } -} diff --git a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala b/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala deleted file mode 100644 index 0fa3493..0000000 --- a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala +++ /dev/null @@ -1,53 +0,0 @@ -package works.iterative.mongo - -import zio.* -import zio.json.* -import zio.config.* -import org.mongodb.scala.* -import org.mongodb.scala.model.Filters.* -import org.bson.json.JsonObject -import org.mongodb.scala.model.ReplaceOptions -import org.mongodb.scala.bson.conversions.Bson - -case class MongoConfig(uri: String) - -object MongoConfig: - val configDesc = - import ConfigDescriptor.* - nested("MONGO")(string("URI").default("mongodb://localhost:27017")) - .to[MongoConfig] - val fromEnv = ZConfig.fromSystemEnv(configDesc) - -extension (m: MongoClient.type) - def layer: RLayer[MongoConfig, MongoClient] = - ZIO - .serviceWithZIO[MongoConfig](c => Task.attempt(MongoClient(c.uri))) - .toLayer - -class MongoJsonRepository[Elem, Key, Criteria]( - collection: MongoCollection[JsonObject], - toFilter: Criteria => Bson, - idFilter: Elem => (String, Key) -)(using JsonCodec[Elem]) { - def matching(criteria: Criteria): Task[Seq[Elem]] = - val filter = toFilter(criteria) - val query = collection.find(filter) - - for - result <- ZIO.fromFuture(_ => query.toFuture) - proof <- ZIO.collect(result)(j => - ZIO.fromOption(j.getJson.fromJson[Elem].toOption) - ) - yield proof - - def put(elem: Elem): Task[Unit] = - Task.async(cb => - collection - .replaceOne( - equal.tupled(idFilter(elem)), - JsonObject(elem.toJson), - ReplaceOptions().upsert(true) - ) - .subscribe(_ => cb(Task.unit), t => cb(Task.fail(t))) - ) -} diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala deleted file mode 100644 index 68d7196..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala +++ /dev/null @@ -1,12 +0,0 @@ -package works.iterative.tapir - -import sttp.model.Uri - -opaque type BaseUri = Option[Uri] - -object BaseUri: - - def apply(optU: Option[Uri]): BaseUri = optU - def apply(u: Uri): BaseUri = Some(u) - - extension (v: BaseUri) def toUri: Option[Uri] = v diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 653bc51..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,22 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.client.sttp.SttpClientInterpreter -import sttp.tapir.PublicEndpoint -import sttp.tapir.client.sttp.WebSocketToPipe -import scala.concurrent.Future -import sttp.client3.SttpBackend -import sttp.capabilities.WebSockets - -trait CustomTapirPlatformSpecific extends SttpClientInterpreter: - self: CustomTapir => - - type Backend = SttpBackend[Future, WebSockets] - - def makeClient[I, E, O]( - endpoint: PublicEndpoint[I, E, O, Any] - )(using - baseUri: BaseUri, - backend: Backend, - wsToPipe: WebSocketToPipe[Any] - ): I => Future[O] = - toClientThrowErrors(endpoint, baseUri.toUri, backend) diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 2545fbd..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,5 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.ztapir.ZTapir - -trait CustomTapirPlatformSpecific extends ZTapir diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala deleted file mode 100644 index 55e4ca1..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala +++ /dev/null @@ -1,7 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.server.http4s.ztapir.ZHttp4sServerInterpreter - -trait Http4sCustomTapir[Env] - extends CustomTapir - with ZHttp4sServerInterpreter[Env] diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala deleted file mode 100644 index dd52e9b..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala +++ /dev/null @@ -1,14 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.Tapir -import sttp.tapir.json.zio.TapirJsonZio -import sttp.tapir.TapirAliases - -trait CustomTapir - extends Tapir - with TapirJsonZio - with TapirAliases - with CustomTapirPlatformSpecific: - given Schema[ServerError] = Schema.derived - -object CustomTapir extends CustomTapir diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala deleted file mode 100644 index 715591d..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.tapir - -import zio.json.* - -sealed trait ServerError -case class InternalServerError(msg: String) extends ServerError -object InternalServerError: - def fromThrowable(t: Throwable): ServerError = InternalServerError( - t.getMessage - ) - -object ServerError: - given JsonCodec[ServerError] = DeriveJsonCodec.gen diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/File.scala deleted file mode 100644 index 09f6c07..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala +++ /dev/null @@ -1,3 +0,0 @@ -package works.iterative.services.files - -case class File(url: String, name: String) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala deleted file mode 100644 index 46633a5..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala +++ /dev/null @@ -1,25 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.Renderable - -given Renderable[File] with - extension (m: File) - def toHtml: HtmlElement = - li( - cls("pl-3 pr-4 py-3 flex items-center justify-between text-sm"), - div( - cls("w-0 flex-1 flex items-center"), - Icons.solid - .paperclip() - .amend(svg.cls := "flex-shrink-0 text-gray-400"), - span(cls("ml-2 flex-1 w-0 truncate"), m.name) - ), - a( - href(m.url), - cls("font-medium text-indigo-600 hover:text-indigo-500"), - "Otevřít" - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala deleted file mode 100644 index ac59db8..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import io.laminext.syntax.core.{*, given} - -def FileList(files: List[File]): HtmlElement = - ul( - role("list"), - cls("border border-gray-200 rounded-md divide-y divide-gray-200"), - files.map(_.toHtml) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala deleted file mode 100644 index 2e8f690..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala +++ /dev/null @@ -1,81 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import io.laminext.syntax.core.{*, given} - -object FilePicker: - sealed trait Event - sealed trait DoneEvent extends Event - case class SelectionUpdated(files: Set[File]) extends DoneEvent - case object SelectionCancelled extends DoneEvent - case object AvailableFilesRequested extends Event - - def apply( - currentFiles: Signal[List[File]], - availableFilesStream: EventStream[List[File]] - )(selectionUpdates: Observer[Event]): HtmlElement = - val (updatesStream, updatesObserver) = EventStream.withObserver[Event] - val selectorOpen = Var[Boolean](false) - - // This sequence tricks browser into displaying modal content centered - // Inspired by modal in headless ui playground - // https://github.com/tailwindlabs/headlessui/blob/fdd26297953080d5ec905dda0bf5ec9607897d86/packages/playground-react/pages/transitions/component-examples/modal.tsx#L78-L79 - inline def browserCenteringModalTrick: Modifier[HtmlElement] = - Seq[Modifier[HtmlElement]]( - span(cls("hidden sm:inline-block sm:h-screen sm:align-middle")), - "​" // Zero width space - ) - - inline def overlay: Modifier[HtmlElement] = - // Page overlay - /* TODO: transition - enter="ease-out duration-300" - enterFrom="opacity-0" - enterTo="opacity-100" - leave="ease-in duration-200" - leaveFrom="opacity-100" - leaveTo="opacity-0" - */ - div( - div( - onClick.mapTo(false) --> selectorOpen.writer, - cls("fixed inset-0 transition-opacity"), - div(cls("absolute inset-0 bg-gray-500 opacity-75")) - ) - ) - - inline def modalSelector: HtmlElement = - div( - cls("fixed inset-0 z-10 overflow-y-auto"), - div( - cls("text-center sm:block sm:p-0"), - overlay, - browserCenteringModalTrick, - child <-- currentFiles.map( - FileSelector(_, availableFilesStream)(updatesObserver) - ) - ) - ) - - div( - updatesStream --> selectionUpdates, - updatesStream.collect { case _: DoneEvent => - false - } --> selectorOpen.writer, - child.maybe <-- selectorOpen.signal.map(isOpen => - if isOpen then Some(modalSelector) else None - ), - div( - cls("flex flex-col space-y-5"), - child.maybe <-- currentFiles.map(files => - if files.isEmpty then None else Some(FileList(files)) - ), - button( - tpe := "button", - cls := "bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500", - "Zvolit soubory", - onClick.mapTo(true) --> selectorOpen.writer - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala deleted file mode 100644 index b43e07a..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala +++ /dev/null @@ -1,74 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.Loading -import io.laminext.syntax.core.{*, given} -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -object FileSelector: - import FilePicker._ - - def apply( - initialFiles: List[File], - availableFilesStream: EventStream[List[File]] - )(selectionUpdates: Observer[Event]): HtmlElement = - val selectedFiles = Var[Set[File]](initialFiles.to(Set)) - val availableFiles = availableFilesStream.startWithNone - // Request the files to display - selectionUpdates.onNext(AvailableFilesRequested) - div( - cls( - "inline-block transform overflow-hidden rounded-lg bg-white text-left align-bottom shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:align-middle" - ), - role("dialog"), - customHtmlAttr("aria.modal", BooleanAsTrueFalseStringCodec)(true), - aria.labelledBy("modal-headline"), - div( - cls("bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"), - div( - cls("sm:flex sm:items-start"), - div( - cls("mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"), - h3( - cls("text-lg font-medium leading-6 text-gray-900"), - idAttr("modal-headline"), - "Výběr souborů" - ) - ) - ), - child <-- availableFiles - .split(_ => ())((_, _, af) => FileTable(af, selectedFiles)) - .map(_.getOrElse(Loading)) - ), - div( - cls("bg-gray-50 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6"), - span( - cls("flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto"), - button( - typ("button"), - cls( - "focus:shadow-outline-green inline-flex w-full justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-base font-medium leading-6 text-white shadow-sm transition duration-150 ease-in-out hover:bg-indigo-500 focus:border-indigo-700 focus:outline-none sm:text-sm sm:leading-5" - ), - "Potvrdit", - composeEvents(onClick)( - _.sample(selectedFiles) - .map(SelectionUpdated(_)) - ) --> selectionUpdates - ) - ), - span( - cls("mt-3 flex w-full rounded-md shadow-sm sm:mt-0 sm:w-auto"), - button( - typ("button"), - cls( - "focus:shadow-outline-blue inline-flex w-full justify-center rounded-md border border-gray-300 bg-white px-4 py-2 text-base font-medium leading-6 text-gray-700 shadow-sm transition duration-150 ease-in-out hover:text-gray-500 focus:border-blue-300 focus:outline-none sm:text-sm sm:leading-5" - ), - "Zrušit", - onClick.mapTo(SelectionCancelled) --> selectionUpdates - ) - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala deleted file mode 100644 index aa11b2b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala +++ /dev/null @@ -1,91 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import works.iterative.ui.components.tailwind.Icons - -def FileTable( - files: Signal[List[File]], - selectedFiles: Var[Set[File]] -): HtmlElement = - val scope = customHtmlAttr("scope", StringAsIsCodec) - - def headerRow: HtmlElement = - val col = scope("col") - val baseM: Modifier[HtmlElement] = Seq(cls("px-6 py-3"), col) - val textH = cls( - "text-left text-xs font-medium text-gray-500 uppercase tracking-wider" - ) - tr( - th(baseM, span(cls("sr-only"), "Vybrat")), - th(baseM, textH, "Název"), - th(baseM, cls("relative"), span(cls("sr-only"), "Otevřít")) - ) - - def tableRow( - f: File, - idx: Int, - selected: Boolean - )(toggleSelection: Observer[Unit]): HtmlElement = - val baseC = cls("px-6 py-4 whitespace-nowrap text-sm") - tr( - cls(if idx % 2 == 0 then "bg-gray-50" else "bg-white"), - td( - cls("font-medium cursor-pointer"), - onClick.mapTo(()) --> toggleSelection, - cls(if selected then "text-green-900" else "text-gray-200"), - Icons.outline.`check-circle`().amend(svg.cls := "mx-auto"), - span(cls("sr-only"), if selected then "Vybráno" else "Nevybráno") - ), - td( - baseC, - cls("font-medium text-gray-900"), - f.name, - onClick.mapTo(()) --> toggleSelection - ), - td( - baseC, - cls("text-right font-medium"), - a( - href(f.url), - target("_blank"), - cls( - "flex items-center space-x-4 text-indigo-600 hover:text-indigo-900" - ), - Icons.outline.`external-link`(), - "Otevřít" - ) - ) - ) - - div( - cls("flex flex-col"), - div(cls("overflow-x-auto sm:-mx-6 lg:-mx-8")), - div( - cls("py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8"), - div( - cls("shadow overflow-hidden border-b border-gray-200 sm:rounded-lg"), - table( - cls("min-w-full divide-y divide-gray-200"), - thead( - cls("bg-gray-50"), - headerRow - ), - tbody( - children <-- files - .map(_.zipWithIndex) - .combineWithFn(selectedFiles)((f, sel) => - f.map((file, idx) => - val active = sel.contains(file) - tableRow(file, idx, active)( - selectedFiles.writer - .contramap(_ => if active then sel - file else sel + file) - ) - ) - ) - ) - ) - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala deleted file mode 100644 index 0fc8796..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala +++ /dev/null @@ -1,32 +0,0 @@ -package works.iterative.ui.components.tailwind - -import CustomAttrs.ariaHidden -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -// TODO: render icon or picture based on img signal -class Avatar($avatarImg: Signal[Option[String]]): - inline def avatarPlaceholder(size: Int): HtmlElement = - div( - cls := s"rounded-full text-indigo-200 bg-indigo-500 h-${size} w-${size} flex items-center justify-center", - Icons.outline.user(size - 2) - ) - - inline def avatarImage(size: Int): Signal[HtmlElement] = - $avatarImg.split(_ => ())((_, _, $url) => - img( - cls := s"w-$size h-$size rounded-full", - src <-- $url, - alt := "" - ) - ).map(_.getOrElse(avatarPlaceholder(size))) - - inline def avatar(size: Int): HtmlElement = - div( - cls := "relative", - child <-- avatarImage(size), - span( - cls := "absolute inset-0 shadow-inner rounded-full", - ariaHidden := true - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala deleted file mode 100644 index bcbed88..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala +++ /dev/null @@ -1,77 +0,0 @@ -package works.iterative.ui.components.tailwind - -enum ColorWeight(value: String): - inline def toCSS: String = value - // To be used for black and white until - // we support better mechanism via extension methods - case w__ extends ColorWeight("") - case w50 extends ColorWeight("50") - case w100 extends ColorWeight("100") - case w200 extends ColorWeight("200") - case w300 extends ColorWeight("300") - case w400 extends ColorWeight("400") - case w500 extends ColorWeight("500") - case w600 extends ColorWeight("600") - case w700 extends ColorWeight("700") - case w800 extends ColorWeight("800") - case w900 extends ColorWeight("900") - -enum Color(name: String): - import ColorWeight._ - - inline def toCSSNoColorWeight(prefix: String): String = - s"${prefix}-${name}" - inline def toCSSWithColorWeight( - prefix: String, - weight: ColorWeight - ): String = - s"${prefix}-${name}-${weight.toCSS}" - inline def toCSS(prefix: String)(weight: ColorWeight): String = - weight match { - case `w__` => toCSSNoColorWeight(prefix) - case _ => toCSSWithColorWeight(prefix, weight) - } - inline def bg: ColorWeight => String = toCSS("bg")(_) - inline def text: ColorWeight => String = toCSS("text")(_) - inline def decoration: ColorWeight => String = toCSS("decoration")(_) - inline def border: ColorWeight => String = toCSS("border")(_) - inline def outline: ColorWeight => String = toCSS("outline")(_) - inline def divide: ColorWeight => String = toCSS("divide")(_) - inline def ring: ColorWeight => String = toCSS("ring")(_) - inline def ringOffset: ColorWeight => String = toCSS("ring-offset")(_) - inline def shadow: ColorWeight => String = toCSS("shadow")(_) - inline def accent: ColorWeight => String = toCSS("accent")(_) - - // TODO: change the "stupid" methods to extension methods - // that will keep the invariants in comments lower - case current extends Color("current") - case inherit extends Color("inherit") - // Not present in for all methods - case transp extends Color("transparent") - // Seen in accent, not preset otherwise - case auto extends Color("auto") - // Black and white do not have weight - case black extends Color("black") - case white extends Color("white") - case slate extends Color("slate") - case gray extends Color("gray") - case zinc extends Color("zinc") - case neutral extends Color("neutral") - case stone extends Color("stone") - case red extends Color("red") - case orange extends Color("orange") - case amber extends Color("amber") - case yellow extends Color("yellow") - case lime extends Color("lime") - case green extends Color("green") - case emerald extends Color("emerald") - case teal extends Color("teal") - case cyan extends Color("cyan") - case sky extends Color("sky") - case blue extends Color("blue") - case indigo extends Color("indigo") - case violet extends Color("violet") - case purple extends Color("purple") - case fuchsia extends Color("fuchsia") - case pink extends Color("pink") - case rose extends Color("rose") diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala deleted file mode 100644 index 6fb453c..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -object CustomAttrs { - // Made a pull request to add aria-current to scala-dom-types, remove after - val ariaCurrent = customHtmlAttr("aria-current", StringAsIsCodec) - val ariaHidden = customHtmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - - val datetime = customHtmlAttr("datetime", StringAsIsCodec) - - object svg { - import com.raquo.laminar.api.L.svg.{*, given} - val ariaHidden = - customSvgAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - } -} diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala deleted file mode 100644 index 6cda48b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala +++ /dev/null @@ -1,28 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -object Display: - - enum Breakpoint: - case sm, md, lg, xl, `2xl` - - enum DisplayClass: - case block, `inline-block`, `inline`, flex, `inline-flex`, table, - `inline-table`, `table-caption` - - object ShowUpFrom: - inline def apply( - br: Breakpoint, - dc: DisplayClass = DisplayClass.block - ): HtmlElement = - div( - cls := "hidden", - cls := s"${br}:${dc}" - ) - - object HideUpTo: - inline def apply(br: Breakpoint): HtmlElement = - div( - cls := s"${br}:hidden" - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala deleted file mode 100644 index 4a8b065..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala +++ /dev/null @@ -1,270 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec -import com.raquo.domtypes.generic.defs.attrs.AriaAttrs -import com.raquo.laminar.api.L.svg.{*, given} -import com.raquo.laminar.builders.SvgBuilders -import com.raquo.laminar.keys.ReactiveSvgAttr - -// TODO: fix sizes, colors, hover and stuff, normalize and amend on call site -object Icons: - object aria: - inline def hidden = CustomAttrs.svg.ariaHidden - - object outline: - val defaultSize: Int = 6 - - inline def bell(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" - ) - ) - - inline def `check-circle`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" - ) - ) - - inline def `document-add`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M9 13h6m-3-3v6m5 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" - ) - ) - - inline def `external-link`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" - ) - ) - - inline def menu(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M4 6h16M4 12h16M4 18h16" - ) - ) - - inline def `status-offline`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M18.364 5.636a9 9 0 010 12.728m0 0l-2.829-2.829m2.829 2.829L21 21M15.536 8.464a5 5 0 010 7.072m0 0l-2.829-2.829m-4.243 2.829a4.978 4.978 0 01-1.414-2.83m-1.414 5.658a9 9 0 01-2.167-9.238m7.824 2.167a1 1 0 111.414 1.414m-1.414-1.414L3 3m8.293 8.293l1.414 1.414" - ) - ) - - inline def user(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" - ) - ) - - inline def x(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M6 18L18 6M6 6l12 12" - ) - ) - - end outline - - object solid: - val defaultSize: Int = 5 - - inline def users(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - d := "M9 6a3 3 0 11-6 0 3 3 0 016 0zM17 6a3 3 0 11-6 0 3 3 0 016 0zM12.93 17c.046-.327.07-.66.07-1a6.97 6.97 0 00-1.5-4.33A5 5 0 0119 16v1h-6.07zM6 11a5 5 0 015 5v1H1v-1a5 5 0 015-5z" - ) - ) - - inline def `location-marker`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M5.05 4.05a7 7 0 119.9 9.9L10 18.9l-4.95-4.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z", - clipRule := "evenodd" - ) - ) - - inline def calendar(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z", - clipRule := "evenodd" - ) - ) - - inline def `chevron-right`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z", - clipRule := "evenodd" - ) - ) - - inline def search(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z", - clipRule := "evenodd" - ) - ) - - inline def filter(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M3 3a1 1 0 011-1h12a1 1 0 011 1v3a1 1 0 01-.293.707L12 11.414V15a1 1 0 01-.293.707l-2 2A1 1 0 018 17v-5.586L3.293 6.707A1 1 0 013 6V3z", - clipRule := "evenodd" - ) - ) - - inline def `arrow-narrow-left`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M7.707 14.707a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 1.414L5.414 9H17a1 1 0 110 2H5.414l2.293 2.293a1 1 0 010 1.414z", - clipRule := "evenodd" - ) - ) - - inline def home(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - d := "M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z" - ) - ) - - inline def paperclip(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size} text-gray-400", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M8 4a3 3 0 00-3 3v4a5 5 0 0010 0V7a1 1 0 112 0v4a7 7 0 11-14 0V7a5 5 0 0110 0v4a3 3 0 11-6 0V7a1 1 0 012 0v4a1 1 0 102 0V7a3 3 0 00-3-3z", - clipRule := "evenodd" - ) - ) - - end solid -end Icons diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/LinkSupport.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/LinkSupport.scala deleted file mode 100644 index 88f0b9a..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/LinkSupport.scala +++ /dev/null @@ -1,12 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.jsdom.defs.events.TypedTargetMouseEvent - -object LinkSupport: - - extension [El <: org.scalajs.dom.EventTarget]( - ep: EventProcessor[TypedTargetMouseEvent[El], TypedTargetMouseEvent[El]] - ) - def noKeyMod = - ep.filter(ev => !(ev.ctrlKey || ev.metaKey || ev.shiftKey || ev.altKey)) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Loader.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Loader.scala deleted file mode 100644 index 5399933..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Loader.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -// TODO: proper loader -def Loading = - div( - cls := "bg-gray-50 overflow-hidden rounded-lg", - div( - cls := "px-4 py-5 sm:p-6", - "Loading..." - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Renderable.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Renderable.scala deleted file mode 100644 index a0fcc5b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Renderable.scala +++ /dev/null @@ -1,6 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -trait Renderable[A]: - extension (a: A) def toHtml: HtmlElement diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/ComboBox.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/ComboBox.scala deleted file mode 100644 index 8c5b8a1..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/ComboBox.scala +++ /dev/null @@ -1,92 +0,0 @@ -package works.iterative.ui.components.tailwind -package form - -import com.raquo.laminar.api.L.{*, given} -import io.laminext.syntax.core.* - -case class ComboBox( - id: String, - options: Signal[List[ComboBox.Option]], - valueUpdates: Observer[List[String]] -) - -object ComboBox: - - extension (m: ComboBox) - def toHtml: HtmlElement = - val isOpen = Var(false) - div( - cls := "relative mt-1", - input( - idAttr := m.id, - tpe := "text", - cls := "w-full rounded-md border border-gray-300 bg-white py-2 pl-3 pr-12 shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 sm:text-sm", - role := "combobox", - aria.controls := "options", - aria.expanded := false, - onClick.mapTo(true) --> isOpen.writer - ), - button( - tpe := "button", - cls := "absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-none", { - import svg.* - svg( - cls := "h-5 w-5 text-gray-400", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - CustomAttrs.svg.ariaHidden := true, - path( - fillRule := "evenodd", - d := "M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z", - clipRule := "evenodd" - ) - ) - } - ), - ul( - cls <-- isOpen.signal.switch("", "hidden"), - cls := "absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm", - idAttr := "options", - role := "listbox", - children <-- m.options.map(_.map(_.toHtml)) - ) - ) - - case class Option(value: String, active: Boolean) - - object Option: - extension (m: Option) - def toHtml: HtmlElement = - li( - cls := "relative cursor-default select-none py-2 pl-8 pr-4", - cls := (if m.active then "text-white bg-indigo-600" - else "text-gray-900"), - idAttr := "option-0", - role := "option", - tabIndex := -1, - span( - cls := "block truncate", - m.value - ), - if m.active then - span( - cls := "absolute inset-y-0 left-0 flex items-center pl-1.5", - cls := (if m.active then "text-white" else "text-indigo-600"), { - import svg.* - svg( - cls := "h-5 w-5", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - CustomAttrs.svg.ariaHidden := true, - path( - fillRule := "evenodd", - d := "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", - clipRule := "evenodd" - ) - ) - } - ) - else emptyNode - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/Form.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/Form.scala deleted file mode 100644 index 1b41dee..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/Form.scala +++ /dev/null @@ -1,17 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} -import org.scalajs.dom -import com.raquo.laminar.nodes.ReactiveHtmlElement - -object Form: - val Body = FormBody - val Section = FormSection - val Row = FormRow - - def apply(body: HtmlElement, buttons: HtmlElement): HtmlElement = - form( - cls := "space-y-8 divide-y divide-gray-200", - body, - buttons - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala deleted file mode 100644 index e7e6627..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.akka - -// Base class for all command-processing related exceptions from handlers -sealed abstract class CommandHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandNotAvailable[C, S](cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd není dostupný ve stavu $state" - ) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandRejected[C, S](reason: String, cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd byl ve stavu $state odmítnut s odůvodněním $reason" - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala deleted file mode 100644 index 72a5bf9..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.akka - -sealed abstract class EventHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -case class UnhandledEvent[Event, State](event: Event, state: State) - extends EventHandlerException( - s"Událost $event nastala ve stavu $state bez možnosti zpracování" - ) diff --git a/iw/bom.sc b/iw/bom.sc deleted file mode 100644 index e9266b8..0000000 --- a/iw/bom.sc +++ /dev/null @@ -1,227 +0,0 @@ -import mill._, scalalib._ - -object IWMaterials { - - object Versions { - val akka = "2.6.16" - val akkaHttp = "10.2.4" - val cats = "2.7.0" - val elastic4s = "7.12.2" - val http4s = "0.23.10" - val http4sPac4J = "4.0.0" - val laminar = "0.14.2" - val laminext = laminar - val logbackClassic = "1.2.10" - val pac4j = "5.2.0" - val play = "2.8.8" - val playJson = "2.9.2" - val scalaTest = "3.2.9" - val slick = "3.3.3" - val sttpClient = "3.5.0" - val tapir = "0.20.1" - val urlDsl = "0.4.0" - val waypoint = "0.5.0" - val zio = "2.0.0-RC2" - val zioConfig = "3.0.0-RC2" - val zioInteropCats = "3.3.0-RC2" - val zioJson = "0.3.0-RC3" - val zioLogging = "2.0.0-RC5" - val zioPrelude = "1.0.0-RC10" - val zioZMX = "0.0.11" - } - - object Deps extends AkkaLibs with SlickLibs { - import IWMaterials.{Versions => V} - - val zioOrg = "dev.zio" - - def zioLib(name: String, version: String): Dep = - ivy"$zioOrg::zio-$name::$version" - - lazy val zio: Dep = ivy"$zioOrg::zio:${V.zio}" - - lazy val zioTest: Dep = zioLib("test", V.zio) - lazy val zioTestSbt: Dep = zioLib("test", V.zio) - - lazy val zioConfig: Dep = zioLib("config", V.zioConfig) - lazy val zioConfigTypesafe: Dep = - zioLib("config-typesafe", V.zioConfig) - lazy val zioConfigMagnolia: Dep = - zioLib("config-magnolia", V.zioConfig) - - lazy val zioJson: Dep = zioLib("json", V.zioJson) - lazy val zioLogging: Dep = zioLib("logging", V.zioLogging) - lazy val zioLoggingSlf4j: Dep = - zioLib("logging-slf4j", V.zioLogging) - lazy val zioPrelude: Dep = zioLib("prelude", V.zioPrelude) - lazy val zioStreams: Dep = zioLib("streams", V.zio) - lazy val zioZMX: Dep = zioLib("zmx", V.zioZMX) - lazy val zioInteropCats: Dep = - zioLib("interop-cats", V.zioInteropCats) - - /* What is the equivalent? ZIOModule with prepared test config? - def useZIO(testConf: Configuration*): Agg[Dep] = Agg( - zio, - zioTest, - zioTestSbt, - testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework") - ) - */ - - /* - def useZIOAll(testConf: Configuration*): Seq[Def.Setting[_]] = - useZIO(testConf: _*) ++ Seq( - zioStreams, - zioConfig, - zioConfigTypesafe, - zioConfigMagnolia, - zioJson, - zioZMX, - zioLogging, - zioPrelude - ) - */ - - private val tapirOrg = "com.softwaremill.sttp.tapir" - def tapirLib(name: String): Dep = - ivy"${tapirOrg}::tapir-$name::${V.tapir}" - - lazy val tapirCore: Dep = tapirLib("core") - lazy val tapirZIO: Dep = tapirLib("zio") - lazy val tapirZIOJson: Dep = tapirLib("json-zio") - lazy val tapirSttpClient: Dep = tapirLib("sttp-client") - lazy val tapirCats: Dep = tapirLib("cats") - lazy val tapirZIOHttp4sServer: Dep = tapirLib("zio-http4s-server") - - private val sttpClientOrg = "com.softwaremill.sttp.client3" - def sttpClientLib(name: String): Dep = - ivy"${sttpClientOrg}::${name}:${V.sttpClient}" - - lazy val sttpClientCore: Dep = sttpClientLib("core") - - lazy val http4sBlazeServer: Dep = - ivy"org.http4s::http4s-blaze-server:${V.http4s}" - - lazy val http4sPac4J: Dep = - ivy"org.pac4j::http4s-pac4j:${V.http4sPac4J}" - lazy val pac4jOIDC: Dep = - ivy"org.pac4j:pac4j-oidc:${V.pac4j}" - - lazy val scalaTest: Dep = - ivy"org.scalatest::scalatest:${V.scalaTest}" - lazy val scalaTestPlusScalacheck: Dep = - ivy"org.scalatestplus::scalacheck-1-15:3.2.9.0" - lazy val playScalaTest: Dep = - ivy"org.scalatestplus.play::scalatestplus-play:5.1.0" - - private val playOrg = "com.typesafe.play" - lazy val playMailer: Dep = ivy"${playOrg}::play-mailer:8.0.1" - lazy val playServer: Dep = ivy"${playOrg}::play-server:${V.play}" - lazy val playAkkaServer: Dep = - ivy"${playOrg}::play-akka-http-server:${V.play}" - lazy val play: Dep = ivy"${playOrg}::play:${V.play}" - lazy val playAhcWs: Dep = ivy"${playOrg}::play-ahc-ws:${V.play}" - lazy val playJson: Dep = ivy"${playOrg}::play-json:${V.playJson}" - - private val elastic4sOrg = "com.sksamuel.elastic4s" - lazy val useElastic4S: Agg[Dep] = Agg( - ivy"${elastic4sOrg}::elastic4s-core:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-client-akka:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-http-streams:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-json-play:${V.elastic4s}" - ) - - lazy val laminar: Dep = ivy"com.raquo::laminar::${V.laminar}" - - private def laminext(name: String): Dep = - ivy"io.laminext::$name::${V.laminar}" - - lazy val laminextCore: Dep = laminext("core") - lazy val laminextTailwind: Dep = laminext("tailwind") - lazy val laminextFetch: Dep = laminext("fetch") - lazy val laminextValidationCore: Dep = laminext("validation-core") - lazy val laminextUI: Dep = laminext("ui") - - lazy val waypoint: Dep = - ivy"com.raquo::waypoint::${V.waypoint}" - - lazy val urlDsl: Dep = - ivy"be.doeraene::url-dsl::${V.urlDsl}" - - lazy val scalaJavaTime: Dep = - ivy"io.github.cquiroz::scala-java-time::2.3.0" - - lazy val scalaJavaLocales: Dep = - ivy"io.github.cquiroz::scala-java-locales::1.2.1" - - lazy val logbackClassic: Dep = - ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" - } - - trait AkkaLibs { - - self: SlickLibs => - - object akka { - val V = Versions.akka - val tOrg = "com.typesafe.akka" - val lOrg = "com.lightbend.akka" - - def akkaMod(name: String): Dep = - ivy"$tOrg::akka-$name:$V" - - lazy val actor: Dep = akkaMod("actor") - lazy val actorTyped: Dep = akkaMod("actor-typed") - lazy val stream: Dep = akkaMod("stream") - lazy val persistence: Dep = akkaMod("persistence-typed") - lazy val persistenceQuery: Dep = akkaMod("persistence-query") - lazy val persistenceJdbc: Dep = - ivy"$lOrg::akka-persistence-jdbc:5.0.4" - val persistenceTestKit: Dep = ivy"$tOrg::akka-persistence-testkit:$V" - - object http { - val V = Versions.akkaHttp - - lazy val http: Dep = ivy"$tOrg::akka-http:$V" - lazy val sprayJson: Dep = ivy"$tOrg::akka-http-spray-json:$V" - - } - - object projection { - val V = "1.2.2" - - lazy val core: Dep = - ivy"$lOrg::akka-projection-core:$V" - lazy val eventsourced: Dep = - ivy"$lOrg::akka-projection-eventsourced:$V" - lazy val slick: Dep = - ivy"$lOrg::akka-projection-slick:$V" - lazy val jdbc: Dep = - ivy"$lOrg::akka-projection-jdbc:$V" - } - - object profiles { - // TODO: deal with cross-version for Scala3 (for3Use2_13) - lazy val eventsourcedJdbcProjection: Agg[Dep] = Agg( - persistenceQuery, - projection.core, - projection.eventsourced, - projection.slick, - persistenceJdbc - ) ++ slick.default - } - } - } - - trait SlickLibs { - object slick { - val V = IWMaterials.Versions.slick - val org = "com.typesafe.slick" - - lazy val slick: Dep = ivy"$org::slick:$V" - lazy val hikaricp: Dep = ivy"$org::slick-hikaricp:$V" - lazy val default: Agg[Dep] = Agg(slick, hikaricp) - } - } - -} diff --git a/iw/build.sc b/iw/build.sc deleted file mode 100644 index fbbc3b8..0000000 --- a/iw/build.sc +++ /dev/null @@ -1,144 +0,0 @@ -import mill._, scalalib._, scalajslib._ - -import $file.bom - -object support { - val Deps = bom.IWMaterials.Deps - val Versions = bom.IWMaterials.Versions - - trait CommonModule extends ScalaModule { - def scalaVersion = "3.1.1" - def scalacOptions = Seq( - "-encoding", - "utf8", - "-deprecation", - "-explain-types", - "-explain", - "-feature", - "-language:experimental.macros", - "-language:higherKinds", - "-language:implicitConversions", - "-unchecked", - "-Xfatal-warnings", - "-Ykind-projector" - ) - } - - trait CommonJSModule extends CommonModule with ScalaJSModule { - def scalaJSVersion = "1.8.0" - } - - trait CrossPlatformModule extends Module { outer => - def ivyDeps: T[Agg[Dep]] = Agg[Dep]() - def jsDeps: T[Agg[Dep]] = Agg[Dep]() - def jvmDeps: T[Agg[Dep]] = Agg[Dep]() - def moduleDeps: Seq[Module] = Seq[Module]() - - trait PlatformModule extends CommonModule { - def platform: String - override def millSourcePath = outer.millSourcePath - override def ivyDeps = outer.ivyDeps() ++ (platform match { - case "js" => jsDeps() - case _ => jvmDeps() - }) - override def moduleDeps = outer.moduleDeps.collect { - case m: CrossPlatformModule => - platform match { - case "js" => m.js - case _ => m.jvm - } - case m: JavaModule => m - } - } - - trait JsModule extends PlatformModule with CommonJSModule { - def platform = "js" - } - - trait JvmModule extends PlatformModule { - def platform = "jvm" - } - - val js: JsModule - val jvm: JvmModule - } - - trait PureCrossModule extends CrossPlatformModule { - override object js extends JsModule - override object jvm extends JvmModule - } - - trait PureCrossSbtModule extends CrossPlatformModule { - override object js extends JsModule with SbtModule - override object jvm extends JvmModule with SbtModule - } - - trait FullCrossSbtModule extends CrossPlatformModule { - trait FullSources extends JavaModule { self: PlatformModule => - override def sources = T.sources( - millSourcePath / platform / "src" / "main" / "scala", - millSourcePath / "shared" / "src" / "main" / "scala" - ) - - override def resources = T.sources( - millSourcePath / platform / "src" / "main" / "resources", - millSourcePath / "shared" / "src" / "main" / "resources" - ) - } - - override object js extends JsModule with FullSources - override object jvm extends JvmModule with FullSources - } - -} - -import support._ - -object mongo extends CommonModule { - def ivyDeps = Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - ivy"org.mongodb.scala::mongo-scala-driver:4.2.3".withDottyCompat( - scalaVersion() - ) - ) -} - -object tapir extends FullCrossSbtModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson, Deps.zioJson) - def jvmDeps = Agg(Deps.tapirZIO, Deps.tapirZIOHttp4sServer) - def jsDeps = Agg(Deps.tapirSttpClient) -} - -object akkaPersistence extends CommonModule { - def millSourcePath = build.millSourcePath / "akka-persistence" - def ivyDeps = (Agg( - Deps.akka.persistenceQuery, - Deps.akka.persistenceJdbc, - Deps.akka.projection.core, - Deps.akka.projection.eventsourced, - Deps.akka.projection.slick - ) ++ Deps.slick.default).map(_.withDottyCompat(scalaVersion())) ++ Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - Deps.akka.persistence.withDottyCompat(scalaVersion()), - ivy"com.typesafe.akka::akka-cluster-sharding-typed:${Deps.akka.V}" - .withDottyCompat(scalaVersion()) - ) -} - -object ui extends CommonJSModule { - def ivyDeps = Agg( - Deps.zio, - Deps.laminar, - Deps.zioJson, - Deps.waypoint, - Deps.urlDsl, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) -} diff --git a/iw/domain.sc b/iw/domain.sc deleted file mode 100644 index b09c56b..0000000 --- a/iw/domain.sc +++ /dev/null @@ -1,78 +0,0 @@ -import mill._, scalalib._ - -import $file.{build => ff}, ff.support._ - -trait DomainModule extends Module { - // General extra model deps - def modelModules: Seq[Module] = Seq.empty - // General extra codecs deps - def codecsModules: Seq[Module] = Seq.empty - // General extra endpoints deps - def endpointsModules: Seq[Module] = Seq.empty - - // Implementation deps for repo - def repoModules: Seq[JavaModule] = Seq.empty - - object shared extends Module { - object model extends PureCrossModule { - def moduleDeps = modelModules - def ivyDeps = Agg(Deps.zioPrelude) - } - object codecs extends PureCrossModule { - def moduleDeps = Seq(model) ++ codecsModules - def ivyDeps = Agg(Deps.zioJson) - } - } - - trait CommonProjects extends Module { - object model extends PureCrossModule { - def ivyDeps = Agg(Deps.zioPrelude) - def moduleDeps = Seq(shared.model) - } - object codecs extends PureCrossModule { - def moduleDeps = - Seq(model, shared.model, shared.codecs, ff.tapir) - } - object endpoints extends PureCrossModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson) - def moduleDeps = Seq(model, codecs) ++ endpointsModules - } - object client extends CommonJSModule { - def moduleDeps = Seq(endpoints.js) - } - object components extends CommonJSModule { - def ivyDeps = Agg( - Deps.laminar, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) - def moduleDeps = Seq(model.js, codecs.js, client, ff.ui) - } - } - - object query extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(repo, query.endpoints.jvm) - } - object repo extends CommonModule { - def ivyDeps = Agg(Deps.zio) - def moduleDeps = Seq(model.jvm, codecs.jvm) ++ repoModules - } - object projection extends CommonModule { - def moduleDeps = Seq(repo, ff.akkaPersistence) - } - } - - object command extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(entity, command.endpoints.jvm) - } - object entity extends CommonModule { - def moduleDeps = Seq(model.jvm, codecs.jvm, ff.akkaPersistence) - } - } -} diff --git a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala b/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala deleted file mode 100644 index 0fa3493..0000000 --- a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala +++ /dev/null @@ -1,53 +0,0 @@ -package works.iterative.mongo - -import zio.* -import zio.json.* -import zio.config.* -import org.mongodb.scala.* -import org.mongodb.scala.model.Filters.* -import org.bson.json.JsonObject -import org.mongodb.scala.model.ReplaceOptions -import org.mongodb.scala.bson.conversions.Bson - -case class MongoConfig(uri: String) - -object MongoConfig: - val configDesc = - import ConfigDescriptor.* - nested("MONGO")(string("URI").default("mongodb://localhost:27017")) - .to[MongoConfig] - val fromEnv = ZConfig.fromSystemEnv(configDesc) - -extension (m: MongoClient.type) - def layer: RLayer[MongoConfig, MongoClient] = - ZIO - .serviceWithZIO[MongoConfig](c => Task.attempt(MongoClient(c.uri))) - .toLayer - -class MongoJsonRepository[Elem, Key, Criteria]( - collection: MongoCollection[JsonObject], - toFilter: Criteria => Bson, - idFilter: Elem => (String, Key) -)(using JsonCodec[Elem]) { - def matching(criteria: Criteria): Task[Seq[Elem]] = - val filter = toFilter(criteria) - val query = collection.find(filter) - - for - result <- ZIO.fromFuture(_ => query.toFuture) - proof <- ZIO.collect(result)(j => - ZIO.fromOption(j.getJson.fromJson[Elem].toOption) - ) - yield proof - - def put(elem: Elem): Task[Unit] = - Task.async(cb => - collection - .replaceOne( - equal.tupled(idFilter(elem)), - JsonObject(elem.toJson), - ReplaceOptions().upsert(true) - ) - .subscribe(_ => cb(Task.unit), t => cb(Task.fail(t))) - ) -} diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala deleted file mode 100644 index 68d7196..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala +++ /dev/null @@ -1,12 +0,0 @@ -package works.iterative.tapir - -import sttp.model.Uri - -opaque type BaseUri = Option[Uri] - -object BaseUri: - - def apply(optU: Option[Uri]): BaseUri = optU - def apply(u: Uri): BaseUri = Some(u) - - extension (v: BaseUri) def toUri: Option[Uri] = v diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 653bc51..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,22 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.client.sttp.SttpClientInterpreter -import sttp.tapir.PublicEndpoint -import sttp.tapir.client.sttp.WebSocketToPipe -import scala.concurrent.Future -import sttp.client3.SttpBackend -import sttp.capabilities.WebSockets - -trait CustomTapirPlatformSpecific extends SttpClientInterpreter: - self: CustomTapir => - - type Backend = SttpBackend[Future, WebSockets] - - def makeClient[I, E, O]( - endpoint: PublicEndpoint[I, E, O, Any] - )(using - baseUri: BaseUri, - backend: Backend, - wsToPipe: WebSocketToPipe[Any] - ): I => Future[O] = - toClientThrowErrors(endpoint, baseUri.toUri, backend) diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 2545fbd..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,5 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.ztapir.ZTapir - -trait CustomTapirPlatformSpecific extends ZTapir diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala deleted file mode 100644 index 55e4ca1..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala +++ /dev/null @@ -1,7 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.server.http4s.ztapir.ZHttp4sServerInterpreter - -trait Http4sCustomTapir[Env] - extends CustomTapir - with ZHttp4sServerInterpreter[Env] diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala deleted file mode 100644 index dd52e9b..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala +++ /dev/null @@ -1,14 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.Tapir -import sttp.tapir.json.zio.TapirJsonZio -import sttp.tapir.TapirAliases - -trait CustomTapir - extends Tapir - with TapirJsonZio - with TapirAliases - with CustomTapirPlatformSpecific: - given Schema[ServerError] = Schema.derived - -object CustomTapir extends CustomTapir diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala deleted file mode 100644 index 715591d..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.tapir - -import zio.json.* - -sealed trait ServerError -case class InternalServerError(msg: String) extends ServerError -object InternalServerError: - def fromThrowable(t: Throwable): ServerError = InternalServerError( - t.getMessage - ) - -object ServerError: - given JsonCodec[ServerError] = DeriveJsonCodec.gen diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/File.scala deleted file mode 100644 index 09f6c07..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala +++ /dev/null @@ -1,3 +0,0 @@ -package works.iterative.services.files - -case class File(url: String, name: String) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala deleted file mode 100644 index 46633a5..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala +++ /dev/null @@ -1,25 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.Renderable - -given Renderable[File] with - extension (m: File) - def toHtml: HtmlElement = - li( - cls("pl-3 pr-4 py-3 flex items-center justify-between text-sm"), - div( - cls("w-0 flex-1 flex items-center"), - Icons.solid - .paperclip() - .amend(svg.cls := "flex-shrink-0 text-gray-400"), - span(cls("ml-2 flex-1 w-0 truncate"), m.name) - ), - a( - href(m.url), - cls("font-medium text-indigo-600 hover:text-indigo-500"), - "Otevřít" - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala deleted file mode 100644 index ac59db8..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import io.laminext.syntax.core.{*, given} - -def FileList(files: List[File]): HtmlElement = - ul( - role("list"), - cls("border border-gray-200 rounded-md divide-y divide-gray-200"), - files.map(_.toHtml) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala deleted file mode 100644 index 2e8f690..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala +++ /dev/null @@ -1,81 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import io.laminext.syntax.core.{*, given} - -object FilePicker: - sealed trait Event - sealed trait DoneEvent extends Event - case class SelectionUpdated(files: Set[File]) extends DoneEvent - case object SelectionCancelled extends DoneEvent - case object AvailableFilesRequested extends Event - - def apply( - currentFiles: Signal[List[File]], - availableFilesStream: EventStream[List[File]] - )(selectionUpdates: Observer[Event]): HtmlElement = - val (updatesStream, updatesObserver) = EventStream.withObserver[Event] - val selectorOpen = Var[Boolean](false) - - // This sequence tricks browser into displaying modal content centered - // Inspired by modal in headless ui playground - // https://github.com/tailwindlabs/headlessui/blob/fdd26297953080d5ec905dda0bf5ec9607897d86/packages/playground-react/pages/transitions/component-examples/modal.tsx#L78-L79 - inline def browserCenteringModalTrick: Modifier[HtmlElement] = - Seq[Modifier[HtmlElement]]( - span(cls("hidden sm:inline-block sm:h-screen sm:align-middle")), - "​" // Zero width space - ) - - inline def overlay: Modifier[HtmlElement] = - // Page overlay - /* TODO: transition - enter="ease-out duration-300" - enterFrom="opacity-0" - enterTo="opacity-100" - leave="ease-in duration-200" - leaveFrom="opacity-100" - leaveTo="opacity-0" - */ - div( - div( - onClick.mapTo(false) --> selectorOpen.writer, - cls("fixed inset-0 transition-opacity"), - div(cls("absolute inset-0 bg-gray-500 opacity-75")) - ) - ) - - inline def modalSelector: HtmlElement = - div( - cls("fixed inset-0 z-10 overflow-y-auto"), - div( - cls("text-center sm:block sm:p-0"), - overlay, - browserCenteringModalTrick, - child <-- currentFiles.map( - FileSelector(_, availableFilesStream)(updatesObserver) - ) - ) - ) - - div( - updatesStream --> selectionUpdates, - updatesStream.collect { case _: DoneEvent => - false - } --> selectorOpen.writer, - child.maybe <-- selectorOpen.signal.map(isOpen => - if isOpen then Some(modalSelector) else None - ), - div( - cls("flex flex-col space-y-5"), - child.maybe <-- currentFiles.map(files => - if files.isEmpty then None else Some(FileList(files)) - ), - button( - tpe := "button", - cls := "bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500", - "Zvolit soubory", - onClick.mapTo(true) --> selectorOpen.writer - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala deleted file mode 100644 index b43e07a..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala +++ /dev/null @@ -1,74 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.Loading -import io.laminext.syntax.core.{*, given} -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -object FileSelector: - import FilePicker._ - - def apply( - initialFiles: List[File], - availableFilesStream: EventStream[List[File]] - )(selectionUpdates: Observer[Event]): HtmlElement = - val selectedFiles = Var[Set[File]](initialFiles.to(Set)) - val availableFiles = availableFilesStream.startWithNone - // Request the files to display - selectionUpdates.onNext(AvailableFilesRequested) - div( - cls( - "inline-block transform overflow-hidden rounded-lg bg-white text-left align-bottom shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:align-middle" - ), - role("dialog"), - customHtmlAttr("aria.modal", BooleanAsTrueFalseStringCodec)(true), - aria.labelledBy("modal-headline"), - div( - cls("bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"), - div( - cls("sm:flex sm:items-start"), - div( - cls("mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"), - h3( - cls("text-lg font-medium leading-6 text-gray-900"), - idAttr("modal-headline"), - "Výběr souborů" - ) - ) - ), - child <-- availableFiles - .split(_ => ())((_, _, af) => FileTable(af, selectedFiles)) - .map(_.getOrElse(Loading)) - ), - div( - cls("bg-gray-50 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6"), - span( - cls("flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto"), - button( - typ("button"), - cls( - "focus:shadow-outline-green inline-flex w-full justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-base font-medium leading-6 text-white shadow-sm transition duration-150 ease-in-out hover:bg-indigo-500 focus:border-indigo-700 focus:outline-none sm:text-sm sm:leading-5" - ), - "Potvrdit", - composeEvents(onClick)( - _.sample(selectedFiles) - .map(SelectionUpdated(_)) - ) --> selectionUpdates - ) - ), - span( - cls("mt-3 flex w-full rounded-md shadow-sm sm:mt-0 sm:w-auto"), - button( - typ("button"), - cls( - "focus:shadow-outline-blue inline-flex w-full justify-center rounded-md border border-gray-300 bg-white px-4 py-2 text-base font-medium leading-6 text-gray-700 shadow-sm transition duration-150 ease-in-out hover:text-gray-500 focus:border-blue-300 focus:outline-none sm:text-sm sm:leading-5" - ), - "Zrušit", - onClick.mapTo(SelectionCancelled) --> selectionUpdates - ) - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala deleted file mode 100644 index aa11b2b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala +++ /dev/null @@ -1,91 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import works.iterative.ui.components.tailwind.Icons - -def FileTable( - files: Signal[List[File]], - selectedFiles: Var[Set[File]] -): HtmlElement = - val scope = customHtmlAttr("scope", StringAsIsCodec) - - def headerRow: HtmlElement = - val col = scope("col") - val baseM: Modifier[HtmlElement] = Seq(cls("px-6 py-3"), col) - val textH = cls( - "text-left text-xs font-medium text-gray-500 uppercase tracking-wider" - ) - tr( - th(baseM, span(cls("sr-only"), "Vybrat")), - th(baseM, textH, "Název"), - th(baseM, cls("relative"), span(cls("sr-only"), "Otevřít")) - ) - - def tableRow( - f: File, - idx: Int, - selected: Boolean - )(toggleSelection: Observer[Unit]): HtmlElement = - val baseC = cls("px-6 py-4 whitespace-nowrap text-sm") - tr( - cls(if idx % 2 == 0 then "bg-gray-50" else "bg-white"), - td( - cls("font-medium cursor-pointer"), - onClick.mapTo(()) --> toggleSelection, - cls(if selected then "text-green-900" else "text-gray-200"), - Icons.outline.`check-circle`().amend(svg.cls := "mx-auto"), - span(cls("sr-only"), if selected then "Vybráno" else "Nevybráno") - ), - td( - baseC, - cls("font-medium text-gray-900"), - f.name, - onClick.mapTo(()) --> toggleSelection - ), - td( - baseC, - cls("text-right font-medium"), - a( - href(f.url), - target("_blank"), - cls( - "flex items-center space-x-4 text-indigo-600 hover:text-indigo-900" - ), - Icons.outline.`external-link`(), - "Otevřít" - ) - ) - ) - - div( - cls("flex flex-col"), - div(cls("overflow-x-auto sm:-mx-6 lg:-mx-8")), - div( - cls("py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8"), - div( - cls("shadow overflow-hidden border-b border-gray-200 sm:rounded-lg"), - table( - cls("min-w-full divide-y divide-gray-200"), - thead( - cls("bg-gray-50"), - headerRow - ), - tbody( - children <-- files - .map(_.zipWithIndex) - .combineWithFn(selectedFiles)((f, sel) => - f.map((file, idx) => - val active = sel.contains(file) - tableRow(file, idx, active)( - selectedFiles.writer - .contramap(_ => if active then sel - file else sel + file) - ) - ) - ) - ) - ) - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala deleted file mode 100644 index 0fc8796..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala +++ /dev/null @@ -1,32 +0,0 @@ -package works.iterative.ui.components.tailwind - -import CustomAttrs.ariaHidden -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -// TODO: render icon or picture based on img signal -class Avatar($avatarImg: Signal[Option[String]]): - inline def avatarPlaceholder(size: Int): HtmlElement = - div( - cls := s"rounded-full text-indigo-200 bg-indigo-500 h-${size} w-${size} flex items-center justify-center", - Icons.outline.user(size - 2) - ) - - inline def avatarImage(size: Int): Signal[HtmlElement] = - $avatarImg.split(_ => ())((_, _, $url) => - img( - cls := s"w-$size h-$size rounded-full", - src <-- $url, - alt := "" - ) - ).map(_.getOrElse(avatarPlaceholder(size))) - - inline def avatar(size: Int): HtmlElement = - div( - cls := "relative", - child <-- avatarImage(size), - span( - cls := "absolute inset-0 shadow-inner rounded-full", - ariaHidden := true - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala deleted file mode 100644 index bcbed88..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala +++ /dev/null @@ -1,77 +0,0 @@ -package works.iterative.ui.components.tailwind - -enum ColorWeight(value: String): - inline def toCSS: String = value - // To be used for black and white until - // we support better mechanism via extension methods - case w__ extends ColorWeight("") - case w50 extends ColorWeight("50") - case w100 extends ColorWeight("100") - case w200 extends ColorWeight("200") - case w300 extends ColorWeight("300") - case w400 extends ColorWeight("400") - case w500 extends ColorWeight("500") - case w600 extends ColorWeight("600") - case w700 extends ColorWeight("700") - case w800 extends ColorWeight("800") - case w900 extends ColorWeight("900") - -enum Color(name: String): - import ColorWeight._ - - inline def toCSSNoColorWeight(prefix: String): String = - s"${prefix}-${name}" - inline def toCSSWithColorWeight( - prefix: String, - weight: ColorWeight - ): String = - s"${prefix}-${name}-${weight.toCSS}" - inline def toCSS(prefix: String)(weight: ColorWeight): String = - weight match { - case `w__` => toCSSNoColorWeight(prefix) - case _ => toCSSWithColorWeight(prefix, weight) - } - inline def bg: ColorWeight => String = toCSS("bg")(_) - inline def text: ColorWeight => String = toCSS("text")(_) - inline def decoration: ColorWeight => String = toCSS("decoration")(_) - inline def border: ColorWeight => String = toCSS("border")(_) - inline def outline: ColorWeight => String = toCSS("outline")(_) - inline def divide: ColorWeight => String = toCSS("divide")(_) - inline def ring: ColorWeight => String = toCSS("ring")(_) - inline def ringOffset: ColorWeight => String = toCSS("ring-offset")(_) - inline def shadow: ColorWeight => String = toCSS("shadow")(_) - inline def accent: ColorWeight => String = toCSS("accent")(_) - - // TODO: change the "stupid" methods to extension methods - // that will keep the invariants in comments lower - case current extends Color("current") - case inherit extends Color("inherit") - // Not present in for all methods - case transp extends Color("transparent") - // Seen in accent, not preset otherwise - case auto extends Color("auto") - // Black and white do not have weight - case black extends Color("black") - case white extends Color("white") - case slate extends Color("slate") - case gray extends Color("gray") - case zinc extends Color("zinc") - case neutral extends Color("neutral") - case stone extends Color("stone") - case red extends Color("red") - case orange extends Color("orange") - case amber extends Color("amber") - case yellow extends Color("yellow") - case lime extends Color("lime") - case green extends Color("green") - case emerald extends Color("emerald") - case teal extends Color("teal") - case cyan extends Color("cyan") - case sky extends Color("sky") - case blue extends Color("blue") - case indigo extends Color("indigo") - case violet extends Color("violet") - case purple extends Color("purple") - case fuchsia extends Color("fuchsia") - case pink extends Color("pink") - case rose extends Color("rose") diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala deleted file mode 100644 index 6fb453c..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -object CustomAttrs { - // Made a pull request to add aria-current to scala-dom-types, remove after - val ariaCurrent = customHtmlAttr("aria-current", StringAsIsCodec) - val ariaHidden = customHtmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - - val datetime = customHtmlAttr("datetime", StringAsIsCodec) - - object svg { - import com.raquo.laminar.api.L.svg.{*, given} - val ariaHidden = - customSvgAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - } -} diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala deleted file mode 100644 index 6cda48b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala +++ /dev/null @@ -1,28 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -object Display: - - enum Breakpoint: - case sm, md, lg, xl, `2xl` - - enum DisplayClass: - case block, `inline-block`, `inline`, flex, `inline-flex`, table, - `inline-table`, `table-caption` - - object ShowUpFrom: - inline def apply( - br: Breakpoint, - dc: DisplayClass = DisplayClass.block - ): HtmlElement = - div( - cls := "hidden", - cls := s"${br}:${dc}" - ) - - object HideUpTo: - inline def apply(br: Breakpoint): HtmlElement = - div( - cls := s"${br}:hidden" - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala deleted file mode 100644 index 4a8b065..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala +++ /dev/null @@ -1,270 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec -import com.raquo.domtypes.generic.defs.attrs.AriaAttrs -import com.raquo.laminar.api.L.svg.{*, given} -import com.raquo.laminar.builders.SvgBuilders -import com.raquo.laminar.keys.ReactiveSvgAttr - -// TODO: fix sizes, colors, hover and stuff, normalize and amend on call site -object Icons: - object aria: - inline def hidden = CustomAttrs.svg.ariaHidden - - object outline: - val defaultSize: Int = 6 - - inline def bell(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" - ) - ) - - inline def `check-circle`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" - ) - ) - - inline def `document-add`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M9 13h6m-3-3v6m5 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" - ) - ) - - inline def `external-link`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" - ) - ) - - inline def menu(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M4 6h16M4 12h16M4 18h16" - ) - ) - - inline def `status-offline`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M18.364 5.636a9 9 0 010 12.728m0 0l-2.829-2.829m2.829 2.829L21 21M15.536 8.464a5 5 0 010 7.072m0 0l-2.829-2.829m-4.243 2.829a4.978 4.978 0 01-1.414-2.83m-1.414 5.658a9 9 0 01-2.167-9.238m7.824 2.167a1 1 0 111.414 1.414m-1.414-1.414L3 3m8.293 8.293l1.414 1.414" - ) - ) - - inline def user(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" - ) - ) - - inline def x(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M6 18L18 6M6 6l12 12" - ) - ) - - end outline - - object solid: - val defaultSize: Int = 5 - - inline def users(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - d := "M9 6a3 3 0 11-6 0 3 3 0 016 0zM17 6a3 3 0 11-6 0 3 3 0 016 0zM12.93 17c.046-.327.07-.66.07-1a6.97 6.97 0 00-1.5-4.33A5 5 0 0119 16v1h-6.07zM6 11a5 5 0 015 5v1H1v-1a5 5 0 015-5z" - ) - ) - - inline def `location-marker`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M5.05 4.05a7 7 0 119.9 9.9L10 18.9l-4.95-4.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z", - clipRule := "evenodd" - ) - ) - - inline def calendar(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z", - clipRule := "evenodd" - ) - ) - - inline def `chevron-right`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z", - clipRule := "evenodd" - ) - ) - - inline def search(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z", - clipRule := "evenodd" - ) - ) - - inline def filter(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M3 3a1 1 0 011-1h12a1 1 0 011 1v3a1 1 0 01-.293.707L12 11.414V15a1 1 0 01-.293.707l-2 2A1 1 0 018 17v-5.586L3.293 6.707A1 1 0 013 6V3z", - clipRule := "evenodd" - ) - ) - - inline def `arrow-narrow-left`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M7.707 14.707a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 1.414L5.414 9H17a1 1 0 110 2H5.414l2.293 2.293a1 1 0 010 1.414z", - clipRule := "evenodd" - ) - ) - - inline def home(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - d := "M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z" - ) - ) - - inline def paperclip(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size} text-gray-400", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M8 4a3 3 0 00-3 3v4a5 5 0 0010 0V7a1 1 0 112 0v4a7 7 0 11-14 0V7a5 5 0 0110 0v4a3 3 0 11-6 0V7a1 1 0 012 0v4a1 1 0 102 0V7a3 3 0 00-3-3z", - clipRule := "evenodd" - ) - ) - - end solid -end Icons diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/LinkSupport.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/LinkSupport.scala deleted file mode 100644 index 88f0b9a..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/LinkSupport.scala +++ /dev/null @@ -1,12 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.jsdom.defs.events.TypedTargetMouseEvent - -object LinkSupport: - - extension [El <: org.scalajs.dom.EventTarget]( - ep: EventProcessor[TypedTargetMouseEvent[El], TypedTargetMouseEvent[El]] - ) - def noKeyMod = - ep.filter(ev => !(ev.ctrlKey || ev.metaKey || ev.shiftKey || ev.altKey)) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Loader.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Loader.scala deleted file mode 100644 index 5399933..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Loader.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -// TODO: proper loader -def Loading = - div( - cls := "bg-gray-50 overflow-hidden rounded-lg", - div( - cls := "px-4 py-5 sm:p-6", - "Loading..." - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Renderable.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Renderable.scala deleted file mode 100644 index a0fcc5b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Renderable.scala +++ /dev/null @@ -1,6 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -trait Renderable[A]: - extension (a: A) def toHtml: HtmlElement diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/ComboBox.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/ComboBox.scala deleted file mode 100644 index 8c5b8a1..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/ComboBox.scala +++ /dev/null @@ -1,92 +0,0 @@ -package works.iterative.ui.components.tailwind -package form - -import com.raquo.laminar.api.L.{*, given} -import io.laminext.syntax.core.* - -case class ComboBox( - id: String, - options: Signal[List[ComboBox.Option]], - valueUpdates: Observer[List[String]] -) - -object ComboBox: - - extension (m: ComboBox) - def toHtml: HtmlElement = - val isOpen = Var(false) - div( - cls := "relative mt-1", - input( - idAttr := m.id, - tpe := "text", - cls := "w-full rounded-md border border-gray-300 bg-white py-2 pl-3 pr-12 shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 sm:text-sm", - role := "combobox", - aria.controls := "options", - aria.expanded := false, - onClick.mapTo(true) --> isOpen.writer - ), - button( - tpe := "button", - cls := "absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-none", { - import svg.* - svg( - cls := "h-5 w-5 text-gray-400", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - CustomAttrs.svg.ariaHidden := true, - path( - fillRule := "evenodd", - d := "M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z", - clipRule := "evenodd" - ) - ) - } - ), - ul( - cls <-- isOpen.signal.switch("", "hidden"), - cls := "absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm", - idAttr := "options", - role := "listbox", - children <-- m.options.map(_.map(_.toHtml)) - ) - ) - - case class Option(value: String, active: Boolean) - - object Option: - extension (m: Option) - def toHtml: HtmlElement = - li( - cls := "relative cursor-default select-none py-2 pl-8 pr-4", - cls := (if m.active then "text-white bg-indigo-600" - else "text-gray-900"), - idAttr := "option-0", - role := "option", - tabIndex := -1, - span( - cls := "block truncate", - m.value - ), - if m.active then - span( - cls := "absolute inset-y-0 left-0 flex items-center pl-1.5", - cls := (if m.active then "text-white" else "text-indigo-600"), { - import svg.* - svg( - cls := "h-5 w-5", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - CustomAttrs.svg.ariaHidden := true, - path( - fillRule := "evenodd", - d := "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", - clipRule := "evenodd" - ) - ) - } - ) - else emptyNode - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/Form.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/Form.scala deleted file mode 100644 index 1b41dee..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/Form.scala +++ /dev/null @@ -1,17 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} -import org.scalajs.dom -import com.raquo.laminar.nodes.ReactiveHtmlElement - -object Form: - val Body = FormBody - val Section = FormSection - val Row = FormRow - - def apply(body: HtmlElement, buttons: HtmlElement): HtmlElement = - form( - cls := "space-y-8 divide-y divide-gray-200", - body, - buttons - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormBody.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormBody.scala deleted file mode 100644 index 89abc9c..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormBody.scala +++ /dev/null @@ -1,10 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} - -object FormBody: - def apply(sections: HtmlElement*): HtmlElement = - div( - cls := "space-y-8 divide-y divide-gray-200 sm:space-y-5", - sections - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala deleted file mode 100644 index e7e6627..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.akka - -// Base class for all command-processing related exceptions from handlers -sealed abstract class CommandHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandNotAvailable[C, S](cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd není dostupný ve stavu $state" - ) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandRejected[C, S](reason: String, cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd byl ve stavu $state odmítnut s odůvodněním $reason" - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala deleted file mode 100644 index 72a5bf9..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.akka - -sealed abstract class EventHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -case class UnhandledEvent[Event, State](event: Event, state: State) - extends EventHandlerException( - s"Událost $event nastala ve stavu $state bez možnosti zpracování" - ) diff --git a/iw/bom.sc b/iw/bom.sc deleted file mode 100644 index e9266b8..0000000 --- a/iw/bom.sc +++ /dev/null @@ -1,227 +0,0 @@ -import mill._, scalalib._ - -object IWMaterials { - - object Versions { - val akka = "2.6.16" - val akkaHttp = "10.2.4" - val cats = "2.7.0" - val elastic4s = "7.12.2" - val http4s = "0.23.10" - val http4sPac4J = "4.0.0" - val laminar = "0.14.2" - val laminext = laminar - val logbackClassic = "1.2.10" - val pac4j = "5.2.0" - val play = "2.8.8" - val playJson = "2.9.2" - val scalaTest = "3.2.9" - val slick = "3.3.3" - val sttpClient = "3.5.0" - val tapir = "0.20.1" - val urlDsl = "0.4.0" - val waypoint = "0.5.0" - val zio = "2.0.0-RC2" - val zioConfig = "3.0.0-RC2" - val zioInteropCats = "3.3.0-RC2" - val zioJson = "0.3.0-RC3" - val zioLogging = "2.0.0-RC5" - val zioPrelude = "1.0.0-RC10" - val zioZMX = "0.0.11" - } - - object Deps extends AkkaLibs with SlickLibs { - import IWMaterials.{Versions => V} - - val zioOrg = "dev.zio" - - def zioLib(name: String, version: String): Dep = - ivy"$zioOrg::zio-$name::$version" - - lazy val zio: Dep = ivy"$zioOrg::zio:${V.zio}" - - lazy val zioTest: Dep = zioLib("test", V.zio) - lazy val zioTestSbt: Dep = zioLib("test", V.zio) - - lazy val zioConfig: Dep = zioLib("config", V.zioConfig) - lazy val zioConfigTypesafe: Dep = - zioLib("config-typesafe", V.zioConfig) - lazy val zioConfigMagnolia: Dep = - zioLib("config-magnolia", V.zioConfig) - - lazy val zioJson: Dep = zioLib("json", V.zioJson) - lazy val zioLogging: Dep = zioLib("logging", V.zioLogging) - lazy val zioLoggingSlf4j: Dep = - zioLib("logging-slf4j", V.zioLogging) - lazy val zioPrelude: Dep = zioLib("prelude", V.zioPrelude) - lazy val zioStreams: Dep = zioLib("streams", V.zio) - lazy val zioZMX: Dep = zioLib("zmx", V.zioZMX) - lazy val zioInteropCats: Dep = - zioLib("interop-cats", V.zioInteropCats) - - /* What is the equivalent? ZIOModule with prepared test config? - def useZIO(testConf: Configuration*): Agg[Dep] = Agg( - zio, - zioTest, - zioTestSbt, - testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework") - ) - */ - - /* - def useZIOAll(testConf: Configuration*): Seq[Def.Setting[_]] = - useZIO(testConf: _*) ++ Seq( - zioStreams, - zioConfig, - zioConfigTypesafe, - zioConfigMagnolia, - zioJson, - zioZMX, - zioLogging, - zioPrelude - ) - */ - - private val tapirOrg = "com.softwaremill.sttp.tapir" - def tapirLib(name: String): Dep = - ivy"${tapirOrg}::tapir-$name::${V.tapir}" - - lazy val tapirCore: Dep = tapirLib("core") - lazy val tapirZIO: Dep = tapirLib("zio") - lazy val tapirZIOJson: Dep = tapirLib("json-zio") - lazy val tapirSttpClient: Dep = tapirLib("sttp-client") - lazy val tapirCats: Dep = tapirLib("cats") - lazy val tapirZIOHttp4sServer: Dep = tapirLib("zio-http4s-server") - - private val sttpClientOrg = "com.softwaremill.sttp.client3" - def sttpClientLib(name: String): Dep = - ivy"${sttpClientOrg}::${name}:${V.sttpClient}" - - lazy val sttpClientCore: Dep = sttpClientLib("core") - - lazy val http4sBlazeServer: Dep = - ivy"org.http4s::http4s-blaze-server:${V.http4s}" - - lazy val http4sPac4J: Dep = - ivy"org.pac4j::http4s-pac4j:${V.http4sPac4J}" - lazy val pac4jOIDC: Dep = - ivy"org.pac4j:pac4j-oidc:${V.pac4j}" - - lazy val scalaTest: Dep = - ivy"org.scalatest::scalatest:${V.scalaTest}" - lazy val scalaTestPlusScalacheck: Dep = - ivy"org.scalatestplus::scalacheck-1-15:3.2.9.0" - lazy val playScalaTest: Dep = - ivy"org.scalatestplus.play::scalatestplus-play:5.1.0" - - private val playOrg = "com.typesafe.play" - lazy val playMailer: Dep = ivy"${playOrg}::play-mailer:8.0.1" - lazy val playServer: Dep = ivy"${playOrg}::play-server:${V.play}" - lazy val playAkkaServer: Dep = - ivy"${playOrg}::play-akka-http-server:${V.play}" - lazy val play: Dep = ivy"${playOrg}::play:${V.play}" - lazy val playAhcWs: Dep = ivy"${playOrg}::play-ahc-ws:${V.play}" - lazy val playJson: Dep = ivy"${playOrg}::play-json:${V.playJson}" - - private val elastic4sOrg = "com.sksamuel.elastic4s" - lazy val useElastic4S: Agg[Dep] = Agg( - ivy"${elastic4sOrg}::elastic4s-core:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-client-akka:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-http-streams:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-json-play:${V.elastic4s}" - ) - - lazy val laminar: Dep = ivy"com.raquo::laminar::${V.laminar}" - - private def laminext(name: String): Dep = - ivy"io.laminext::$name::${V.laminar}" - - lazy val laminextCore: Dep = laminext("core") - lazy val laminextTailwind: Dep = laminext("tailwind") - lazy val laminextFetch: Dep = laminext("fetch") - lazy val laminextValidationCore: Dep = laminext("validation-core") - lazy val laminextUI: Dep = laminext("ui") - - lazy val waypoint: Dep = - ivy"com.raquo::waypoint::${V.waypoint}" - - lazy val urlDsl: Dep = - ivy"be.doeraene::url-dsl::${V.urlDsl}" - - lazy val scalaJavaTime: Dep = - ivy"io.github.cquiroz::scala-java-time::2.3.0" - - lazy val scalaJavaLocales: Dep = - ivy"io.github.cquiroz::scala-java-locales::1.2.1" - - lazy val logbackClassic: Dep = - ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" - } - - trait AkkaLibs { - - self: SlickLibs => - - object akka { - val V = Versions.akka - val tOrg = "com.typesafe.akka" - val lOrg = "com.lightbend.akka" - - def akkaMod(name: String): Dep = - ivy"$tOrg::akka-$name:$V" - - lazy val actor: Dep = akkaMod("actor") - lazy val actorTyped: Dep = akkaMod("actor-typed") - lazy val stream: Dep = akkaMod("stream") - lazy val persistence: Dep = akkaMod("persistence-typed") - lazy val persistenceQuery: Dep = akkaMod("persistence-query") - lazy val persistenceJdbc: Dep = - ivy"$lOrg::akka-persistence-jdbc:5.0.4" - val persistenceTestKit: Dep = ivy"$tOrg::akka-persistence-testkit:$V" - - object http { - val V = Versions.akkaHttp - - lazy val http: Dep = ivy"$tOrg::akka-http:$V" - lazy val sprayJson: Dep = ivy"$tOrg::akka-http-spray-json:$V" - - } - - object projection { - val V = "1.2.2" - - lazy val core: Dep = - ivy"$lOrg::akka-projection-core:$V" - lazy val eventsourced: Dep = - ivy"$lOrg::akka-projection-eventsourced:$V" - lazy val slick: Dep = - ivy"$lOrg::akka-projection-slick:$V" - lazy val jdbc: Dep = - ivy"$lOrg::akka-projection-jdbc:$V" - } - - object profiles { - // TODO: deal with cross-version for Scala3 (for3Use2_13) - lazy val eventsourcedJdbcProjection: Agg[Dep] = Agg( - persistenceQuery, - projection.core, - projection.eventsourced, - projection.slick, - persistenceJdbc - ) ++ slick.default - } - } - } - - trait SlickLibs { - object slick { - val V = IWMaterials.Versions.slick - val org = "com.typesafe.slick" - - lazy val slick: Dep = ivy"$org::slick:$V" - lazy val hikaricp: Dep = ivy"$org::slick-hikaricp:$V" - lazy val default: Agg[Dep] = Agg(slick, hikaricp) - } - } - -} diff --git a/iw/build.sc b/iw/build.sc deleted file mode 100644 index fbbc3b8..0000000 --- a/iw/build.sc +++ /dev/null @@ -1,144 +0,0 @@ -import mill._, scalalib._, scalajslib._ - -import $file.bom - -object support { - val Deps = bom.IWMaterials.Deps - val Versions = bom.IWMaterials.Versions - - trait CommonModule extends ScalaModule { - def scalaVersion = "3.1.1" - def scalacOptions = Seq( - "-encoding", - "utf8", - "-deprecation", - "-explain-types", - "-explain", - "-feature", - "-language:experimental.macros", - "-language:higherKinds", - "-language:implicitConversions", - "-unchecked", - "-Xfatal-warnings", - "-Ykind-projector" - ) - } - - trait CommonJSModule extends CommonModule with ScalaJSModule { - def scalaJSVersion = "1.8.0" - } - - trait CrossPlatformModule extends Module { outer => - def ivyDeps: T[Agg[Dep]] = Agg[Dep]() - def jsDeps: T[Agg[Dep]] = Agg[Dep]() - def jvmDeps: T[Agg[Dep]] = Agg[Dep]() - def moduleDeps: Seq[Module] = Seq[Module]() - - trait PlatformModule extends CommonModule { - def platform: String - override def millSourcePath = outer.millSourcePath - override def ivyDeps = outer.ivyDeps() ++ (platform match { - case "js" => jsDeps() - case _ => jvmDeps() - }) - override def moduleDeps = outer.moduleDeps.collect { - case m: CrossPlatformModule => - platform match { - case "js" => m.js - case _ => m.jvm - } - case m: JavaModule => m - } - } - - trait JsModule extends PlatformModule with CommonJSModule { - def platform = "js" - } - - trait JvmModule extends PlatformModule { - def platform = "jvm" - } - - val js: JsModule - val jvm: JvmModule - } - - trait PureCrossModule extends CrossPlatformModule { - override object js extends JsModule - override object jvm extends JvmModule - } - - trait PureCrossSbtModule extends CrossPlatformModule { - override object js extends JsModule with SbtModule - override object jvm extends JvmModule with SbtModule - } - - trait FullCrossSbtModule extends CrossPlatformModule { - trait FullSources extends JavaModule { self: PlatformModule => - override def sources = T.sources( - millSourcePath / platform / "src" / "main" / "scala", - millSourcePath / "shared" / "src" / "main" / "scala" - ) - - override def resources = T.sources( - millSourcePath / platform / "src" / "main" / "resources", - millSourcePath / "shared" / "src" / "main" / "resources" - ) - } - - override object js extends JsModule with FullSources - override object jvm extends JvmModule with FullSources - } - -} - -import support._ - -object mongo extends CommonModule { - def ivyDeps = Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - ivy"org.mongodb.scala::mongo-scala-driver:4.2.3".withDottyCompat( - scalaVersion() - ) - ) -} - -object tapir extends FullCrossSbtModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson, Deps.zioJson) - def jvmDeps = Agg(Deps.tapirZIO, Deps.tapirZIOHttp4sServer) - def jsDeps = Agg(Deps.tapirSttpClient) -} - -object akkaPersistence extends CommonModule { - def millSourcePath = build.millSourcePath / "akka-persistence" - def ivyDeps = (Agg( - Deps.akka.persistenceQuery, - Deps.akka.persistenceJdbc, - Deps.akka.projection.core, - Deps.akka.projection.eventsourced, - Deps.akka.projection.slick - ) ++ Deps.slick.default).map(_.withDottyCompat(scalaVersion())) ++ Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - Deps.akka.persistence.withDottyCompat(scalaVersion()), - ivy"com.typesafe.akka::akka-cluster-sharding-typed:${Deps.akka.V}" - .withDottyCompat(scalaVersion()) - ) -} - -object ui extends CommonJSModule { - def ivyDeps = Agg( - Deps.zio, - Deps.laminar, - Deps.zioJson, - Deps.waypoint, - Deps.urlDsl, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) -} diff --git a/iw/domain.sc b/iw/domain.sc deleted file mode 100644 index b09c56b..0000000 --- a/iw/domain.sc +++ /dev/null @@ -1,78 +0,0 @@ -import mill._, scalalib._ - -import $file.{build => ff}, ff.support._ - -trait DomainModule extends Module { - // General extra model deps - def modelModules: Seq[Module] = Seq.empty - // General extra codecs deps - def codecsModules: Seq[Module] = Seq.empty - // General extra endpoints deps - def endpointsModules: Seq[Module] = Seq.empty - - // Implementation deps for repo - def repoModules: Seq[JavaModule] = Seq.empty - - object shared extends Module { - object model extends PureCrossModule { - def moduleDeps = modelModules - def ivyDeps = Agg(Deps.zioPrelude) - } - object codecs extends PureCrossModule { - def moduleDeps = Seq(model) ++ codecsModules - def ivyDeps = Agg(Deps.zioJson) - } - } - - trait CommonProjects extends Module { - object model extends PureCrossModule { - def ivyDeps = Agg(Deps.zioPrelude) - def moduleDeps = Seq(shared.model) - } - object codecs extends PureCrossModule { - def moduleDeps = - Seq(model, shared.model, shared.codecs, ff.tapir) - } - object endpoints extends PureCrossModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson) - def moduleDeps = Seq(model, codecs) ++ endpointsModules - } - object client extends CommonJSModule { - def moduleDeps = Seq(endpoints.js) - } - object components extends CommonJSModule { - def ivyDeps = Agg( - Deps.laminar, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) - def moduleDeps = Seq(model.js, codecs.js, client, ff.ui) - } - } - - object query extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(repo, query.endpoints.jvm) - } - object repo extends CommonModule { - def ivyDeps = Agg(Deps.zio) - def moduleDeps = Seq(model.jvm, codecs.jvm) ++ repoModules - } - object projection extends CommonModule { - def moduleDeps = Seq(repo, ff.akkaPersistence) - } - } - - object command extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(entity, command.endpoints.jvm) - } - object entity extends CommonModule { - def moduleDeps = Seq(model.jvm, codecs.jvm, ff.akkaPersistence) - } - } -} diff --git a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala b/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala deleted file mode 100644 index 0fa3493..0000000 --- a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala +++ /dev/null @@ -1,53 +0,0 @@ -package works.iterative.mongo - -import zio.* -import zio.json.* -import zio.config.* -import org.mongodb.scala.* -import org.mongodb.scala.model.Filters.* -import org.bson.json.JsonObject -import org.mongodb.scala.model.ReplaceOptions -import org.mongodb.scala.bson.conversions.Bson - -case class MongoConfig(uri: String) - -object MongoConfig: - val configDesc = - import ConfigDescriptor.* - nested("MONGO")(string("URI").default("mongodb://localhost:27017")) - .to[MongoConfig] - val fromEnv = ZConfig.fromSystemEnv(configDesc) - -extension (m: MongoClient.type) - def layer: RLayer[MongoConfig, MongoClient] = - ZIO - .serviceWithZIO[MongoConfig](c => Task.attempt(MongoClient(c.uri))) - .toLayer - -class MongoJsonRepository[Elem, Key, Criteria]( - collection: MongoCollection[JsonObject], - toFilter: Criteria => Bson, - idFilter: Elem => (String, Key) -)(using JsonCodec[Elem]) { - def matching(criteria: Criteria): Task[Seq[Elem]] = - val filter = toFilter(criteria) - val query = collection.find(filter) - - for - result <- ZIO.fromFuture(_ => query.toFuture) - proof <- ZIO.collect(result)(j => - ZIO.fromOption(j.getJson.fromJson[Elem].toOption) - ) - yield proof - - def put(elem: Elem): Task[Unit] = - Task.async(cb => - collection - .replaceOne( - equal.tupled(idFilter(elem)), - JsonObject(elem.toJson), - ReplaceOptions().upsert(true) - ) - .subscribe(_ => cb(Task.unit), t => cb(Task.fail(t))) - ) -} diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala deleted file mode 100644 index 68d7196..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala +++ /dev/null @@ -1,12 +0,0 @@ -package works.iterative.tapir - -import sttp.model.Uri - -opaque type BaseUri = Option[Uri] - -object BaseUri: - - def apply(optU: Option[Uri]): BaseUri = optU - def apply(u: Uri): BaseUri = Some(u) - - extension (v: BaseUri) def toUri: Option[Uri] = v diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 653bc51..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,22 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.client.sttp.SttpClientInterpreter -import sttp.tapir.PublicEndpoint -import sttp.tapir.client.sttp.WebSocketToPipe -import scala.concurrent.Future -import sttp.client3.SttpBackend -import sttp.capabilities.WebSockets - -trait CustomTapirPlatformSpecific extends SttpClientInterpreter: - self: CustomTapir => - - type Backend = SttpBackend[Future, WebSockets] - - def makeClient[I, E, O]( - endpoint: PublicEndpoint[I, E, O, Any] - )(using - baseUri: BaseUri, - backend: Backend, - wsToPipe: WebSocketToPipe[Any] - ): I => Future[O] = - toClientThrowErrors(endpoint, baseUri.toUri, backend) diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 2545fbd..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,5 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.ztapir.ZTapir - -trait CustomTapirPlatformSpecific extends ZTapir diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala deleted file mode 100644 index 55e4ca1..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala +++ /dev/null @@ -1,7 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.server.http4s.ztapir.ZHttp4sServerInterpreter - -trait Http4sCustomTapir[Env] - extends CustomTapir - with ZHttp4sServerInterpreter[Env] diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala deleted file mode 100644 index dd52e9b..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala +++ /dev/null @@ -1,14 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.Tapir -import sttp.tapir.json.zio.TapirJsonZio -import sttp.tapir.TapirAliases - -trait CustomTapir - extends Tapir - with TapirJsonZio - with TapirAliases - with CustomTapirPlatformSpecific: - given Schema[ServerError] = Schema.derived - -object CustomTapir extends CustomTapir diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala deleted file mode 100644 index 715591d..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.tapir - -import zio.json.* - -sealed trait ServerError -case class InternalServerError(msg: String) extends ServerError -object InternalServerError: - def fromThrowable(t: Throwable): ServerError = InternalServerError( - t.getMessage - ) - -object ServerError: - given JsonCodec[ServerError] = DeriveJsonCodec.gen diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/File.scala deleted file mode 100644 index 09f6c07..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala +++ /dev/null @@ -1,3 +0,0 @@ -package works.iterative.services.files - -case class File(url: String, name: String) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala deleted file mode 100644 index 46633a5..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala +++ /dev/null @@ -1,25 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.Renderable - -given Renderable[File] with - extension (m: File) - def toHtml: HtmlElement = - li( - cls("pl-3 pr-4 py-3 flex items-center justify-between text-sm"), - div( - cls("w-0 flex-1 flex items-center"), - Icons.solid - .paperclip() - .amend(svg.cls := "flex-shrink-0 text-gray-400"), - span(cls("ml-2 flex-1 w-0 truncate"), m.name) - ), - a( - href(m.url), - cls("font-medium text-indigo-600 hover:text-indigo-500"), - "Otevřít" - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala deleted file mode 100644 index ac59db8..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import io.laminext.syntax.core.{*, given} - -def FileList(files: List[File]): HtmlElement = - ul( - role("list"), - cls("border border-gray-200 rounded-md divide-y divide-gray-200"), - files.map(_.toHtml) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala deleted file mode 100644 index 2e8f690..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala +++ /dev/null @@ -1,81 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import io.laminext.syntax.core.{*, given} - -object FilePicker: - sealed trait Event - sealed trait DoneEvent extends Event - case class SelectionUpdated(files: Set[File]) extends DoneEvent - case object SelectionCancelled extends DoneEvent - case object AvailableFilesRequested extends Event - - def apply( - currentFiles: Signal[List[File]], - availableFilesStream: EventStream[List[File]] - )(selectionUpdates: Observer[Event]): HtmlElement = - val (updatesStream, updatesObserver) = EventStream.withObserver[Event] - val selectorOpen = Var[Boolean](false) - - // This sequence tricks browser into displaying modal content centered - // Inspired by modal in headless ui playground - // https://github.com/tailwindlabs/headlessui/blob/fdd26297953080d5ec905dda0bf5ec9607897d86/packages/playground-react/pages/transitions/component-examples/modal.tsx#L78-L79 - inline def browserCenteringModalTrick: Modifier[HtmlElement] = - Seq[Modifier[HtmlElement]]( - span(cls("hidden sm:inline-block sm:h-screen sm:align-middle")), - "​" // Zero width space - ) - - inline def overlay: Modifier[HtmlElement] = - // Page overlay - /* TODO: transition - enter="ease-out duration-300" - enterFrom="opacity-0" - enterTo="opacity-100" - leave="ease-in duration-200" - leaveFrom="opacity-100" - leaveTo="opacity-0" - */ - div( - div( - onClick.mapTo(false) --> selectorOpen.writer, - cls("fixed inset-0 transition-opacity"), - div(cls("absolute inset-0 bg-gray-500 opacity-75")) - ) - ) - - inline def modalSelector: HtmlElement = - div( - cls("fixed inset-0 z-10 overflow-y-auto"), - div( - cls("text-center sm:block sm:p-0"), - overlay, - browserCenteringModalTrick, - child <-- currentFiles.map( - FileSelector(_, availableFilesStream)(updatesObserver) - ) - ) - ) - - div( - updatesStream --> selectionUpdates, - updatesStream.collect { case _: DoneEvent => - false - } --> selectorOpen.writer, - child.maybe <-- selectorOpen.signal.map(isOpen => - if isOpen then Some(modalSelector) else None - ), - div( - cls("flex flex-col space-y-5"), - child.maybe <-- currentFiles.map(files => - if files.isEmpty then None else Some(FileList(files)) - ), - button( - tpe := "button", - cls := "bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500", - "Zvolit soubory", - onClick.mapTo(true) --> selectorOpen.writer - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala deleted file mode 100644 index b43e07a..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala +++ /dev/null @@ -1,74 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.Loading -import io.laminext.syntax.core.{*, given} -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -object FileSelector: - import FilePicker._ - - def apply( - initialFiles: List[File], - availableFilesStream: EventStream[List[File]] - )(selectionUpdates: Observer[Event]): HtmlElement = - val selectedFiles = Var[Set[File]](initialFiles.to(Set)) - val availableFiles = availableFilesStream.startWithNone - // Request the files to display - selectionUpdates.onNext(AvailableFilesRequested) - div( - cls( - "inline-block transform overflow-hidden rounded-lg bg-white text-left align-bottom shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:align-middle" - ), - role("dialog"), - customHtmlAttr("aria.modal", BooleanAsTrueFalseStringCodec)(true), - aria.labelledBy("modal-headline"), - div( - cls("bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"), - div( - cls("sm:flex sm:items-start"), - div( - cls("mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"), - h3( - cls("text-lg font-medium leading-6 text-gray-900"), - idAttr("modal-headline"), - "Výběr souborů" - ) - ) - ), - child <-- availableFiles - .split(_ => ())((_, _, af) => FileTable(af, selectedFiles)) - .map(_.getOrElse(Loading)) - ), - div( - cls("bg-gray-50 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6"), - span( - cls("flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto"), - button( - typ("button"), - cls( - "focus:shadow-outline-green inline-flex w-full justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-base font-medium leading-6 text-white shadow-sm transition duration-150 ease-in-out hover:bg-indigo-500 focus:border-indigo-700 focus:outline-none sm:text-sm sm:leading-5" - ), - "Potvrdit", - composeEvents(onClick)( - _.sample(selectedFiles) - .map(SelectionUpdated(_)) - ) --> selectionUpdates - ) - ), - span( - cls("mt-3 flex w-full rounded-md shadow-sm sm:mt-0 sm:w-auto"), - button( - typ("button"), - cls( - "focus:shadow-outline-blue inline-flex w-full justify-center rounded-md border border-gray-300 bg-white px-4 py-2 text-base font-medium leading-6 text-gray-700 shadow-sm transition duration-150 ease-in-out hover:text-gray-500 focus:border-blue-300 focus:outline-none sm:text-sm sm:leading-5" - ), - "Zrušit", - onClick.mapTo(SelectionCancelled) --> selectionUpdates - ) - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala deleted file mode 100644 index aa11b2b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala +++ /dev/null @@ -1,91 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import works.iterative.ui.components.tailwind.Icons - -def FileTable( - files: Signal[List[File]], - selectedFiles: Var[Set[File]] -): HtmlElement = - val scope = customHtmlAttr("scope", StringAsIsCodec) - - def headerRow: HtmlElement = - val col = scope("col") - val baseM: Modifier[HtmlElement] = Seq(cls("px-6 py-3"), col) - val textH = cls( - "text-left text-xs font-medium text-gray-500 uppercase tracking-wider" - ) - tr( - th(baseM, span(cls("sr-only"), "Vybrat")), - th(baseM, textH, "Název"), - th(baseM, cls("relative"), span(cls("sr-only"), "Otevřít")) - ) - - def tableRow( - f: File, - idx: Int, - selected: Boolean - )(toggleSelection: Observer[Unit]): HtmlElement = - val baseC = cls("px-6 py-4 whitespace-nowrap text-sm") - tr( - cls(if idx % 2 == 0 then "bg-gray-50" else "bg-white"), - td( - cls("font-medium cursor-pointer"), - onClick.mapTo(()) --> toggleSelection, - cls(if selected then "text-green-900" else "text-gray-200"), - Icons.outline.`check-circle`().amend(svg.cls := "mx-auto"), - span(cls("sr-only"), if selected then "Vybráno" else "Nevybráno") - ), - td( - baseC, - cls("font-medium text-gray-900"), - f.name, - onClick.mapTo(()) --> toggleSelection - ), - td( - baseC, - cls("text-right font-medium"), - a( - href(f.url), - target("_blank"), - cls( - "flex items-center space-x-4 text-indigo-600 hover:text-indigo-900" - ), - Icons.outline.`external-link`(), - "Otevřít" - ) - ) - ) - - div( - cls("flex flex-col"), - div(cls("overflow-x-auto sm:-mx-6 lg:-mx-8")), - div( - cls("py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8"), - div( - cls("shadow overflow-hidden border-b border-gray-200 sm:rounded-lg"), - table( - cls("min-w-full divide-y divide-gray-200"), - thead( - cls("bg-gray-50"), - headerRow - ), - tbody( - children <-- files - .map(_.zipWithIndex) - .combineWithFn(selectedFiles)((f, sel) => - f.map((file, idx) => - val active = sel.contains(file) - tableRow(file, idx, active)( - selectedFiles.writer - .contramap(_ => if active then sel - file else sel + file) - ) - ) - ) - ) - ) - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala deleted file mode 100644 index 0fc8796..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala +++ /dev/null @@ -1,32 +0,0 @@ -package works.iterative.ui.components.tailwind - -import CustomAttrs.ariaHidden -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -// TODO: render icon or picture based on img signal -class Avatar($avatarImg: Signal[Option[String]]): - inline def avatarPlaceholder(size: Int): HtmlElement = - div( - cls := s"rounded-full text-indigo-200 bg-indigo-500 h-${size} w-${size} flex items-center justify-center", - Icons.outline.user(size - 2) - ) - - inline def avatarImage(size: Int): Signal[HtmlElement] = - $avatarImg.split(_ => ())((_, _, $url) => - img( - cls := s"w-$size h-$size rounded-full", - src <-- $url, - alt := "" - ) - ).map(_.getOrElse(avatarPlaceholder(size))) - - inline def avatar(size: Int): HtmlElement = - div( - cls := "relative", - child <-- avatarImage(size), - span( - cls := "absolute inset-0 shadow-inner rounded-full", - ariaHidden := true - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala deleted file mode 100644 index bcbed88..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala +++ /dev/null @@ -1,77 +0,0 @@ -package works.iterative.ui.components.tailwind - -enum ColorWeight(value: String): - inline def toCSS: String = value - // To be used for black and white until - // we support better mechanism via extension methods - case w__ extends ColorWeight("") - case w50 extends ColorWeight("50") - case w100 extends ColorWeight("100") - case w200 extends ColorWeight("200") - case w300 extends ColorWeight("300") - case w400 extends ColorWeight("400") - case w500 extends ColorWeight("500") - case w600 extends ColorWeight("600") - case w700 extends ColorWeight("700") - case w800 extends ColorWeight("800") - case w900 extends ColorWeight("900") - -enum Color(name: String): - import ColorWeight._ - - inline def toCSSNoColorWeight(prefix: String): String = - s"${prefix}-${name}" - inline def toCSSWithColorWeight( - prefix: String, - weight: ColorWeight - ): String = - s"${prefix}-${name}-${weight.toCSS}" - inline def toCSS(prefix: String)(weight: ColorWeight): String = - weight match { - case `w__` => toCSSNoColorWeight(prefix) - case _ => toCSSWithColorWeight(prefix, weight) - } - inline def bg: ColorWeight => String = toCSS("bg")(_) - inline def text: ColorWeight => String = toCSS("text")(_) - inline def decoration: ColorWeight => String = toCSS("decoration")(_) - inline def border: ColorWeight => String = toCSS("border")(_) - inline def outline: ColorWeight => String = toCSS("outline")(_) - inline def divide: ColorWeight => String = toCSS("divide")(_) - inline def ring: ColorWeight => String = toCSS("ring")(_) - inline def ringOffset: ColorWeight => String = toCSS("ring-offset")(_) - inline def shadow: ColorWeight => String = toCSS("shadow")(_) - inline def accent: ColorWeight => String = toCSS("accent")(_) - - // TODO: change the "stupid" methods to extension methods - // that will keep the invariants in comments lower - case current extends Color("current") - case inherit extends Color("inherit") - // Not present in for all methods - case transp extends Color("transparent") - // Seen in accent, not preset otherwise - case auto extends Color("auto") - // Black and white do not have weight - case black extends Color("black") - case white extends Color("white") - case slate extends Color("slate") - case gray extends Color("gray") - case zinc extends Color("zinc") - case neutral extends Color("neutral") - case stone extends Color("stone") - case red extends Color("red") - case orange extends Color("orange") - case amber extends Color("amber") - case yellow extends Color("yellow") - case lime extends Color("lime") - case green extends Color("green") - case emerald extends Color("emerald") - case teal extends Color("teal") - case cyan extends Color("cyan") - case sky extends Color("sky") - case blue extends Color("blue") - case indigo extends Color("indigo") - case violet extends Color("violet") - case purple extends Color("purple") - case fuchsia extends Color("fuchsia") - case pink extends Color("pink") - case rose extends Color("rose") diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala deleted file mode 100644 index 6fb453c..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -object CustomAttrs { - // Made a pull request to add aria-current to scala-dom-types, remove after - val ariaCurrent = customHtmlAttr("aria-current", StringAsIsCodec) - val ariaHidden = customHtmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - - val datetime = customHtmlAttr("datetime", StringAsIsCodec) - - object svg { - import com.raquo.laminar.api.L.svg.{*, given} - val ariaHidden = - customSvgAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - } -} diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala deleted file mode 100644 index 6cda48b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala +++ /dev/null @@ -1,28 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -object Display: - - enum Breakpoint: - case sm, md, lg, xl, `2xl` - - enum DisplayClass: - case block, `inline-block`, `inline`, flex, `inline-flex`, table, - `inline-table`, `table-caption` - - object ShowUpFrom: - inline def apply( - br: Breakpoint, - dc: DisplayClass = DisplayClass.block - ): HtmlElement = - div( - cls := "hidden", - cls := s"${br}:${dc}" - ) - - object HideUpTo: - inline def apply(br: Breakpoint): HtmlElement = - div( - cls := s"${br}:hidden" - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala deleted file mode 100644 index 4a8b065..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala +++ /dev/null @@ -1,270 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec -import com.raquo.domtypes.generic.defs.attrs.AriaAttrs -import com.raquo.laminar.api.L.svg.{*, given} -import com.raquo.laminar.builders.SvgBuilders -import com.raquo.laminar.keys.ReactiveSvgAttr - -// TODO: fix sizes, colors, hover and stuff, normalize and amend on call site -object Icons: - object aria: - inline def hidden = CustomAttrs.svg.ariaHidden - - object outline: - val defaultSize: Int = 6 - - inline def bell(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" - ) - ) - - inline def `check-circle`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" - ) - ) - - inline def `document-add`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M9 13h6m-3-3v6m5 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" - ) - ) - - inline def `external-link`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" - ) - ) - - inline def menu(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M4 6h16M4 12h16M4 18h16" - ) - ) - - inline def `status-offline`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M18.364 5.636a9 9 0 010 12.728m0 0l-2.829-2.829m2.829 2.829L21 21M15.536 8.464a5 5 0 010 7.072m0 0l-2.829-2.829m-4.243 2.829a4.978 4.978 0 01-1.414-2.83m-1.414 5.658a9 9 0 01-2.167-9.238m7.824 2.167a1 1 0 111.414 1.414m-1.414-1.414L3 3m8.293 8.293l1.414 1.414" - ) - ) - - inline def user(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" - ) - ) - - inline def x(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M6 18L18 6M6 6l12 12" - ) - ) - - end outline - - object solid: - val defaultSize: Int = 5 - - inline def users(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - d := "M9 6a3 3 0 11-6 0 3 3 0 016 0zM17 6a3 3 0 11-6 0 3 3 0 016 0zM12.93 17c.046-.327.07-.66.07-1a6.97 6.97 0 00-1.5-4.33A5 5 0 0119 16v1h-6.07zM6 11a5 5 0 015 5v1H1v-1a5 5 0 015-5z" - ) - ) - - inline def `location-marker`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M5.05 4.05a7 7 0 119.9 9.9L10 18.9l-4.95-4.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z", - clipRule := "evenodd" - ) - ) - - inline def calendar(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z", - clipRule := "evenodd" - ) - ) - - inline def `chevron-right`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z", - clipRule := "evenodd" - ) - ) - - inline def search(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z", - clipRule := "evenodd" - ) - ) - - inline def filter(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M3 3a1 1 0 011-1h12a1 1 0 011 1v3a1 1 0 01-.293.707L12 11.414V15a1 1 0 01-.293.707l-2 2A1 1 0 018 17v-5.586L3.293 6.707A1 1 0 013 6V3z", - clipRule := "evenodd" - ) - ) - - inline def `arrow-narrow-left`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M7.707 14.707a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 1.414L5.414 9H17a1 1 0 110 2H5.414l2.293 2.293a1 1 0 010 1.414z", - clipRule := "evenodd" - ) - ) - - inline def home(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - d := "M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z" - ) - ) - - inline def paperclip(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size} text-gray-400", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M8 4a3 3 0 00-3 3v4a5 5 0 0010 0V7a1 1 0 112 0v4a7 7 0 11-14 0V7a5 5 0 0110 0v4a3 3 0 11-6 0V7a1 1 0 012 0v4a1 1 0 102 0V7a3 3 0 00-3-3z", - clipRule := "evenodd" - ) - ) - - end solid -end Icons diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/LinkSupport.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/LinkSupport.scala deleted file mode 100644 index 88f0b9a..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/LinkSupport.scala +++ /dev/null @@ -1,12 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.jsdom.defs.events.TypedTargetMouseEvent - -object LinkSupport: - - extension [El <: org.scalajs.dom.EventTarget]( - ep: EventProcessor[TypedTargetMouseEvent[El], TypedTargetMouseEvent[El]] - ) - def noKeyMod = - ep.filter(ev => !(ev.ctrlKey || ev.metaKey || ev.shiftKey || ev.altKey)) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Loader.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Loader.scala deleted file mode 100644 index 5399933..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Loader.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -// TODO: proper loader -def Loading = - div( - cls := "bg-gray-50 overflow-hidden rounded-lg", - div( - cls := "px-4 py-5 sm:p-6", - "Loading..." - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Renderable.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Renderable.scala deleted file mode 100644 index a0fcc5b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Renderable.scala +++ /dev/null @@ -1,6 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -trait Renderable[A]: - extension (a: A) def toHtml: HtmlElement diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/ComboBox.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/ComboBox.scala deleted file mode 100644 index 8c5b8a1..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/ComboBox.scala +++ /dev/null @@ -1,92 +0,0 @@ -package works.iterative.ui.components.tailwind -package form - -import com.raquo.laminar.api.L.{*, given} -import io.laminext.syntax.core.* - -case class ComboBox( - id: String, - options: Signal[List[ComboBox.Option]], - valueUpdates: Observer[List[String]] -) - -object ComboBox: - - extension (m: ComboBox) - def toHtml: HtmlElement = - val isOpen = Var(false) - div( - cls := "relative mt-1", - input( - idAttr := m.id, - tpe := "text", - cls := "w-full rounded-md border border-gray-300 bg-white py-2 pl-3 pr-12 shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 sm:text-sm", - role := "combobox", - aria.controls := "options", - aria.expanded := false, - onClick.mapTo(true) --> isOpen.writer - ), - button( - tpe := "button", - cls := "absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-none", { - import svg.* - svg( - cls := "h-5 w-5 text-gray-400", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - CustomAttrs.svg.ariaHidden := true, - path( - fillRule := "evenodd", - d := "M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z", - clipRule := "evenodd" - ) - ) - } - ), - ul( - cls <-- isOpen.signal.switch("", "hidden"), - cls := "absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm", - idAttr := "options", - role := "listbox", - children <-- m.options.map(_.map(_.toHtml)) - ) - ) - - case class Option(value: String, active: Boolean) - - object Option: - extension (m: Option) - def toHtml: HtmlElement = - li( - cls := "relative cursor-default select-none py-2 pl-8 pr-4", - cls := (if m.active then "text-white bg-indigo-600" - else "text-gray-900"), - idAttr := "option-0", - role := "option", - tabIndex := -1, - span( - cls := "block truncate", - m.value - ), - if m.active then - span( - cls := "absolute inset-y-0 left-0 flex items-center pl-1.5", - cls := (if m.active then "text-white" else "text-indigo-600"), { - import svg.* - svg( - cls := "h-5 w-5", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - CustomAttrs.svg.ariaHidden := true, - path( - fillRule := "evenodd", - d := "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", - clipRule := "evenodd" - ) - ) - } - ) - else emptyNode - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/Form.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/Form.scala deleted file mode 100644 index 1b41dee..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/Form.scala +++ /dev/null @@ -1,17 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} -import org.scalajs.dom -import com.raquo.laminar.nodes.ReactiveHtmlElement - -object Form: - val Body = FormBody - val Section = FormSection - val Row = FormRow - - def apply(body: HtmlElement, buttons: HtmlElement): HtmlElement = - form( - cls := "space-y-8 divide-y divide-gray-200", - body, - buttons - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormBody.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormBody.scala deleted file mode 100644 index 89abc9c..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormBody.scala +++ /dev/null @@ -1,10 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} - -object FormBody: - def apply(sections: HtmlElement*): HtmlElement = - div( - cls := "space-y-8 divide-y divide-gray-200 sm:space-y-5", - sections - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormFields.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormFields.scala deleted file mode 100644 index a2e4cc6..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormFields.scala +++ /dev/null @@ -1,14 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.laminar.nodes.ReactiveHtmlElement -import org.scalajs.dom - -object FormFields: - def apply( - mods: Modifier[ReactiveHtmlElement[dom.HTMLElement]]* - ): HtmlElement = - div( - cls := "mt-6 sm:mt-5 space-y-6 sm:space-y-5", - mods - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala deleted file mode 100644 index e7e6627..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.akka - -// Base class for all command-processing related exceptions from handlers -sealed abstract class CommandHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandNotAvailable[C, S](cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd není dostupný ve stavu $state" - ) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandRejected[C, S](reason: String, cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd byl ve stavu $state odmítnut s odůvodněním $reason" - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala deleted file mode 100644 index 72a5bf9..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.akka - -sealed abstract class EventHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -case class UnhandledEvent[Event, State](event: Event, state: State) - extends EventHandlerException( - s"Událost $event nastala ve stavu $state bez možnosti zpracování" - ) diff --git a/iw/bom.sc b/iw/bom.sc deleted file mode 100644 index e9266b8..0000000 --- a/iw/bom.sc +++ /dev/null @@ -1,227 +0,0 @@ -import mill._, scalalib._ - -object IWMaterials { - - object Versions { - val akka = "2.6.16" - val akkaHttp = "10.2.4" - val cats = "2.7.0" - val elastic4s = "7.12.2" - val http4s = "0.23.10" - val http4sPac4J = "4.0.0" - val laminar = "0.14.2" - val laminext = laminar - val logbackClassic = "1.2.10" - val pac4j = "5.2.0" - val play = "2.8.8" - val playJson = "2.9.2" - val scalaTest = "3.2.9" - val slick = "3.3.3" - val sttpClient = "3.5.0" - val tapir = "0.20.1" - val urlDsl = "0.4.0" - val waypoint = "0.5.0" - val zio = "2.0.0-RC2" - val zioConfig = "3.0.0-RC2" - val zioInteropCats = "3.3.0-RC2" - val zioJson = "0.3.0-RC3" - val zioLogging = "2.0.0-RC5" - val zioPrelude = "1.0.0-RC10" - val zioZMX = "0.0.11" - } - - object Deps extends AkkaLibs with SlickLibs { - import IWMaterials.{Versions => V} - - val zioOrg = "dev.zio" - - def zioLib(name: String, version: String): Dep = - ivy"$zioOrg::zio-$name::$version" - - lazy val zio: Dep = ivy"$zioOrg::zio:${V.zio}" - - lazy val zioTest: Dep = zioLib("test", V.zio) - lazy val zioTestSbt: Dep = zioLib("test", V.zio) - - lazy val zioConfig: Dep = zioLib("config", V.zioConfig) - lazy val zioConfigTypesafe: Dep = - zioLib("config-typesafe", V.zioConfig) - lazy val zioConfigMagnolia: Dep = - zioLib("config-magnolia", V.zioConfig) - - lazy val zioJson: Dep = zioLib("json", V.zioJson) - lazy val zioLogging: Dep = zioLib("logging", V.zioLogging) - lazy val zioLoggingSlf4j: Dep = - zioLib("logging-slf4j", V.zioLogging) - lazy val zioPrelude: Dep = zioLib("prelude", V.zioPrelude) - lazy val zioStreams: Dep = zioLib("streams", V.zio) - lazy val zioZMX: Dep = zioLib("zmx", V.zioZMX) - lazy val zioInteropCats: Dep = - zioLib("interop-cats", V.zioInteropCats) - - /* What is the equivalent? ZIOModule with prepared test config? - def useZIO(testConf: Configuration*): Agg[Dep] = Agg( - zio, - zioTest, - zioTestSbt, - testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework") - ) - */ - - /* - def useZIOAll(testConf: Configuration*): Seq[Def.Setting[_]] = - useZIO(testConf: _*) ++ Seq( - zioStreams, - zioConfig, - zioConfigTypesafe, - zioConfigMagnolia, - zioJson, - zioZMX, - zioLogging, - zioPrelude - ) - */ - - private val tapirOrg = "com.softwaremill.sttp.tapir" - def tapirLib(name: String): Dep = - ivy"${tapirOrg}::tapir-$name::${V.tapir}" - - lazy val tapirCore: Dep = tapirLib("core") - lazy val tapirZIO: Dep = tapirLib("zio") - lazy val tapirZIOJson: Dep = tapirLib("json-zio") - lazy val tapirSttpClient: Dep = tapirLib("sttp-client") - lazy val tapirCats: Dep = tapirLib("cats") - lazy val tapirZIOHttp4sServer: Dep = tapirLib("zio-http4s-server") - - private val sttpClientOrg = "com.softwaremill.sttp.client3" - def sttpClientLib(name: String): Dep = - ivy"${sttpClientOrg}::${name}:${V.sttpClient}" - - lazy val sttpClientCore: Dep = sttpClientLib("core") - - lazy val http4sBlazeServer: Dep = - ivy"org.http4s::http4s-blaze-server:${V.http4s}" - - lazy val http4sPac4J: Dep = - ivy"org.pac4j::http4s-pac4j:${V.http4sPac4J}" - lazy val pac4jOIDC: Dep = - ivy"org.pac4j:pac4j-oidc:${V.pac4j}" - - lazy val scalaTest: Dep = - ivy"org.scalatest::scalatest:${V.scalaTest}" - lazy val scalaTestPlusScalacheck: Dep = - ivy"org.scalatestplus::scalacheck-1-15:3.2.9.0" - lazy val playScalaTest: Dep = - ivy"org.scalatestplus.play::scalatestplus-play:5.1.0" - - private val playOrg = "com.typesafe.play" - lazy val playMailer: Dep = ivy"${playOrg}::play-mailer:8.0.1" - lazy val playServer: Dep = ivy"${playOrg}::play-server:${V.play}" - lazy val playAkkaServer: Dep = - ivy"${playOrg}::play-akka-http-server:${V.play}" - lazy val play: Dep = ivy"${playOrg}::play:${V.play}" - lazy val playAhcWs: Dep = ivy"${playOrg}::play-ahc-ws:${V.play}" - lazy val playJson: Dep = ivy"${playOrg}::play-json:${V.playJson}" - - private val elastic4sOrg = "com.sksamuel.elastic4s" - lazy val useElastic4S: Agg[Dep] = Agg( - ivy"${elastic4sOrg}::elastic4s-core:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-client-akka:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-http-streams:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-json-play:${V.elastic4s}" - ) - - lazy val laminar: Dep = ivy"com.raquo::laminar::${V.laminar}" - - private def laminext(name: String): Dep = - ivy"io.laminext::$name::${V.laminar}" - - lazy val laminextCore: Dep = laminext("core") - lazy val laminextTailwind: Dep = laminext("tailwind") - lazy val laminextFetch: Dep = laminext("fetch") - lazy val laminextValidationCore: Dep = laminext("validation-core") - lazy val laminextUI: Dep = laminext("ui") - - lazy val waypoint: Dep = - ivy"com.raquo::waypoint::${V.waypoint}" - - lazy val urlDsl: Dep = - ivy"be.doeraene::url-dsl::${V.urlDsl}" - - lazy val scalaJavaTime: Dep = - ivy"io.github.cquiroz::scala-java-time::2.3.0" - - lazy val scalaJavaLocales: Dep = - ivy"io.github.cquiroz::scala-java-locales::1.2.1" - - lazy val logbackClassic: Dep = - ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" - } - - trait AkkaLibs { - - self: SlickLibs => - - object akka { - val V = Versions.akka - val tOrg = "com.typesafe.akka" - val lOrg = "com.lightbend.akka" - - def akkaMod(name: String): Dep = - ivy"$tOrg::akka-$name:$V" - - lazy val actor: Dep = akkaMod("actor") - lazy val actorTyped: Dep = akkaMod("actor-typed") - lazy val stream: Dep = akkaMod("stream") - lazy val persistence: Dep = akkaMod("persistence-typed") - lazy val persistenceQuery: Dep = akkaMod("persistence-query") - lazy val persistenceJdbc: Dep = - ivy"$lOrg::akka-persistence-jdbc:5.0.4" - val persistenceTestKit: Dep = ivy"$tOrg::akka-persistence-testkit:$V" - - object http { - val V = Versions.akkaHttp - - lazy val http: Dep = ivy"$tOrg::akka-http:$V" - lazy val sprayJson: Dep = ivy"$tOrg::akka-http-spray-json:$V" - - } - - object projection { - val V = "1.2.2" - - lazy val core: Dep = - ivy"$lOrg::akka-projection-core:$V" - lazy val eventsourced: Dep = - ivy"$lOrg::akka-projection-eventsourced:$V" - lazy val slick: Dep = - ivy"$lOrg::akka-projection-slick:$V" - lazy val jdbc: Dep = - ivy"$lOrg::akka-projection-jdbc:$V" - } - - object profiles { - // TODO: deal with cross-version for Scala3 (for3Use2_13) - lazy val eventsourcedJdbcProjection: Agg[Dep] = Agg( - persistenceQuery, - projection.core, - projection.eventsourced, - projection.slick, - persistenceJdbc - ) ++ slick.default - } - } - } - - trait SlickLibs { - object slick { - val V = IWMaterials.Versions.slick - val org = "com.typesafe.slick" - - lazy val slick: Dep = ivy"$org::slick:$V" - lazy val hikaricp: Dep = ivy"$org::slick-hikaricp:$V" - lazy val default: Agg[Dep] = Agg(slick, hikaricp) - } - } - -} diff --git a/iw/build.sc b/iw/build.sc deleted file mode 100644 index fbbc3b8..0000000 --- a/iw/build.sc +++ /dev/null @@ -1,144 +0,0 @@ -import mill._, scalalib._, scalajslib._ - -import $file.bom - -object support { - val Deps = bom.IWMaterials.Deps - val Versions = bom.IWMaterials.Versions - - trait CommonModule extends ScalaModule { - def scalaVersion = "3.1.1" - def scalacOptions = Seq( - "-encoding", - "utf8", - "-deprecation", - "-explain-types", - "-explain", - "-feature", - "-language:experimental.macros", - "-language:higherKinds", - "-language:implicitConversions", - "-unchecked", - "-Xfatal-warnings", - "-Ykind-projector" - ) - } - - trait CommonJSModule extends CommonModule with ScalaJSModule { - def scalaJSVersion = "1.8.0" - } - - trait CrossPlatformModule extends Module { outer => - def ivyDeps: T[Agg[Dep]] = Agg[Dep]() - def jsDeps: T[Agg[Dep]] = Agg[Dep]() - def jvmDeps: T[Agg[Dep]] = Agg[Dep]() - def moduleDeps: Seq[Module] = Seq[Module]() - - trait PlatformModule extends CommonModule { - def platform: String - override def millSourcePath = outer.millSourcePath - override def ivyDeps = outer.ivyDeps() ++ (platform match { - case "js" => jsDeps() - case _ => jvmDeps() - }) - override def moduleDeps = outer.moduleDeps.collect { - case m: CrossPlatformModule => - platform match { - case "js" => m.js - case _ => m.jvm - } - case m: JavaModule => m - } - } - - trait JsModule extends PlatformModule with CommonJSModule { - def platform = "js" - } - - trait JvmModule extends PlatformModule { - def platform = "jvm" - } - - val js: JsModule - val jvm: JvmModule - } - - trait PureCrossModule extends CrossPlatformModule { - override object js extends JsModule - override object jvm extends JvmModule - } - - trait PureCrossSbtModule extends CrossPlatformModule { - override object js extends JsModule with SbtModule - override object jvm extends JvmModule with SbtModule - } - - trait FullCrossSbtModule extends CrossPlatformModule { - trait FullSources extends JavaModule { self: PlatformModule => - override def sources = T.sources( - millSourcePath / platform / "src" / "main" / "scala", - millSourcePath / "shared" / "src" / "main" / "scala" - ) - - override def resources = T.sources( - millSourcePath / platform / "src" / "main" / "resources", - millSourcePath / "shared" / "src" / "main" / "resources" - ) - } - - override object js extends JsModule with FullSources - override object jvm extends JvmModule with FullSources - } - -} - -import support._ - -object mongo extends CommonModule { - def ivyDeps = Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - ivy"org.mongodb.scala::mongo-scala-driver:4.2.3".withDottyCompat( - scalaVersion() - ) - ) -} - -object tapir extends FullCrossSbtModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson, Deps.zioJson) - def jvmDeps = Agg(Deps.tapirZIO, Deps.tapirZIOHttp4sServer) - def jsDeps = Agg(Deps.tapirSttpClient) -} - -object akkaPersistence extends CommonModule { - def millSourcePath = build.millSourcePath / "akka-persistence" - def ivyDeps = (Agg( - Deps.akka.persistenceQuery, - Deps.akka.persistenceJdbc, - Deps.akka.projection.core, - Deps.akka.projection.eventsourced, - Deps.akka.projection.slick - ) ++ Deps.slick.default).map(_.withDottyCompat(scalaVersion())) ++ Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - Deps.akka.persistence.withDottyCompat(scalaVersion()), - ivy"com.typesafe.akka::akka-cluster-sharding-typed:${Deps.akka.V}" - .withDottyCompat(scalaVersion()) - ) -} - -object ui extends CommonJSModule { - def ivyDeps = Agg( - Deps.zio, - Deps.laminar, - Deps.zioJson, - Deps.waypoint, - Deps.urlDsl, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) -} diff --git a/iw/domain.sc b/iw/domain.sc deleted file mode 100644 index b09c56b..0000000 --- a/iw/domain.sc +++ /dev/null @@ -1,78 +0,0 @@ -import mill._, scalalib._ - -import $file.{build => ff}, ff.support._ - -trait DomainModule extends Module { - // General extra model deps - def modelModules: Seq[Module] = Seq.empty - // General extra codecs deps - def codecsModules: Seq[Module] = Seq.empty - // General extra endpoints deps - def endpointsModules: Seq[Module] = Seq.empty - - // Implementation deps for repo - def repoModules: Seq[JavaModule] = Seq.empty - - object shared extends Module { - object model extends PureCrossModule { - def moduleDeps = modelModules - def ivyDeps = Agg(Deps.zioPrelude) - } - object codecs extends PureCrossModule { - def moduleDeps = Seq(model) ++ codecsModules - def ivyDeps = Agg(Deps.zioJson) - } - } - - trait CommonProjects extends Module { - object model extends PureCrossModule { - def ivyDeps = Agg(Deps.zioPrelude) - def moduleDeps = Seq(shared.model) - } - object codecs extends PureCrossModule { - def moduleDeps = - Seq(model, shared.model, shared.codecs, ff.tapir) - } - object endpoints extends PureCrossModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson) - def moduleDeps = Seq(model, codecs) ++ endpointsModules - } - object client extends CommonJSModule { - def moduleDeps = Seq(endpoints.js) - } - object components extends CommonJSModule { - def ivyDeps = Agg( - Deps.laminar, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) - def moduleDeps = Seq(model.js, codecs.js, client, ff.ui) - } - } - - object query extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(repo, query.endpoints.jvm) - } - object repo extends CommonModule { - def ivyDeps = Agg(Deps.zio) - def moduleDeps = Seq(model.jvm, codecs.jvm) ++ repoModules - } - object projection extends CommonModule { - def moduleDeps = Seq(repo, ff.akkaPersistence) - } - } - - object command extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(entity, command.endpoints.jvm) - } - object entity extends CommonModule { - def moduleDeps = Seq(model.jvm, codecs.jvm, ff.akkaPersistence) - } - } -} diff --git a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala b/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala deleted file mode 100644 index 0fa3493..0000000 --- a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala +++ /dev/null @@ -1,53 +0,0 @@ -package works.iterative.mongo - -import zio.* -import zio.json.* -import zio.config.* -import org.mongodb.scala.* -import org.mongodb.scala.model.Filters.* -import org.bson.json.JsonObject -import org.mongodb.scala.model.ReplaceOptions -import org.mongodb.scala.bson.conversions.Bson - -case class MongoConfig(uri: String) - -object MongoConfig: - val configDesc = - import ConfigDescriptor.* - nested("MONGO")(string("URI").default("mongodb://localhost:27017")) - .to[MongoConfig] - val fromEnv = ZConfig.fromSystemEnv(configDesc) - -extension (m: MongoClient.type) - def layer: RLayer[MongoConfig, MongoClient] = - ZIO - .serviceWithZIO[MongoConfig](c => Task.attempt(MongoClient(c.uri))) - .toLayer - -class MongoJsonRepository[Elem, Key, Criteria]( - collection: MongoCollection[JsonObject], - toFilter: Criteria => Bson, - idFilter: Elem => (String, Key) -)(using JsonCodec[Elem]) { - def matching(criteria: Criteria): Task[Seq[Elem]] = - val filter = toFilter(criteria) - val query = collection.find(filter) - - for - result <- ZIO.fromFuture(_ => query.toFuture) - proof <- ZIO.collect(result)(j => - ZIO.fromOption(j.getJson.fromJson[Elem].toOption) - ) - yield proof - - def put(elem: Elem): Task[Unit] = - Task.async(cb => - collection - .replaceOne( - equal.tupled(idFilter(elem)), - JsonObject(elem.toJson), - ReplaceOptions().upsert(true) - ) - .subscribe(_ => cb(Task.unit), t => cb(Task.fail(t))) - ) -} diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala deleted file mode 100644 index 68d7196..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala +++ /dev/null @@ -1,12 +0,0 @@ -package works.iterative.tapir - -import sttp.model.Uri - -opaque type BaseUri = Option[Uri] - -object BaseUri: - - def apply(optU: Option[Uri]): BaseUri = optU - def apply(u: Uri): BaseUri = Some(u) - - extension (v: BaseUri) def toUri: Option[Uri] = v diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 653bc51..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,22 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.client.sttp.SttpClientInterpreter -import sttp.tapir.PublicEndpoint -import sttp.tapir.client.sttp.WebSocketToPipe -import scala.concurrent.Future -import sttp.client3.SttpBackend -import sttp.capabilities.WebSockets - -trait CustomTapirPlatformSpecific extends SttpClientInterpreter: - self: CustomTapir => - - type Backend = SttpBackend[Future, WebSockets] - - def makeClient[I, E, O]( - endpoint: PublicEndpoint[I, E, O, Any] - )(using - baseUri: BaseUri, - backend: Backend, - wsToPipe: WebSocketToPipe[Any] - ): I => Future[O] = - toClientThrowErrors(endpoint, baseUri.toUri, backend) diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 2545fbd..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,5 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.ztapir.ZTapir - -trait CustomTapirPlatformSpecific extends ZTapir diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala deleted file mode 100644 index 55e4ca1..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala +++ /dev/null @@ -1,7 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.server.http4s.ztapir.ZHttp4sServerInterpreter - -trait Http4sCustomTapir[Env] - extends CustomTapir - with ZHttp4sServerInterpreter[Env] diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala deleted file mode 100644 index dd52e9b..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala +++ /dev/null @@ -1,14 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.Tapir -import sttp.tapir.json.zio.TapirJsonZio -import sttp.tapir.TapirAliases - -trait CustomTapir - extends Tapir - with TapirJsonZio - with TapirAliases - with CustomTapirPlatformSpecific: - given Schema[ServerError] = Schema.derived - -object CustomTapir extends CustomTapir diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala deleted file mode 100644 index 715591d..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.tapir - -import zio.json.* - -sealed trait ServerError -case class InternalServerError(msg: String) extends ServerError -object InternalServerError: - def fromThrowable(t: Throwable): ServerError = InternalServerError( - t.getMessage - ) - -object ServerError: - given JsonCodec[ServerError] = DeriveJsonCodec.gen diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/File.scala deleted file mode 100644 index 09f6c07..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala +++ /dev/null @@ -1,3 +0,0 @@ -package works.iterative.services.files - -case class File(url: String, name: String) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala deleted file mode 100644 index 46633a5..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala +++ /dev/null @@ -1,25 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.Renderable - -given Renderable[File] with - extension (m: File) - def toHtml: HtmlElement = - li( - cls("pl-3 pr-4 py-3 flex items-center justify-between text-sm"), - div( - cls("w-0 flex-1 flex items-center"), - Icons.solid - .paperclip() - .amend(svg.cls := "flex-shrink-0 text-gray-400"), - span(cls("ml-2 flex-1 w-0 truncate"), m.name) - ), - a( - href(m.url), - cls("font-medium text-indigo-600 hover:text-indigo-500"), - "Otevřít" - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala deleted file mode 100644 index ac59db8..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import io.laminext.syntax.core.{*, given} - -def FileList(files: List[File]): HtmlElement = - ul( - role("list"), - cls("border border-gray-200 rounded-md divide-y divide-gray-200"), - files.map(_.toHtml) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala deleted file mode 100644 index 2e8f690..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala +++ /dev/null @@ -1,81 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import io.laminext.syntax.core.{*, given} - -object FilePicker: - sealed trait Event - sealed trait DoneEvent extends Event - case class SelectionUpdated(files: Set[File]) extends DoneEvent - case object SelectionCancelled extends DoneEvent - case object AvailableFilesRequested extends Event - - def apply( - currentFiles: Signal[List[File]], - availableFilesStream: EventStream[List[File]] - )(selectionUpdates: Observer[Event]): HtmlElement = - val (updatesStream, updatesObserver) = EventStream.withObserver[Event] - val selectorOpen = Var[Boolean](false) - - // This sequence tricks browser into displaying modal content centered - // Inspired by modal in headless ui playground - // https://github.com/tailwindlabs/headlessui/blob/fdd26297953080d5ec905dda0bf5ec9607897d86/packages/playground-react/pages/transitions/component-examples/modal.tsx#L78-L79 - inline def browserCenteringModalTrick: Modifier[HtmlElement] = - Seq[Modifier[HtmlElement]]( - span(cls("hidden sm:inline-block sm:h-screen sm:align-middle")), - "​" // Zero width space - ) - - inline def overlay: Modifier[HtmlElement] = - // Page overlay - /* TODO: transition - enter="ease-out duration-300" - enterFrom="opacity-0" - enterTo="opacity-100" - leave="ease-in duration-200" - leaveFrom="opacity-100" - leaveTo="opacity-0" - */ - div( - div( - onClick.mapTo(false) --> selectorOpen.writer, - cls("fixed inset-0 transition-opacity"), - div(cls("absolute inset-0 bg-gray-500 opacity-75")) - ) - ) - - inline def modalSelector: HtmlElement = - div( - cls("fixed inset-0 z-10 overflow-y-auto"), - div( - cls("text-center sm:block sm:p-0"), - overlay, - browserCenteringModalTrick, - child <-- currentFiles.map( - FileSelector(_, availableFilesStream)(updatesObserver) - ) - ) - ) - - div( - updatesStream --> selectionUpdates, - updatesStream.collect { case _: DoneEvent => - false - } --> selectorOpen.writer, - child.maybe <-- selectorOpen.signal.map(isOpen => - if isOpen then Some(modalSelector) else None - ), - div( - cls("flex flex-col space-y-5"), - child.maybe <-- currentFiles.map(files => - if files.isEmpty then None else Some(FileList(files)) - ), - button( - tpe := "button", - cls := "bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500", - "Zvolit soubory", - onClick.mapTo(true) --> selectorOpen.writer - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala deleted file mode 100644 index b43e07a..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala +++ /dev/null @@ -1,74 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.Loading -import io.laminext.syntax.core.{*, given} -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -object FileSelector: - import FilePicker._ - - def apply( - initialFiles: List[File], - availableFilesStream: EventStream[List[File]] - )(selectionUpdates: Observer[Event]): HtmlElement = - val selectedFiles = Var[Set[File]](initialFiles.to(Set)) - val availableFiles = availableFilesStream.startWithNone - // Request the files to display - selectionUpdates.onNext(AvailableFilesRequested) - div( - cls( - "inline-block transform overflow-hidden rounded-lg bg-white text-left align-bottom shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:align-middle" - ), - role("dialog"), - customHtmlAttr("aria.modal", BooleanAsTrueFalseStringCodec)(true), - aria.labelledBy("modal-headline"), - div( - cls("bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"), - div( - cls("sm:flex sm:items-start"), - div( - cls("mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"), - h3( - cls("text-lg font-medium leading-6 text-gray-900"), - idAttr("modal-headline"), - "Výběr souborů" - ) - ) - ), - child <-- availableFiles - .split(_ => ())((_, _, af) => FileTable(af, selectedFiles)) - .map(_.getOrElse(Loading)) - ), - div( - cls("bg-gray-50 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6"), - span( - cls("flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto"), - button( - typ("button"), - cls( - "focus:shadow-outline-green inline-flex w-full justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-base font-medium leading-6 text-white shadow-sm transition duration-150 ease-in-out hover:bg-indigo-500 focus:border-indigo-700 focus:outline-none sm:text-sm sm:leading-5" - ), - "Potvrdit", - composeEvents(onClick)( - _.sample(selectedFiles) - .map(SelectionUpdated(_)) - ) --> selectionUpdates - ) - ), - span( - cls("mt-3 flex w-full rounded-md shadow-sm sm:mt-0 sm:w-auto"), - button( - typ("button"), - cls( - "focus:shadow-outline-blue inline-flex w-full justify-center rounded-md border border-gray-300 bg-white px-4 py-2 text-base font-medium leading-6 text-gray-700 shadow-sm transition duration-150 ease-in-out hover:text-gray-500 focus:border-blue-300 focus:outline-none sm:text-sm sm:leading-5" - ), - "Zrušit", - onClick.mapTo(SelectionCancelled) --> selectionUpdates - ) - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala deleted file mode 100644 index aa11b2b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala +++ /dev/null @@ -1,91 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import works.iterative.ui.components.tailwind.Icons - -def FileTable( - files: Signal[List[File]], - selectedFiles: Var[Set[File]] -): HtmlElement = - val scope = customHtmlAttr("scope", StringAsIsCodec) - - def headerRow: HtmlElement = - val col = scope("col") - val baseM: Modifier[HtmlElement] = Seq(cls("px-6 py-3"), col) - val textH = cls( - "text-left text-xs font-medium text-gray-500 uppercase tracking-wider" - ) - tr( - th(baseM, span(cls("sr-only"), "Vybrat")), - th(baseM, textH, "Název"), - th(baseM, cls("relative"), span(cls("sr-only"), "Otevřít")) - ) - - def tableRow( - f: File, - idx: Int, - selected: Boolean - )(toggleSelection: Observer[Unit]): HtmlElement = - val baseC = cls("px-6 py-4 whitespace-nowrap text-sm") - tr( - cls(if idx % 2 == 0 then "bg-gray-50" else "bg-white"), - td( - cls("font-medium cursor-pointer"), - onClick.mapTo(()) --> toggleSelection, - cls(if selected then "text-green-900" else "text-gray-200"), - Icons.outline.`check-circle`().amend(svg.cls := "mx-auto"), - span(cls("sr-only"), if selected then "Vybráno" else "Nevybráno") - ), - td( - baseC, - cls("font-medium text-gray-900"), - f.name, - onClick.mapTo(()) --> toggleSelection - ), - td( - baseC, - cls("text-right font-medium"), - a( - href(f.url), - target("_blank"), - cls( - "flex items-center space-x-4 text-indigo-600 hover:text-indigo-900" - ), - Icons.outline.`external-link`(), - "Otevřít" - ) - ) - ) - - div( - cls("flex flex-col"), - div(cls("overflow-x-auto sm:-mx-6 lg:-mx-8")), - div( - cls("py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8"), - div( - cls("shadow overflow-hidden border-b border-gray-200 sm:rounded-lg"), - table( - cls("min-w-full divide-y divide-gray-200"), - thead( - cls("bg-gray-50"), - headerRow - ), - tbody( - children <-- files - .map(_.zipWithIndex) - .combineWithFn(selectedFiles)((f, sel) => - f.map((file, idx) => - val active = sel.contains(file) - tableRow(file, idx, active)( - selectedFiles.writer - .contramap(_ => if active then sel - file else sel + file) - ) - ) - ) - ) - ) - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala deleted file mode 100644 index 0fc8796..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala +++ /dev/null @@ -1,32 +0,0 @@ -package works.iterative.ui.components.tailwind - -import CustomAttrs.ariaHidden -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -// TODO: render icon or picture based on img signal -class Avatar($avatarImg: Signal[Option[String]]): - inline def avatarPlaceholder(size: Int): HtmlElement = - div( - cls := s"rounded-full text-indigo-200 bg-indigo-500 h-${size} w-${size} flex items-center justify-center", - Icons.outline.user(size - 2) - ) - - inline def avatarImage(size: Int): Signal[HtmlElement] = - $avatarImg.split(_ => ())((_, _, $url) => - img( - cls := s"w-$size h-$size rounded-full", - src <-- $url, - alt := "" - ) - ).map(_.getOrElse(avatarPlaceholder(size))) - - inline def avatar(size: Int): HtmlElement = - div( - cls := "relative", - child <-- avatarImage(size), - span( - cls := "absolute inset-0 shadow-inner rounded-full", - ariaHidden := true - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala deleted file mode 100644 index bcbed88..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala +++ /dev/null @@ -1,77 +0,0 @@ -package works.iterative.ui.components.tailwind - -enum ColorWeight(value: String): - inline def toCSS: String = value - // To be used for black and white until - // we support better mechanism via extension methods - case w__ extends ColorWeight("") - case w50 extends ColorWeight("50") - case w100 extends ColorWeight("100") - case w200 extends ColorWeight("200") - case w300 extends ColorWeight("300") - case w400 extends ColorWeight("400") - case w500 extends ColorWeight("500") - case w600 extends ColorWeight("600") - case w700 extends ColorWeight("700") - case w800 extends ColorWeight("800") - case w900 extends ColorWeight("900") - -enum Color(name: String): - import ColorWeight._ - - inline def toCSSNoColorWeight(prefix: String): String = - s"${prefix}-${name}" - inline def toCSSWithColorWeight( - prefix: String, - weight: ColorWeight - ): String = - s"${prefix}-${name}-${weight.toCSS}" - inline def toCSS(prefix: String)(weight: ColorWeight): String = - weight match { - case `w__` => toCSSNoColorWeight(prefix) - case _ => toCSSWithColorWeight(prefix, weight) - } - inline def bg: ColorWeight => String = toCSS("bg")(_) - inline def text: ColorWeight => String = toCSS("text")(_) - inline def decoration: ColorWeight => String = toCSS("decoration")(_) - inline def border: ColorWeight => String = toCSS("border")(_) - inline def outline: ColorWeight => String = toCSS("outline")(_) - inline def divide: ColorWeight => String = toCSS("divide")(_) - inline def ring: ColorWeight => String = toCSS("ring")(_) - inline def ringOffset: ColorWeight => String = toCSS("ring-offset")(_) - inline def shadow: ColorWeight => String = toCSS("shadow")(_) - inline def accent: ColorWeight => String = toCSS("accent")(_) - - // TODO: change the "stupid" methods to extension methods - // that will keep the invariants in comments lower - case current extends Color("current") - case inherit extends Color("inherit") - // Not present in for all methods - case transp extends Color("transparent") - // Seen in accent, not preset otherwise - case auto extends Color("auto") - // Black and white do not have weight - case black extends Color("black") - case white extends Color("white") - case slate extends Color("slate") - case gray extends Color("gray") - case zinc extends Color("zinc") - case neutral extends Color("neutral") - case stone extends Color("stone") - case red extends Color("red") - case orange extends Color("orange") - case amber extends Color("amber") - case yellow extends Color("yellow") - case lime extends Color("lime") - case green extends Color("green") - case emerald extends Color("emerald") - case teal extends Color("teal") - case cyan extends Color("cyan") - case sky extends Color("sky") - case blue extends Color("blue") - case indigo extends Color("indigo") - case violet extends Color("violet") - case purple extends Color("purple") - case fuchsia extends Color("fuchsia") - case pink extends Color("pink") - case rose extends Color("rose") diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala deleted file mode 100644 index 6fb453c..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -object CustomAttrs { - // Made a pull request to add aria-current to scala-dom-types, remove after - val ariaCurrent = customHtmlAttr("aria-current", StringAsIsCodec) - val ariaHidden = customHtmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - - val datetime = customHtmlAttr("datetime", StringAsIsCodec) - - object svg { - import com.raquo.laminar.api.L.svg.{*, given} - val ariaHidden = - customSvgAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - } -} diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala deleted file mode 100644 index 6cda48b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala +++ /dev/null @@ -1,28 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -object Display: - - enum Breakpoint: - case sm, md, lg, xl, `2xl` - - enum DisplayClass: - case block, `inline-block`, `inline`, flex, `inline-flex`, table, - `inline-table`, `table-caption` - - object ShowUpFrom: - inline def apply( - br: Breakpoint, - dc: DisplayClass = DisplayClass.block - ): HtmlElement = - div( - cls := "hidden", - cls := s"${br}:${dc}" - ) - - object HideUpTo: - inline def apply(br: Breakpoint): HtmlElement = - div( - cls := s"${br}:hidden" - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala deleted file mode 100644 index 4a8b065..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala +++ /dev/null @@ -1,270 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec -import com.raquo.domtypes.generic.defs.attrs.AriaAttrs -import com.raquo.laminar.api.L.svg.{*, given} -import com.raquo.laminar.builders.SvgBuilders -import com.raquo.laminar.keys.ReactiveSvgAttr - -// TODO: fix sizes, colors, hover and stuff, normalize and amend on call site -object Icons: - object aria: - inline def hidden = CustomAttrs.svg.ariaHidden - - object outline: - val defaultSize: Int = 6 - - inline def bell(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" - ) - ) - - inline def `check-circle`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" - ) - ) - - inline def `document-add`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M9 13h6m-3-3v6m5 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" - ) - ) - - inline def `external-link`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" - ) - ) - - inline def menu(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M4 6h16M4 12h16M4 18h16" - ) - ) - - inline def `status-offline`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M18.364 5.636a9 9 0 010 12.728m0 0l-2.829-2.829m2.829 2.829L21 21M15.536 8.464a5 5 0 010 7.072m0 0l-2.829-2.829m-4.243 2.829a4.978 4.978 0 01-1.414-2.83m-1.414 5.658a9 9 0 01-2.167-9.238m7.824 2.167a1 1 0 111.414 1.414m-1.414-1.414L3 3m8.293 8.293l1.414 1.414" - ) - ) - - inline def user(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" - ) - ) - - inline def x(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M6 18L18 6M6 6l12 12" - ) - ) - - end outline - - object solid: - val defaultSize: Int = 5 - - inline def users(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - d := "M9 6a3 3 0 11-6 0 3 3 0 016 0zM17 6a3 3 0 11-6 0 3 3 0 016 0zM12.93 17c.046-.327.07-.66.07-1a6.97 6.97 0 00-1.5-4.33A5 5 0 0119 16v1h-6.07zM6 11a5 5 0 015 5v1H1v-1a5 5 0 015-5z" - ) - ) - - inline def `location-marker`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M5.05 4.05a7 7 0 119.9 9.9L10 18.9l-4.95-4.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z", - clipRule := "evenodd" - ) - ) - - inline def calendar(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z", - clipRule := "evenodd" - ) - ) - - inline def `chevron-right`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z", - clipRule := "evenodd" - ) - ) - - inline def search(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z", - clipRule := "evenodd" - ) - ) - - inline def filter(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M3 3a1 1 0 011-1h12a1 1 0 011 1v3a1 1 0 01-.293.707L12 11.414V15a1 1 0 01-.293.707l-2 2A1 1 0 018 17v-5.586L3.293 6.707A1 1 0 013 6V3z", - clipRule := "evenodd" - ) - ) - - inline def `arrow-narrow-left`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M7.707 14.707a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 1.414L5.414 9H17a1 1 0 110 2H5.414l2.293 2.293a1 1 0 010 1.414z", - clipRule := "evenodd" - ) - ) - - inline def home(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - d := "M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z" - ) - ) - - inline def paperclip(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size} text-gray-400", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M8 4a3 3 0 00-3 3v4a5 5 0 0010 0V7a1 1 0 112 0v4a7 7 0 11-14 0V7a5 5 0 0110 0v4a3 3 0 11-6 0V7a1 1 0 012 0v4a1 1 0 102 0V7a3 3 0 00-3-3z", - clipRule := "evenodd" - ) - ) - - end solid -end Icons diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/LinkSupport.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/LinkSupport.scala deleted file mode 100644 index 88f0b9a..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/LinkSupport.scala +++ /dev/null @@ -1,12 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.jsdom.defs.events.TypedTargetMouseEvent - -object LinkSupport: - - extension [El <: org.scalajs.dom.EventTarget]( - ep: EventProcessor[TypedTargetMouseEvent[El], TypedTargetMouseEvent[El]] - ) - def noKeyMod = - ep.filter(ev => !(ev.ctrlKey || ev.metaKey || ev.shiftKey || ev.altKey)) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Loader.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Loader.scala deleted file mode 100644 index 5399933..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Loader.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -// TODO: proper loader -def Loading = - div( - cls := "bg-gray-50 overflow-hidden rounded-lg", - div( - cls := "px-4 py-5 sm:p-6", - "Loading..." - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Renderable.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Renderable.scala deleted file mode 100644 index a0fcc5b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Renderable.scala +++ /dev/null @@ -1,6 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -trait Renderable[A]: - extension (a: A) def toHtml: HtmlElement diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/ComboBox.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/ComboBox.scala deleted file mode 100644 index 8c5b8a1..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/ComboBox.scala +++ /dev/null @@ -1,92 +0,0 @@ -package works.iterative.ui.components.tailwind -package form - -import com.raquo.laminar.api.L.{*, given} -import io.laminext.syntax.core.* - -case class ComboBox( - id: String, - options: Signal[List[ComboBox.Option]], - valueUpdates: Observer[List[String]] -) - -object ComboBox: - - extension (m: ComboBox) - def toHtml: HtmlElement = - val isOpen = Var(false) - div( - cls := "relative mt-1", - input( - idAttr := m.id, - tpe := "text", - cls := "w-full rounded-md border border-gray-300 bg-white py-2 pl-3 pr-12 shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 sm:text-sm", - role := "combobox", - aria.controls := "options", - aria.expanded := false, - onClick.mapTo(true) --> isOpen.writer - ), - button( - tpe := "button", - cls := "absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-none", { - import svg.* - svg( - cls := "h-5 w-5 text-gray-400", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - CustomAttrs.svg.ariaHidden := true, - path( - fillRule := "evenodd", - d := "M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z", - clipRule := "evenodd" - ) - ) - } - ), - ul( - cls <-- isOpen.signal.switch("", "hidden"), - cls := "absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm", - idAttr := "options", - role := "listbox", - children <-- m.options.map(_.map(_.toHtml)) - ) - ) - - case class Option(value: String, active: Boolean) - - object Option: - extension (m: Option) - def toHtml: HtmlElement = - li( - cls := "relative cursor-default select-none py-2 pl-8 pr-4", - cls := (if m.active then "text-white bg-indigo-600" - else "text-gray-900"), - idAttr := "option-0", - role := "option", - tabIndex := -1, - span( - cls := "block truncate", - m.value - ), - if m.active then - span( - cls := "absolute inset-y-0 left-0 flex items-center pl-1.5", - cls := (if m.active then "text-white" else "text-indigo-600"), { - import svg.* - svg( - cls := "h-5 w-5", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - CustomAttrs.svg.ariaHidden := true, - path( - fillRule := "evenodd", - d := "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", - clipRule := "evenodd" - ) - ) - } - ) - else emptyNode - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/Form.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/Form.scala deleted file mode 100644 index 1b41dee..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/Form.scala +++ /dev/null @@ -1,17 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} -import org.scalajs.dom -import com.raquo.laminar.nodes.ReactiveHtmlElement - -object Form: - val Body = FormBody - val Section = FormSection - val Row = FormRow - - def apply(body: HtmlElement, buttons: HtmlElement): HtmlElement = - form( - cls := "space-y-8 divide-y divide-gray-200", - body, - buttons - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormBody.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormBody.scala deleted file mode 100644 index 89abc9c..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormBody.scala +++ /dev/null @@ -1,10 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} - -object FormBody: - def apply(sections: HtmlElement*): HtmlElement = - div( - cls := "space-y-8 divide-y divide-gray-200 sm:space-y-5", - sections - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormFields.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormFields.scala deleted file mode 100644 index a2e4cc6..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormFields.scala +++ /dev/null @@ -1,14 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.laminar.nodes.ReactiveHtmlElement -import org.scalajs.dom - -object FormFields: - def apply( - mods: Modifier[ReactiveHtmlElement[dom.HTMLElement]]* - ): HtmlElement = - div( - cls := "mt-6 sm:mt-5 space-y-6 sm:space-y-5", - mods - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormHeader.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormHeader.scala deleted file mode 100644 index 4083841..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormHeader.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} - -object FormHeader: - case class ViewModel(header: String, description: String) - def apply(m: ViewModel): HtmlElement = - div( - h3(cls := "text-lg leading-6 font-medium text-gray-900", m.header), - p(cls := "mt-1 max-w-2xl text-sm text-gray-500", m.description) - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala deleted file mode 100644 index e7e6627..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.akka - -// Base class for all command-processing related exceptions from handlers -sealed abstract class CommandHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandNotAvailable[C, S](cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd není dostupný ve stavu $state" - ) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandRejected[C, S](reason: String, cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd byl ve stavu $state odmítnut s odůvodněním $reason" - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala deleted file mode 100644 index 72a5bf9..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.akka - -sealed abstract class EventHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -case class UnhandledEvent[Event, State](event: Event, state: State) - extends EventHandlerException( - s"Událost $event nastala ve stavu $state bez možnosti zpracování" - ) diff --git a/iw/bom.sc b/iw/bom.sc deleted file mode 100644 index e9266b8..0000000 --- a/iw/bom.sc +++ /dev/null @@ -1,227 +0,0 @@ -import mill._, scalalib._ - -object IWMaterials { - - object Versions { - val akka = "2.6.16" - val akkaHttp = "10.2.4" - val cats = "2.7.0" - val elastic4s = "7.12.2" - val http4s = "0.23.10" - val http4sPac4J = "4.0.0" - val laminar = "0.14.2" - val laminext = laminar - val logbackClassic = "1.2.10" - val pac4j = "5.2.0" - val play = "2.8.8" - val playJson = "2.9.2" - val scalaTest = "3.2.9" - val slick = "3.3.3" - val sttpClient = "3.5.0" - val tapir = "0.20.1" - val urlDsl = "0.4.0" - val waypoint = "0.5.0" - val zio = "2.0.0-RC2" - val zioConfig = "3.0.0-RC2" - val zioInteropCats = "3.3.0-RC2" - val zioJson = "0.3.0-RC3" - val zioLogging = "2.0.0-RC5" - val zioPrelude = "1.0.0-RC10" - val zioZMX = "0.0.11" - } - - object Deps extends AkkaLibs with SlickLibs { - import IWMaterials.{Versions => V} - - val zioOrg = "dev.zio" - - def zioLib(name: String, version: String): Dep = - ivy"$zioOrg::zio-$name::$version" - - lazy val zio: Dep = ivy"$zioOrg::zio:${V.zio}" - - lazy val zioTest: Dep = zioLib("test", V.zio) - lazy val zioTestSbt: Dep = zioLib("test", V.zio) - - lazy val zioConfig: Dep = zioLib("config", V.zioConfig) - lazy val zioConfigTypesafe: Dep = - zioLib("config-typesafe", V.zioConfig) - lazy val zioConfigMagnolia: Dep = - zioLib("config-magnolia", V.zioConfig) - - lazy val zioJson: Dep = zioLib("json", V.zioJson) - lazy val zioLogging: Dep = zioLib("logging", V.zioLogging) - lazy val zioLoggingSlf4j: Dep = - zioLib("logging-slf4j", V.zioLogging) - lazy val zioPrelude: Dep = zioLib("prelude", V.zioPrelude) - lazy val zioStreams: Dep = zioLib("streams", V.zio) - lazy val zioZMX: Dep = zioLib("zmx", V.zioZMX) - lazy val zioInteropCats: Dep = - zioLib("interop-cats", V.zioInteropCats) - - /* What is the equivalent? ZIOModule with prepared test config? - def useZIO(testConf: Configuration*): Agg[Dep] = Agg( - zio, - zioTest, - zioTestSbt, - testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework") - ) - */ - - /* - def useZIOAll(testConf: Configuration*): Seq[Def.Setting[_]] = - useZIO(testConf: _*) ++ Seq( - zioStreams, - zioConfig, - zioConfigTypesafe, - zioConfigMagnolia, - zioJson, - zioZMX, - zioLogging, - zioPrelude - ) - */ - - private val tapirOrg = "com.softwaremill.sttp.tapir" - def tapirLib(name: String): Dep = - ivy"${tapirOrg}::tapir-$name::${V.tapir}" - - lazy val tapirCore: Dep = tapirLib("core") - lazy val tapirZIO: Dep = tapirLib("zio") - lazy val tapirZIOJson: Dep = tapirLib("json-zio") - lazy val tapirSttpClient: Dep = tapirLib("sttp-client") - lazy val tapirCats: Dep = tapirLib("cats") - lazy val tapirZIOHttp4sServer: Dep = tapirLib("zio-http4s-server") - - private val sttpClientOrg = "com.softwaremill.sttp.client3" - def sttpClientLib(name: String): Dep = - ivy"${sttpClientOrg}::${name}:${V.sttpClient}" - - lazy val sttpClientCore: Dep = sttpClientLib("core") - - lazy val http4sBlazeServer: Dep = - ivy"org.http4s::http4s-blaze-server:${V.http4s}" - - lazy val http4sPac4J: Dep = - ivy"org.pac4j::http4s-pac4j:${V.http4sPac4J}" - lazy val pac4jOIDC: Dep = - ivy"org.pac4j:pac4j-oidc:${V.pac4j}" - - lazy val scalaTest: Dep = - ivy"org.scalatest::scalatest:${V.scalaTest}" - lazy val scalaTestPlusScalacheck: Dep = - ivy"org.scalatestplus::scalacheck-1-15:3.2.9.0" - lazy val playScalaTest: Dep = - ivy"org.scalatestplus.play::scalatestplus-play:5.1.0" - - private val playOrg = "com.typesafe.play" - lazy val playMailer: Dep = ivy"${playOrg}::play-mailer:8.0.1" - lazy val playServer: Dep = ivy"${playOrg}::play-server:${V.play}" - lazy val playAkkaServer: Dep = - ivy"${playOrg}::play-akka-http-server:${V.play}" - lazy val play: Dep = ivy"${playOrg}::play:${V.play}" - lazy val playAhcWs: Dep = ivy"${playOrg}::play-ahc-ws:${V.play}" - lazy val playJson: Dep = ivy"${playOrg}::play-json:${V.playJson}" - - private val elastic4sOrg = "com.sksamuel.elastic4s" - lazy val useElastic4S: Agg[Dep] = Agg( - ivy"${elastic4sOrg}::elastic4s-core:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-client-akka:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-http-streams:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-json-play:${V.elastic4s}" - ) - - lazy val laminar: Dep = ivy"com.raquo::laminar::${V.laminar}" - - private def laminext(name: String): Dep = - ivy"io.laminext::$name::${V.laminar}" - - lazy val laminextCore: Dep = laminext("core") - lazy val laminextTailwind: Dep = laminext("tailwind") - lazy val laminextFetch: Dep = laminext("fetch") - lazy val laminextValidationCore: Dep = laminext("validation-core") - lazy val laminextUI: Dep = laminext("ui") - - lazy val waypoint: Dep = - ivy"com.raquo::waypoint::${V.waypoint}" - - lazy val urlDsl: Dep = - ivy"be.doeraene::url-dsl::${V.urlDsl}" - - lazy val scalaJavaTime: Dep = - ivy"io.github.cquiroz::scala-java-time::2.3.0" - - lazy val scalaJavaLocales: Dep = - ivy"io.github.cquiroz::scala-java-locales::1.2.1" - - lazy val logbackClassic: Dep = - ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" - } - - trait AkkaLibs { - - self: SlickLibs => - - object akka { - val V = Versions.akka - val tOrg = "com.typesafe.akka" - val lOrg = "com.lightbend.akka" - - def akkaMod(name: String): Dep = - ivy"$tOrg::akka-$name:$V" - - lazy val actor: Dep = akkaMod("actor") - lazy val actorTyped: Dep = akkaMod("actor-typed") - lazy val stream: Dep = akkaMod("stream") - lazy val persistence: Dep = akkaMod("persistence-typed") - lazy val persistenceQuery: Dep = akkaMod("persistence-query") - lazy val persistenceJdbc: Dep = - ivy"$lOrg::akka-persistence-jdbc:5.0.4" - val persistenceTestKit: Dep = ivy"$tOrg::akka-persistence-testkit:$V" - - object http { - val V = Versions.akkaHttp - - lazy val http: Dep = ivy"$tOrg::akka-http:$V" - lazy val sprayJson: Dep = ivy"$tOrg::akka-http-spray-json:$V" - - } - - object projection { - val V = "1.2.2" - - lazy val core: Dep = - ivy"$lOrg::akka-projection-core:$V" - lazy val eventsourced: Dep = - ivy"$lOrg::akka-projection-eventsourced:$V" - lazy val slick: Dep = - ivy"$lOrg::akka-projection-slick:$V" - lazy val jdbc: Dep = - ivy"$lOrg::akka-projection-jdbc:$V" - } - - object profiles { - // TODO: deal with cross-version for Scala3 (for3Use2_13) - lazy val eventsourcedJdbcProjection: Agg[Dep] = Agg( - persistenceQuery, - projection.core, - projection.eventsourced, - projection.slick, - persistenceJdbc - ) ++ slick.default - } - } - } - - trait SlickLibs { - object slick { - val V = IWMaterials.Versions.slick - val org = "com.typesafe.slick" - - lazy val slick: Dep = ivy"$org::slick:$V" - lazy val hikaricp: Dep = ivy"$org::slick-hikaricp:$V" - lazy val default: Agg[Dep] = Agg(slick, hikaricp) - } - } - -} diff --git a/iw/build.sc b/iw/build.sc deleted file mode 100644 index fbbc3b8..0000000 --- a/iw/build.sc +++ /dev/null @@ -1,144 +0,0 @@ -import mill._, scalalib._, scalajslib._ - -import $file.bom - -object support { - val Deps = bom.IWMaterials.Deps - val Versions = bom.IWMaterials.Versions - - trait CommonModule extends ScalaModule { - def scalaVersion = "3.1.1" - def scalacOptions = Seq( - "-encoding", - "utf8", - "-deprecation", - "-explain-types", - "-explain", - "-feature", - "-language:experimental.macros", - "-language:higherKinds", - "-language:implicitConversions", - "-unchecked", - "-Xfatal-warnings", - "-Ykind-projector" - ) - } - - trait CommonJSModule extends CommonModule with ScalaJSModule { - def scalaJSVersion = "1.8.0" - } - - trait CrossPlatformModule extends Module { outer => - def ivyDeps: T[Agg[Dep]] = Agg[Dep]() - def jsDeps: T[Agg[Dep]] = Agg[Dep]() - def jvmDeps: T[Agg[Dep]] = Agg[Dep]() - def moduleDeps: Seq[Module] = Seq[Module]() - - trait PlatformModule extends CommonModule { - def platform: String - override def millSourcePath = outer.millSourcePath - override def ivyDeps = outer.ivyDeps() ++ (platform match { - case "js" => jsDeps() - case _ => jvmDeps() - }) - override def moduleDeps = outer.moduleDeps.collect { - case m: CrossPlatformModule => - platform match { - case "js" => m.js - case _ => m.jvm - } - case m: JavaModule => m - } - } - - trait JsModule extends PlatformModule with CommonJSModule { - def platform = "js" - } - - trait JvmModule extends PlatformModule { - def platform = "jvm" - } - - val js: JsModule - val jvm: JvmModule - } - - trait PureCrossModule extends CrossPlatformModule { - override object js extends JsModule - override object jvm extends JvmModule - } - - trait PureCrossSbtModule extends CrossPlatformModule { - override object js extends JsModule with SbtModule - override object jvm extends JvmModule with SbtModule - } - - trait FullCrossSbtModule extends CrossPlatformModule { - trait FullSources extends JavaModule { self: PlatformModule => - override def sources = T.sources( - millSourcePath / platform / "src" / "main" / "scala", - millSourcePath / "shared" / "src" / "main" / "scala" - ) - - override def resources = T.sources( - millSourcePath / platform / "src" / "main" / "resources", - millSourcePath / "shared" / "src" / "main" / "resources" - ) - } - - override object js extends JsModule with FullSources - override object jvm extends JvmModule with FullSources - } - -} - -import support._ - -object mongo extends CommonModule { - def ivyDeps = Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - ivy"org.mongodb.scala::mongo-scala-driver:4.2.3".withDottyCompat( - scalaVersion() - ) - ) -} - -object tapir extends FullCrossSbtModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson, Deps.zioJson) - def jvmDeps = Agg(Deps.tapirZIO, Deps.tapirZIOHttp4sServer) - def jsDeps = Agg(Deps.tapirSttpClient) -} - -object akkaPersistence extends CommonModule { - def millSourcePath = build.millSourcePath / "akka-persistence" - def ivyDeps = (Agg( - Deps.akka.persistenceQuery, - Deps.akka.persistenceJdbc, - Deps.akka.projection.core, - Deps.akka.projection.eventsourced, - Deps.akka.projection.slick - ) ++ Deps.slick.default).map(_.withDottyCompat(scalaVersion())) ++ Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - Deps.akka.persistence.withDottyCompat(scalaVersion()), - ivy"com.typesafe.akka::akka-cluster-sharding-typed:${Deps.akka.V}" - .withDottyCompat(scalaVersion()) - ) -} - -object ui extends CommonJSModule { - def ivyDeps = Agg( - Deps.zio, - Deps.laminar, - Deps.zioJson, - Deps.waypoint, - Deps.urlDsl, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) -} diff --git a/iw/domain.sc b/iw/domain.sc deleted file mode 100644 index b09c56b..0000000 --- a/iw/domain.sc +++ /dev/null @@ -1,78 +0,0 @@ -import mill._, scalalib._ - -import $file.{build => ff}, ff.support._ - -trait DomainModule extends Module { - // General extra model deps - def modelModules: Seq[Module] = Seq.empty - // General extra codecs deps - def codecsModules: Seq[Module] = Seq.empty - // General extra endpoints deps - def endpointsModules: Seq[Module] = Seq.empty - - // Implementation deps for repo - def repoModules: Seq[JavaModule] = Seq.empty - - object shared extends Module { - object model extends PureCrossModule { - def moduleDeps = modelModules - def ivyDeps = Agg(Deps.zioPrelude) - } - object codecs extends PureCrossModule { - def moduleDeps = Seq(model) ++ codecsModules - def ivyDeps = Agg(Deps.zioJson) - } - } - - trait CommonProjects extends Module { - object model extends PureCrossModule { - def ivyDeps = Agg(Deps.zioPrelude) - def moduleDeps = Seq(shared.model) - } - object codecs extends PureCrossModule { - def moduleDeps = - Seq(model, shared.model, shared.codecs, ff.tapir) - } - object endpoints extends PureCrossModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson) - def moduleDeps = Seq(model, codecs) ++ endpointsModules - } - object client extends CommonJSModule { - def moduleDeps = Seq(endpoints.js) - } - object components extends CommonJSModule { - def ivyDeps = Agg( - Deps.laminar, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) - def moduleDeps = Seq(model.js, codecs.js, client, ff.ui) - } - } - - object query extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(repo, query.endpoints.jvm) - } - object repo extends CommonModule { - def ivyDeps = Agg(Deps.zio) - def moduleDeps = Seq(model.jvm, codecs.jvm) ++ repoModules - } - object projection extends CommonModule { - def moduleDeps = Seq(repo, ff.akkaPersistence) - } - } - - object command extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(entity, command.endpoints.jvm) - } - object entity extends CommonModule { - def moduleDeps = Seq(model.jvm, codecs.jvm, ff.akkaPersistence) - } - } -} diff --git a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala b/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala deleted file mode 100644 index 0fa3493..0000000 --- a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala +++ /dev/null @@ -1,53 +0,0 @@ -package works.iterative.mongo - -import zio.* -import zio.json.* -import zio.config.* -import org.mongodb.scala.* -import org.mongodb.scala.model.Filters.* -import org.bson.json.JsonObject -import org.mongodb.scala.model.ReplaceOptions -import org.mongodb.scala.bson.conversions.Bson - -case class MongoConfig(uri: String) - -object MongoConfig: - val configDesc = - import ConfigDescriptor.* - nested("MONGO")(string("URI").default("mongodb://localhost:27017")) - .to[MongoConfig] - val fromEnv = ZConfig.fromSystemEnv(configDesc) - -extension (m: MongoClient.type) - def layer: RLayer[MongoConfig, MongoClient] = - ZIO - .serviceWithZIO[MongoConfig](c => Task.attempt(MongoClient(c.uri))) - .toLayer - -class MongoJsonRepository[Elem, Key, Criteria]( - collection: MongoCollection[JsonObject], - toFilter: Criteria => Bson, - idFilter: Elem => (String, Key) -)(using JsonCodec[Elem]) { - def matching(criteria: Criteria): Task[Seq[Elem]] = - val filter = toFilter(criteria) - val query = collection.find(filter) - - for - result <- ZIO.fromFuture(_ => query.toFuture) - proof <- ZIO.collect(result)(j => - ZIO.fromOption(j.getJson.fromJson[Elem].toOption) - ) - yield proof - - def put(elem: Elem): Task[Unit] = - Task.async(cb => - collection - .replaceOne( - equal.tupled(idFilter(elem)), - JsonObject(elem.toJson), - ReplaceOptions().upsert(true) - ) - .subscribe(_ => cb(Task.unit), t => cb(Task.fail(t))) - ) -} diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala deleted file mode 100644 index 68d7196..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala +++ /dev/null @@ -1,12 +0,0 @@ -package works.iterative.tapir - -import sttp.model.Uri - -opaque type BaseUri = Option[Uri] - -object BaseUri: - - def apply(optU: Option[Uri]): BaseUri = optU - def apply(u: Uri): BaseUri = Some(u) - - extension (v: BaseUri) def toUri: Option[Uri] = v diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 653bc51..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,22 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.client.sttp.SttpClientInterpreter -import sttp.tapir.PublicEndpoint -import sttp.tapir.client.sttp.WebSocketToPipe -import scala.concurrent.Future -import sttp.client3.SttpBackend -import sttp.capabilities.WebSockets - -trait CustomTapirPlatformSpecific extends SttpClientInterpreter: - self: CustomTapir => - - type Backend = SttpBackend[Future, WebSockets] - - def makeClient[I, E, O]( - endpoint: PublicEndpoint[I, E, O, Any] - )(using - baseUri: BaseUri, - backend: Backend, - wsToPipe: WebSocketToPipe[Any] - ): I => Future[O] = - toClientThrowErrors(endpoint, baseUri.toUri, backend) diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 2545fbd..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,5 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.ztapir.ZTapir - -trait CustomTapirPlatformSpecific extends ZTapir diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala deleted file mode 100644 index 55e4ca1..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala +++ /dev/null @@ -1,7 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.server.http4s.ztapir.ZHttp4sServerInterpreter - -trait Http4sCustomTapir[Env] - extends CustomTapir - with ZHttp4sServerInterpreter[Env] diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala deleted file mode 100644 index dd52e9b..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala +++ /dev/null @@ -1,14 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.Tapir -import sttp.tapir.json.zio.TapirJsonZio -import sttp.tapir.TapirAliases - -trait CustomTapir - extends Tapir - with TapirJsonZio - with TapirAliases - with CustomTapirPlatformSpecific: - given Schema[ServerError] = Schema.derived - -object CustomTapir extends CustomTapir diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala deleted file mode 100644 index 715591d..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.tapir - -import zio.json.* - -sealed trait ServerError -case class InternalServerError(msg: String) extends ServerError -object InternalServerError: - def fromThrowable(t: Throwable): ServerError = InternalServerError( - t.getMessage - ) - -object ServerError: - given JsonCodec[ServerError] = DeriveJsonCodec.gen diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/File.scala deleted file mode 100644 index 09f6c07..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala +++ /dev/null @@ -1,3 +0,0 @@ -package works.iterative.services.files - -case class File(url: String, name: String) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala deleted file mode 100644 index 46633a5..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala +++ /dev/null @@ -1,25 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.Renderable - -given Renderable[File] with - extension (m: File) - def toHtml: HtmlElement = - li( - cls("pl-3 pr-4 py-3 flex items-center justify-between text-sm"), - div( - cls("w-0 flex-1 flex items-center"), - Icons.solid - .paperclip() - .amend(svg.cls := "flex-shrink-0 text-gray-400"), - span(cls("ml-2 flex-1 w-0 truncate"), m.name) - ), - a( - href(m.url), - cls("font-medium text-indigo-600 hover:text-indigo-500"), - "Otevřít" - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala deleted file mode 100644 index ac59db8..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import io.laminext.syntax.core.{*, given} - -def FileList(files: List[File]): HtmlElement = - ul( - role("list"), - cls("border border-gray-200 rounded-md divide-y divide-gray-200"), - files.map(_.toHtml) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala deleted file mode 100644 index 2e8f690..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala +++ /dev/null @@ -1,81 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import io.laminext.syntax.core.{*, given} - -object FilePicker: - sealed trait Event - sealed trait DoneEvent extends Event - case class SelectionUpdated(files: Set[File]) extends DoneEvent - case object SelectionCancelled extends DoneEvent - case object AvailableFilesRequested extends Event - - def apply( - currentFiles: Signal[List[File]], - availableFilesStream: EventStream[List[File]] - )(selectionUpdates: Observer[Event]): HtmlElement = - val (updatesStream, updatesObserver) = EventStream.withObserver[Event] - val selectorOpen = Var[Boolean](false) - - // This sequence tricks browser into displaying modal content centered - // Inspired by modal in headless ui playground - // https://github.com/tailwindlabs/headlessui/blob/fdd26297953080d5ec905dda0bf5ec9607897d86/packages/playground-react/pages/transitions/component-examples/modal.tsx#L78-L79 - inline def browserCenteringModalTrick: Modifier[HtmlElement] = - Seq[Modifier[HtmlElement]]( - span(cls("hidden sm:inline-block sm:h-screen sm:align-middle")), - "​" // Zero width space - ) - - inline def overlay: Modifier[HtmlElement] = - // Page overlay - /* TODO: transition - enter="ease-out duration-300" - enterFrom="opacity-0" - enterTo="opacity-100" - leave="ease-in duration-200" - leaveFrom="opacity-100" - leaveTo="opacity-0" - */ - div( - div( - onClick.mapTo(false) --> selectorOpen.writer, - cls("fixed inset-0 transition-opacity"), - div(cls("absolute inset-0 bg-gray-500 opacity-75")) - ) - ) - - inline def modalSelector: HtmlElement = - div( - cls("fixed inset-0 z-10 overflow-y-auto"), - div( - cls("text-center sm:block sm:p-0"), - overlay, - browserCenteringModalTrick, - child <-- currentFiles.map( - FileSelector(_, availableFilesStream)(updatesObserver) - ) - ) - ) - - div( - updatesStream --> selectionUpdates, - updatesStream.collect { case _: DoneEvent => - false - } --> selectorOpen.writer, - child.maybe <-- selectorOpen.signal.map(isOpen => - if isOpen then Some(modalSelector) else None - ), - div( - cls("flex flex-col space-y-5"), - child.maybe <-- currentFiles.map(files => - if files.isEmpty then None else Some(FileList(files)) - ), - button( - tpe := "button", - cls := "bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500", - "Zvolit soubory", - onClick.mapTo(true) --> selectorOpen.writer - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala deleted file mode 100644 index b43e07a..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala +++ /dev/null @@ -1,74 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.Loading -import io.laminext.syntax.core.{*, given} -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -object FileSelector: - import FilePicker._ - - def apply( - initialFiles: List[File], - availableFilesStream: EventStream[List[File]] - )(selectionUpdates: Observer[Event]): HtmlElement = - val selectedFiles = Var[Set[File]](initialFiles.to(Set)) - val availableFiles = availableFilesStream.startWithNone - // Request the files to display - selectionUpdates.onNext(AvailableFilesRequested) - div( - cls( - "inline-block transform overflow-hidden rounded-lg bg-white text-left align-bottom shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:align-middle" - ), - role("dialog"), - customHtmlAttr("aria.modal", BooleanAsTrueFalseStringCodec)(true), - aria.labelledBy("modal-headline"), - div( - cls("bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"), - div( - cls("sm:flex sm:items-start"), - div( - cls("mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"), - h3( - cls("text-lg font-medium leading-6 text-gray-900"), - idAttr("modal-headline"), - "Výběr souborů" - ) - ) - ), - child <-- availableFiles - .split(_ => ())((_, _, af) => FileTable(af, selectedFiles)) - .map(_.getOrElse(Loading)) - ), - div( - cls("bg-gray-50 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6"), - span( - cls("flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto"), - button( - typ("button"), - cls( - "focus:shadow-outline-green inline-flex w-full justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-base font-medium leading-6 text-white shadow-sm transition duration-150 ease-in-out hover:bg-indigo-500 focus:border-indigo-700 focus:outline-none sm:text-sm sm:leading-5" - ), - "Potvrdit", - composeEvents(onClick)( - _.sample(selectedFiles) - .map(SelectionUpdated(_)) - ) --> selectionUpdates - ) - ), - span( - cls("mt-3 flex w-full rounded-md shadow-sm sm:mt-0 sm:w-auto"), - button( - typ("button"), - cls( - "focus:shadow-outline-blue inline-flex w-full justify-center rounded-md border border-gray-300 bg-white px-4 py-2 text-base font-medium leading-6 text-gray-700 shadow-sm transition duration-150 ease-in-out hover:text-gray-500 focus:border-blue-300 focus:outline-none sm:text-sm sm:leading-5" - ), - "Zrušit", - onClick.mapTo(SelectionCancelled) --> selectionUpdates - ) - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala deleted file mode 100644 index aa11b2b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala +++ /dev/null @@ -1,91 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import works.iterative.ui.components.tailwind.Icons - -def FileTable( - files: Signal[List[File]], - selectedFiles: Var[Set[File]] -): HtmlElement = - val scope = customHtmlAttr("scope", StringAsIsCodec) - - def headerRow: HtmlElement = - val col = scope("col") - val baseM: Modifier[HtmlElement] = Seq(cls("px-6 py-3"), col) - val textH = cls( - "text-left text-xs font-medium text-gray-500 uppercase tracking-wider" - ) - tr( - th(baseM, span(cls("sr-only"), "Vybrat")), - th(baseM, textH, "Název"), - th(baseM, cls("relative"), span(cls("sr-only"), "Otevřít")) - ) - - def tableRow( - f: File, - idx: Int, - selected: Boolean - )(toggleSelection: Observer[Unit]): HtmlElement = - val baseC = cls("px-6 py-4 whitespace-nowrap text-sm") - tr( - cls(if idx % 2 == 0 then "bg-gray-50" else "bg-white"), - td( - cls("font-medium cursor-pointer"), - onClick.mapTo(()) --> toggleSelection, - cls(if selected then "text-green-900" else "text-gray-200"), - Icons.outline.`check-circle`().amend(svg.cls := "mx-auto"), - span(cls("sr-only"), if selected then "Vybráno" else "Nevybráno") - ), - td( - baseC, - cls("font-medium text-gray-900"), - f.name, - onClick.mapTo(()) --> toggleSelection - ), - td( - baseC, - cls("text-right font-medium"), - a( - href(f.url), - target("_blank"), - cls( - "flex items-center space-x-4 text-indigo-600 hover:text-indigo-900" - ), - Icons.outline.`external-link`(), - "Otevřít" - ) - ) - ) - - div( - cls("flex flex-col"), - div(cls("overflow-x-auto sm:-mx-6 lg:-mx-8")), - div( - cls("py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8"), - div( - cls("shadow overflow-hidden border-b border-gray-200 sm:rounded-lg"), - table( - cls("min-w-full divide-y divide-gray-200"), - thead( - cls("bg-gray-50"), - headerRow - ), - tbody( - children <-- files - .map(_.zipWithIndex) - .combineWithFn(selectedFiles)((f, sel) => - f.map((file, idx) => - val active = sel.contains(file) - tableRow(file, idx, active)( - selectedFiles.writer - .contramap(_ => if active then sel - file else sel + file) - ) - ) - ) - ) - ) - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala deleted file mode 100644 index 0fc8796..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala +++ /dev/null @@ -1,32 +0,0 @@ -package works.iterative.ui.components.tailwind - -import CustomAttrs.ariaHidden -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -// TODO: render icon or picture based on img signal -class Avatar($avatarImg: Signal[Option[String]]): - inline def avatarPlaceholder(size: Int): HtmlElement = - div( - cls := s"rounded-full text-indigo-200 bg-indigo-500 h-${size} w-${size} flex items-center justify-center", - Icons.outline.user(size - 2) - ) - - inline def avatarImage(size: Int): Signal[HtmlElement] = - $avatarImg.split(_ => ())((_, _, $url) => - img( - cls := s"w-$size h-$size rounded-full", - src <-- $url, - alt := "" - ) - ).map(_.getOrElse(avatarPlaceholder(size))) - - inline def avatar(size: Int): HtmlElement = - div( - cls := "relative", - child <-- avatarImage(size), - span( - cls := "absolute inset-0 shadow-inner rounded-full", - ariaHidden := true - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala deleted file mode 100644 index bcbed88..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala +++ /dev/null @@ -1,77 +0,0 @@ -package works.iterative.ui.components.tailwind - -enum ColorWeight(value: String): - inline def toCSS: String = value - // To be used for black and white until - // we support better mechanism via extension methods - case w__ extends ColorWeight("") - case w50 extends ColorWeight("50") - case w100 extends ColorWeight("100") - case w200 extends ColorWeight("200") - case w300 extends ColorWeight("300") - case w400 extends ColorWeight("400") - case w500 extends ColorWeight("500") - case w600 extends ColorWeight("600") - case w700 extends ColorWeight("700") - case w800 extends ColorWeight("800") - case w900 extends ColorWeight("900") - -enum Color(name: String): - import ColorWeight._ - - inline def toCSSNoColorWeight(prefix: String): String = - s"${prefix}-${name}" - inline def toCSSWithColorWeight( - prefix: String, - weight: ColorWeight - ): String = - s"${prefix}-${name}-${weight.toCSS}" - inline def toCSS(prefix: String)(weight: ColorWeight): String = - weight match { - case `w__` => toCSSNoColorWeight(prefix) - case _ => toCSSWithColorWeight(prefix, weight) - } - inline def bg: ColorWeight => String = toCSS("bg")(_) - inline def text: ColorWeight => String = toCSS("text")(_) - inline def decoration: ColorWeight => String = toCSS("decoration")(_) - inline def border: ColorWeight => String = toCSS("border")(_) - inline def outline: ColorWeight => String = toCSS("outline")(_) - inline def divide: ColorWeight => String = toCSS("divide")(_) - inline def ring: ColorWeight => String = toCSS("ring")(_) - inline def ringOffset: ColorWeight => String = toCSS("ring-offset")(_) - inline def shadow: ColorWeight => String = toCSS("shadow")(_) - inline def accent: ColorWeight => String = toCSS("accent")(_) - - // TODO: change the "stupid" methods to extension methods - // that will keep the invariants in comments lower - case current extends Color("current") - case inherit extends Color("inherit") - // Not present in for all methods - case transp extends Color("transparent") - // Seen in accent, not preset otherwise - case auto extends Color("auto") - // Black and white do not have weight - case black extends Color("black") - case white extends Color("white") - case slate extends Color("slate") - case gray extends Color("gray") - case zinc extends Color("zinc") - case neutral extends Color("neutral") - case stone extends Color("stone") - case red extends Color("red") - case orange extends Color("orange") - case amber extends Color("amber") - case yellow extends Color("yellow") - case lime extends Color("lime") - case green extends Color("green") - case emerald extends Color("emerald") - case teal extends Color("teal") - case cyan extends Color("cyan") - case sky extends Color("sky") - case blue extends Color("blue") - case indigo extends Color("indigo") - case violet extends Color("violet") - case purple extends Color("purple") - case fuchsia extends Color("fuchsia") - case pink extends Color("pink") - case rose extends Color("rose") diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala deleted file mode 100644 index 6fb453c..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -object CustomAttrs { - // Made a pull request to add aria-current to scala-dom-types, remove after - val ariaCurrent = customHtmlAttr("aria-current", StringAsIsCodec) - val ariaHidden = customHtmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - - val datetime = customHtmlAttr("datetime", StringAsIsCodec) - - object svg { - import com.raquo.laminar.api.L.svg.{*, given} - val ariaHidden = - customSvgAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - } -} diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala deleted file mode 100644 index 6cda48b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala +++ /dev/null @@ -1,28 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -object Display: - - enum Breakpoint: - case sm, md, lg, xl, `2xl` - - enum DisplayClass: - case block, `inline-block`, `inline`, flex, `inline-flex`, table, - `inline-table`, `table-caption` - - object ShowUpFrom: - inline def apply( - br: Breakpoint, - dc: DisplayClass = DisplayClass.block - ): HtmlElement = - div( - cls := "hidden", - cls := s"${br}:${dc}" - ) - - object HideUpTo: - inline def apply(br: Breakpoint): HtmlElement = - div( - cls := s"${br}:hidden" - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala deleted file mode 100644 index 4a8b065..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala +++ /dev/null @@ -1,270 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec -import com.raquo.domtypes.generic.defs.attrs.AriaAttrs -import com.raquo.laminar.api.L.svg.{*, given} -import com.raquo.laminar.builders.SvgBuilders -import com.raquo.laminar.keys.ReactiveSvgAttr - -// TODO: fix sizes, colors, hover and stuff, normalize and amend on call site -object Icons: - object aria: - inline def hidden = CustomAttrs.svg.ariaHidden - - object outline: - val defaultSize: Int = 6 - - inline def bell(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" - ) - ) - - inline def `check-circle`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" - ) - ) - - inline def `document-add`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M9 13h6m-3-3v6m5 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" - ) - ) - - inline def `external-link`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" - ) - ) - - inline def menu(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M4 6h16M4 12h16M4 18h16" - ) - ) - - inline def `status-offline`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M18.364 5.636a9 9 0 010 12.728m0 0l-2.829-2.829m2.829 2.829L21 21M15.536 8.464a5 5 0 010 7.072m0 0l-2.829-2.829m-4.243 2.829a4.978 4.978 0 01-1.414-2.83m-1.414 5.658a9 9 0 01-2.167-9.238m7.824 2.167a1 1 0 111.414 1.414m-1.414-1.414L3 3m8.293 8.293l1.414 1.414" - ) - ) - - inline def user(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" - ) - ) - - inline def x(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M6 18L18 6M6 6l12 12" - ) - ) - - end outline - - object solid: - val defaultSize: Int = 5 - - inline def users(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - d := "M9 6a3 3 0 11-6 0 3 3 0 016 0zM17 6a3 3 0 11-6 0 3 3 0 016 0zM12.93 17c.046-.327.07-.66.07-1a6.97 6.97 0 00-1.5-4.33A5 5 0 0119 16v1h-6.07zM6 11a5 5 0 015 5v1H1v-1a5 5 0 015-5z" - ) - ) - - inline def `location-marker`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M5.05 4.05a7 7 0 119.9 9.9L10 18.9l-4.95-4.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z", - clipRule := "evenodd" - ) - ) - - inline def calendar(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z", - clipRule := "evenodd" - ) - ) - - inline def `chevron-right`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z", - clipRule := "evenodd" - ) - ) - - inline def search(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z", - clipRule := "evenodd" - ) - ) - - inline def filter(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M3 3a1 1 0 011-1h12a1 1 0 011 1v3a1 1 0 01-.293.707L12 11.414V15a1 1 0 01-.293.707l-2 2A1 1 0 018 17v-5.586L3.293 6.707A1 1 0 013 6V3z", - clipRule := "evenodd" - ) - ) - - inline def `arrow-narrow-left`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M7.707 14.707a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 1.414L5.414 9H17a1 1 0 110 2H5.414l2.293 2.293a1 1 0 010 1.414z", - clipRule := "evenodd" - ) - ) - - inline def home(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - d := "M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z" - ) - ) - - inline def paperclip(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size} text-gray-400", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M8 4a3 3 0 00-3 3v4a5 5 0 0010 0V7a1 1 0 112 0v4a7 7 0 11-14 0V7a5 5 0 0110 0v4a3 3 0 11-6 0V7a1 1 0 012 0v4a1 1 0 102 0V7a3 3 0 00-3-3z", - clipRule := "evenodd" - ) - ) - - end solid -end Icons diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/LinkSupport.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/LinkSupport.scala deleted file mode 100644 index 88f0b9a..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/LinkSupport.scala +++ /dev/null @@ -1,12 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.jsdom.defs.events.TypedTargetMouseEvent - -object LinkSupport: - - extension [El <: org.scalajs.dom.EventTarget]( - ep: EventProcessor[TypedTargetMouseEvent[El], TypedTargetMouseEvent[El]] - ) - def noKeyMod = - ep.filter(ev => !(ev.ctrlKey || ev.metaKey || ev.shiftKey || ev.altKey)) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Loader.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Loader.scala deleted file mode 100644 index 5399933..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Loader.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -// TODO: proper loader -def Loading = - div( - cls := "bg-gray-50 overflow-hidden rounded-lg", - div( - cls := "px-4 py-5 sm:p-6", - "Loading..." - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Renderable.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Renderable.scala deleted file mode 100644 index a0fcc5b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Renderable.scala +++ /dev/null @@ -1,6 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -trait Renderable[A]: - extension (a: A) def toHtml: HtmlElement diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/ComboBox.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/ComboBox.scala deleted file mode 100644 index 8c5b8a1..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/ComboBox.scala +++ /dev/null @@ -1,92 +0,0 @@ -package works.iterative.ui.components.tailwind -package form - -import com.raquo.laminar.api.L.{*, given} -import io.laminext.syntax.core.* - -case class ComboBox( - id: String, - options: Signal[List[ComboBox.Option]], - valueUpdates: Observer[List[String]] -) - -object ComboBox: - - extension (m: ComboBox) - def toHtml: HtmlElement = - val isOpen = Var(false) - div( - cls := "relative mt-1", - input( - idAttr := m.id, - tpe := "text", - cls := "w-full rounded-md border border-gray-300 bg-white py-2 pl-3 pr-12 shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 sm:text-sm", - role := "combobox", - aria.controls := "options", - aria.expanded := false, - onClick.mapTo(true) --> isOpen.writer - ), - button( - tpe := "button", - cls := "absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-none", { - import svg.* - svg( - cls := "h-5 w-5 text-gray-400", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - CustomAttrs.svg.ariaHidden := true, - path( - fillRule := "evenodd", - d := "M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z", - clipRule := "evenodd" - ) - ) - } - ), - ul( - cls <-- isOpen.signal.switch("", "hidden"), - cls := "absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm", - idAttr := "options", - role := "listbox", - children <-- m.options.map(_.map(_.toHtml)) - ) - ) - - case class Option(value: String, active: Boolean) - - object Option: - extension (m: Option) - def toHtml: HtmlElement = - li( - cls := "relative cursor-default select-none py-2 pl-8 pr-4", - cls := (if m.active then "text-white bg-indigo-600" - else "text-gray-900"), - idAttr := "option-0", - role := "option", - tabIndex := -1, - span( - cls := "block truncate", - m.value - ), - if m.active then - span( - cls := "absolute inset-y-0 left-0 flex items-center pl-1.5", - cls := (if m.active then "text-white" else "text-indigo-600"), { - import svg.* - svg( - cls := "h-5 w-5", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - CustomAttrs.svg.ariaHidden := true, - path( - fillRule := "evenodd", - d := "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", - clipRule := "evenodd" - ) - ) - } - ) - else emptyNode - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/Form.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/Form.scala deleted file mode 100644 index 1b41dee..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/Form.scala +++ /dev/null @@ -1,17 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} -import org.scalajs.dom -import com.raquo.laminar.nodes.ReactiveHtmlElement - -object Form: - val Body = FormBody - val Section = FormSection - val Row = FormRow - - def apply(body: HtmlElement, buttons: HtmlElement): HtmlElement = - form( - cls := "space-y-8 divide-y divide-gray-200", - body, - buttons - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormBody.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormBody.scala deleted file mode 100644 index 89abc9c..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormBody.scala +++ /dev/null @@ -1,10 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} - -object FormBody: - def apply(sections: HtmlElement*): HtmlElement = - div( - cls := "space-y-8 divide-y divide-gray-200 sm:space-y-5", - sections - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormFields.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormFields.scala deleted file mode 100644 index a2e4cc6..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormFields.scala +++ /dev/null @@ -1,14 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.laminar.nodes.ReactiveHtmlElement -import org.scalajs.dom - -object FormFields: - def apply( - mods: Modifier[ReactiveHtmlElement[dom.HTMLElement]]* - ): HtmlElement = - div( - cls := "mt-6 sm:mt-5 space-y-6 sm:space-y-5", - mods - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormHeader.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormHeader.scala deleted file mode 100644 index 4083841..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormHeader.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} - -object FormHeader: - case class ViewModel(header: String, description: String) - def apply(m: ViewModel): HtmlElement = - div( - h3(cls := "text-lg leading-6 font-medium text-gray-900", m.header), - p(cls := "mt-1 max-w-2xl text-sm text-gray-500", m.description) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormRow.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormRow.scala deleted file mode 100644 index ffc5807..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormRow.scala +++ /dev/null @@ -1,22 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} - -case class FormRow(id: String, label: String, content: Modifier[Div]) - -object FormRow: - - extension (m: FormRow) - def toHtml: HtmlElement = - div( - cls := "sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5", - label( - forId := m.id, - cls := "block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2", - m.label - ), - div( - cls := "mt-1 sm:mt-0 sm:col-span-2", - m.content - ) - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala deleted file mode 100644 index e7e6627..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.akka - -// Base class for all command-processing related exceptions from handlers -sealed abstract class CommandHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandNotAvailable[C, S](cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd není dostupný ve stavu $state" - ) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandRejected[C, S](reason: String, cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd byl ve stavu $state odmítnut s odůvodněním $reason" - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala deleted file mode 100644 index 72a5bf9..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.akka - -sealed abstract class EventHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -case class UnhandledEvent[Event, State](event: Event, state: State) - extends EventHandlerException( - s"Událost $event nastala ve stavu $state bez možnosti zpracování" - ) diff --git a/iw/bom.sc b/iw/bom.sc deleted file mode 100644 index e9266b8..0000000 --- a/iw/bom.sc +++ /dev/null @@ -1,227 +0,0 @@ -import mill._, scalalib._ - -object IWMaterials { - - object Versions { - val akka = "2.6.16" - val akkaHttp = "10.2.4" - val cats = "2.7.0" - val elastic4s = "7.12.2" - val http4s = "0.23.10" - val http4sPac4J = "4.0.0" - val laminar = "0.14.2" - val laminext = laminar - val logbackClassic = "1.2.10" - val pac4j = "5.2.0" - val play = "2.8.8" - val playJson = "2.9.2" - val scalaTest = "3.2.9" - val slick = "3.3.3" - val sttpClient = "3.5.0" - val tapir = "0.20.1" - val urlDsl = "0.4.0" - val waypoint = "0.5.0" - val zio = "2.0.0-RC2" - val zioConfig = "3.0.0-RC2" - val zioInteropCats = "3.3.0-RC2" - val zioJson = "0.3.0-RC3" - val zioLogging = "2.0.0-RC5" - val zioPrelude = "1.0.0-RC10" - val zioZMX = "0.0.11" - } - - object Deps extends AkkaLibs with SlickLibs { - import IWMaterials.{Versions => V} - - val zioOrg = "dev.zio" - - def zioLib(name: String, version: String): Dep = - ivy"$zioOrg::zio-$name::$version" - - lazy val zio: Dep = ivy"$zioOrg::zio:${V.zio}" - - lazy val zioTest: Dep = zioLib("test", V.zio) - lazy val zioTestSbt: Dep = zioLib("test", V.zio) - - lazy val zioConfig: Dep = zioLib("config", V.zioConfig) - lazy val zioConfigTypesafe: Dep = - zioLib("config-typesafe", V.zioConfig) - lazy val zioConfigMagnolia: Dep = - zioLib("config-magnolia", V.zioConfig) - - lazy val zioJson: Dep = zioLib("json", V.zioJson) - lazy val zioLogging: Dep = zioLib("logging", V.zioLogging) - lazy val zioLoggingSlf4j: Dep = - zioLib("logging-slf4j", V.zioLogging) - lazy val zioPrelude: Dep = zioLib("prelude", V.zioPrelude) - lazy val zioStreams: Dep = zioLib("streams", V.zio) - lazy val zioZMX: Dep = zioLib("zmx", V.zioZMX) - lazy val zioInteropCats: Dep = - zioLib("interop-cats", V.zioInteropCats) - - /* What is the equivalent? ZIOModule with prepared test config? - def useZIO(testConf: Configuration*): Agg[Dep] = Agg( - zio, - zioTest, - zioTestSbt, - testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework") - ) - */ - - /* - def useZIOAll(testConf: Configuration*): Seq[Def.Setting[_]] = - useZIO(testConf: _*) ++ Seq( - zioStreams, - zioConfig, - zioConfigTypesafe, - zioConfigMagnolia, - zioJson, - zioZMX, - zioLogging, - zioPrelude - ) - */ - - private val tapirOrg = "com.softwaremill.sttp.tapir" - def tapirLib(name: String): Dep = - ivy"${tapirOrg}::tapir-$name::${V.tapir}" - - lazy val tapirCore: Dep = tapirLib("core") - lazy val tapirZIO: Dep = tapirLib("zio") - lazy val tapirZIOJson: Dep = tapirLib("json-zio") - lazy val tapirSttpClient: Dep = tapirLib("sttp-client") - lazy val tapirCats: Dep = tapirLib("cats") - lazy val tapirZIOHttp4sServer: Dep = tapirLib("zio-http4s-server") - - private val sttpClientOrg = "com.softwaremill.sttp.client3" - def sttpClientLib(name: String): Dep = - ivy"${sttpClientOrg}::${name}:${V.sttpClient}" - - lazy val sttpClientCore: Dep = sttpClientLib("core") - - lazy val http4sBlazeServer: Dep = - ivy"org.http4s::http4s-blaze-server:${V.http4s}" - - lazy val http4sPac4J: Dep = - ivy"org.pac4j::http4s-pac4j:${V.http4sPac4J}" - lazy val pac4jOIDC: Dep = - ivy"org.pac4j:pac4j-oidc:${V.pac4j}" - - lazy val scalaTest: Dep = - ivy"org.scalatest::scalatest:${V.scalaTest}" - lazy val scalaTestPlusScalacheck: Dep = - ivy"org.scalatestplus::scalacheck-1-15:3.2.9.0" - lazy val playScalaTest: Dep = - ivy"org.scalatestplus.play::scalatestplus-play:5.1.0" - - private val playOrg = "com.typesafe.play" - lazy val playMailer: Dep = ivy"${playOrg}::play-mailer:8.0.1" - lazy val playServer: Dep = ivy"${playOrg}::play-server:${V.play}" - lazy val playAkkaServer: Dep = - ivy"${playOrg}::play-akka-http-server:${V.play}" - lazy val play: Dep = ivy"${playOrg}::play:${V.play}" - lazy val playAhcWs: Dep = ivy"${playOrg}::play-ahc-ws:${V.play}" - lazy val playJson: Dep = ivy"${playOrg}::play-json:${V.playJson}" - - private val elastic4sOrg = "com.sksamuel.elastic4s" - lazy val useElastic4S: Agg[Dep] = Agg( - ivy"${elastic4sOrg}::elastic4s-core:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-client-akka:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-http-streams:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-json-play:${V.elastic4s}" - ) - - lazy val laminar: Dep = ivy"com.raquo::laminar::${V.laminar}" - - private def laminext(name: String): Dep = - ivy"io.laminext::$name::${V.laminar}" - - lazy val laminextCore: Dep = laminext("core") - lazy val laminextTailwind: Dep = laminext("tailwind") - lazy val laminextFetch: Dep = laminext("fetch") - lazy val laminextValidationCore: Dep = laminext("validation-core") - lazy val laminextUI: Dep = laminext("ui") - - lazy val waypoint: Dep = - ivy"com.raquo::waypoint::${V.waypoint}" - - lazy val urlDsl: Dep = - ivy"be.doeraene::url-dsl::${V.urlDsl}" - - lazy val scalaJavaTime: Dep = - ivy"io.github.cquiroz::scala-java-time::2.3.0" - - lazy val scalaJavaLocales: Dep = - ivy"io.github.cquiroz::scala-java-locales::1.2.1" - - lazy val logbackClassic: Dep = - ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" - } - - trait AkkaLibs { - - self: SlickLibs => - - object akka { - val V = Versions.akka - val tOrg = "com.typesafe.akka" - val lOrg = "com.lightbend.akka" - - def akkaMod(name: String): Dep = - ivy"$tOrg::akka-$name:$V" - - lazy val actor: Dep = akkaMod("actor") - lazy val actorTyped: Dep = akkaMod("actor-typed") - lazy val stream: Dep = akkaMod("stream") - lazy val persistence: Dep = akkaMod("persistence-typed") - lazy val persistenceQuery: Dep = akkaMod("persistence-query") - lazy val persistenceJdbc: Dep = - ivy"$lOrg::akka-persistence-jdbc:5.0.4" - val persistenceTestKit: Dep = ivy"$tOrg::akka-persistence-testkit:$V" - - object http { - val V = Versions.akkaHttp - - lazy val http: Dep = ivy"$tOrg::akka-http:$V" - lazy val sprayJson: Dep = ivy"$tOrg::akka-http-spray-json:$V" - - } - - object projection { - val V = "1.2.2" - - lazy val core: Dep = - ivy"$lOrg::akka-projection-core:$V" - lazy val eventsourced: Dep = - ivy"$lOrg::akka-projection-eventsourced:$V" - lazy val slick: Dep = - ivy"$lOrg::akka-projection-slick:$V" - lazy val jdbc: Dep = - ivy"$lOrg::akka-projection-jdbc:$V" - } - - object profiles { - // TODO: deal with cross-version for Scala3 (for3Use2_13) - lazy val eventsourcedJdbcProjection: Agg[Dep] = Agg( - persistenceQuery, - projection.core, - projection.eventsourced, - projection.slick, - persistenceJdbc - ) ++ slick.default - } - } - } - - trait SlickLibs { - object slick { - val V = IWMaterials.Versions.slick - val org = "com.typesafe.slick" - - lazy val slick: Dep = ivy"$org::slick:$V" - lazy val hikaricp: Dep = ivy"$org::slick-hikaricp:$V" - lazy val default: Agg[Dep] = Agg(slick, hikaricp) - } - } - -} diff --git a/iw/build.sc b/iw/build.sc deleted file mode 100644 index fbbc3b8..0000000 --- a/iw/build.sc +++ /dev/null @@ -1,144 +0,0 @@ -import mill._, scalalib._, scalajslib._ - -import $file.bom - -object support { - val Deps = bom.IWMaterials.Deps - val Versions = bom.IWMaterials.Versions - - trait CommonModule extends ScalaModule { - def scalaVersion = "3.1.1" - def scalacOptions = Seq( - "-encoding", - "utf8", - "-deprecation", - "-explain-types", - "-explain", - "-feature", - "-language:experimental.macros", - "-language:higherKinds", - "-language:implicitConversions", - "-unchecked", - "-Xfatal-warnings", - "-Ykind-projector" - ) - } - - trait CommonJSModule extends CommonModule with ScalaJSModule { - def scalaJSVersion = "1.8.0" - } - - trait CrossPlatformModule extends Module { outer => - def ivyDeps: T[Agg[Dep]] = Agg[Dep]() - def jsDeps: T[Agg[Dep]] = Agg[Dep]() - def jvmDeps: T[Agg[Dep]] = Agg[Dep]() - def moduleDeps: Seq[Module] = Seq[Module]() - - trait PlatformModule extends CommonModule { - def platform: String - override def millSourcePath = outer.millSourcePath - override def ivyDeps = outer.ivyDeps() ++ (platform match { - case "js" => jsDeps() - case _ => jvmDeps() - }) - override def moduleDeps = outer.moduleDeps.collect { - case m: CrossPlatformModule => - platform match { - case "js" => m.js - case _ => m.jvm - } - case m: JavaModule => m - } - } - - trait JsModule extends PlatformModule with CommonJSModule { - def platform = "js" - } - - trait JvmModule extends PlatformModule { - def platform = "jvm" - } - - val js: JsModule - val jvm: JvmModule - } - - trait PureCrossModule extends CrossPlatformModule { - override object js extends JsModule - override object jvm extends JvmModule - } - - trait PureCrossSbtModule extends CrossPlatformModule { - override object js extends JsModule with SbtModule - override object jvm extends JvmModule with SbtModule - } - - trait FullCrossSbtModule extends CrossPlatformModule { - trait FullSources extends JavaModule { self: PlatformModule => - override def sources = T.sources( - millSourcePath / platform / "src" / "main" / "scala", - millSourcePath / "shared" / "src" / "main" / "scala" - ) - - override def resources = T.sources( - millSourcePath / platform / "src" / "main" / "resources", - millSourcePath / "shared" / "src" / "main" / "resources" - ) - } - - override object js extends JsModule with FullSources - override object jvm extends JvmModule with FullSources - } - -} - -import support._ - -object mongo extends CommonModule { - def ivyDeps = Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - ivy"org.mongodb.scala::mongo-scala-driver:4.2.3".withDottyCompat( - scalaVersion() - ) - ) -} - -object tapir extends FullCrossSbtModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson, Deps.zioJson) - def jvmDeps = Agg(Deps.tapirZIO, Deps.tapirZIOHttp4sServer) - def jsDeps = Agg(Deps.tapirSttpClient) -} - -object akkaPersistence extends CommonModule { - def millSourcePath = build.millSourcePath / "akka-persistence" - def ivyDeps = (Agg( - Deps.akka.persistenceQuery, - Deps.akka.persistenceJdbc, - Deps.akka.projection.core, - Deps.akka.projection.eventsourced, - Deps.akka.projection.slick - ) ++ Deps.slick.default).map(_.withDottyCompat(scalaVersion())) ++ Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - Deps.akka.persistence.withDottyCompat(scalaVersion()), - ivy"com.typesafe.akka::akka-cluster-sharding-typed:${Deps.akka.V}" - .withDottyCompat(scalaVersion()) - ) -} - -object ui extends CommonJSModule { - def ivyDeps = Agg( - Deps.zio, - Deps.laminar, - Deps.zioJson, - Deps.waypoint, - Deps.urlDsl, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) -} diff --git a/iw/domain.sc b/iw/domain.sc deleted file mode 100644 index b09c56b..0000000 --- a/iw/domain.sc +++ /dev/null @@ -1,78 +0,0 @@ -import mill._, scalalib._ - -import $file.{build => ff}, ff.support._ - -trait DomainModule extends Module { - // General extra model deps - def modelModules: Seq[Module] = Seq.empty - // General extra codecs deps - def codecsModules: Seq[Module] = Seq.empty - // General extra endpoints deps - def endpointsModules: Seq[Module] = Seq.empty - - // Implementation deps for repo - def repoModules: Seq[JavaModule] = Seq.empty - - object shared extends Module { - object model extends PureCrossModule { - def moduleDeps = modelModules - def ivyDeps = Agg(Deps.zioPrelude) - } - object codecs extends PureCrossModule { - def moduleDeps = Seq(model) ++ codecsModules - def ivyDeps = Agg(Deps.zioJson) - } - } - - trait CommonProjects extends Module { - object model extends PureCrossModule { - def ivyDeps = Agg(Deps.zioPrelude) - def moduleDeps = Seq(shared.model) - } - object codecs extends PureCrossModule { - def moduleDeps = - Seq(model, shared.model, shared.codecs, ff.tapir) - } - object endpoints extends PureCrossModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson) - def moduleDeps = Seq(model, codecs) ++ endpointsModules - } - object client extends CommonJSModule { - def moduleDeps = Seq(endpoints.js) - } - object components extends CommonJSModule { - def ivyDeps = Agg( - Deps.laminar, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) - def moduleDeps = Seq(model.js, codecs.js, client, ff.ui) - } - } - - object query extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(repo, query.endpoints.jvm) - } - object repo extends CommonModule { - def ivyDeps = Agg(Deps.zio) - def moduleDeps = Seq(model.jvm, codecs.jvm) ++ repoModules - } - object projection extends CommonModule { - def moduleDeps = Seq(repo, ff.akkaPersistence) - } - } - - object command extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(entity, command.endpoints.jvm) - } - object entity extends CommonModule { - def moduleDeps = Seq(model.jvm, codecs.jvm, ff.akkaPersistence) - } - } -} diff --git a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala b/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala deleted file mode 100644 index 0fa3493..0000000 --- a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala +++ /dev/null @@ -1,53 +0,0 @@ -package works.iterative.mongo - -import zio.* -import zio.json.* -import zio.config.* -import org.mongodb.scala.* -import org.mongodb.scala.model.Filters.* -import org.bson.json.JsonObject -import org.mongodb.scala.model.ReplaceOptions -import org.mongodb.scala.bson.conversions.Bson - -case class MongoConfig(uri: String) - -object MongoConfig: - val configDesc = - import ConfigDescriptor.* - nested("MONGO")(string("URI").default("mongodb://localhost:27017")) - .to[MongoConfig] - val fromEnv = ZConfig.fromSystemEnv(configDesc) - -extension (m: MongoClient.type) - def layer: RLayer[MongoConfig, MongoClient] = - ZIO - .serviceWithZIO[MongoConfig](c => Task.attempt(MongoClient(c.uri))) - .toLayer - -class MongoJsonRepository[Elem, Key, Criteria]( - collection: MongoCollection[JsonObject], - toFilter: Criteria => Bson, - idFilter: Elem => (String, Key) -)(using JsonCodec[Elem]) { - def matching(criteria: Criteria): Task[Seq[Elem]] = - val filter = toFilter(criteria) - val query = collection.find(filter) - - for - result <- ZIO.fromFuture(_ => query.toFuture) - proof <- ZIO.collect(result)(j => - ZIO.fromOption(j.getJson.fromJson[Elem].toOption) - ) - yield proof - - def put(elem: Elem): Task[Unit] = - Task.async(cb => - collection - .replaceOne( - equal.tupled(idFilter(elem)), - JsonObject(elem.toJson), - ReplaceOptions().upsert(true) - ) - .subscribe(_ => cb(Task.unit), t => cb(Task.fail(t))) - ) -} diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala deleted file mode 100644 index 68d7196..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala +++ /dev/null @@ -1,12 +0,0 @@ -package works.iterative.tapir - -import sttp.model.Uri - -opaque type BaseUri = Option[Uri] - -object BaseUri: - - def apply(optU: Option[Uri]): BaseUri = optU - def apply(u: Uri): BaseUri = Some(u) - - extension (v: BaseUri) def toUri: Option[Uri] = v diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 653bc51..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,22 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.client.sttp.SttpClientInterpreter -import sttp.tapir.PublicEndpoint -import sttp.tapir.client.sttp.WebSocketToPipe -import scala.concurrent.Future -import sttp.client3.SttpBackend -import sttp.capabilities.WebSockets - -trait CustomTapirPlatformSpecific extends SttpClientInterpreter: - self: CustomTapir => - - type Backend = SttpBackend[Future, WebSockets] - - def makeClient[I, E, O]( - endpoint: PublicEndpoint[I, E, O, Any] - )(using - baseUri: BaseUri, - backend: Backend, - wsToPipe: WebSocketToPipe[Any] - ): I => Future[O] = - toClientThrowErrors(endpoint, baseUri.toUri, backend) diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 2545fbd..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,5 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.ztapir.ZTapir - -trait CustomTapirPlatformSpecific extends ZTapir diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala deleted file mode 100644 index 55e4ca1..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala +++ /dev/null @@ -1,7 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.server.http4s.ztapir.ZHttp4sServerInterpreter - -trait Http4sCustomTapir[Env] - extends CustomTapir - with ZHttp4sServerInterpreter[Env] diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala deleted file mode 100644 index dd52e9b..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala +++ /dev/null @@ -1,14 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.Tapir -import sttp.tapir.json.zio.TapirJsonZio -import sttp.tapir.TapirAliases - -trait CustomTapir - extends Tapir - with TapirJsonZio - with TapirAliases - with CustomTapirPlatformSpecific: - given Schema[ServerError] = Schema.derived - -object CustomTapir extends CustomTapir diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala deleted file mode 100644 index 715591d..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.tapir - -import zio.json.* - -sealed trait ServerError -case class InternalServerError(msg: String) extends ServerError -object InternalServerError: - def fromThrowable(t: Throwable): ServerError = InternalServerError( - t.getMessage - ) - -object ServerError: - given JsonCodec[ServerError] = DeriveJsonCodec.gen diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/File.scala deleted file mode 100644 index 09f6c07..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala +++ /dev/null @@ -1,3 +0,0 @@ -package works.iterative.services.files - -case class File(url: String, name: String) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala deleted file mode 100644 index 46633a5..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala +++ /dev/null @@ -1,25 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.Renderable - -given Renderable[File] with - extension (m: File) - def toHtml: HtmlElement = - li( - cls("pl-3 pr-4 py-3 flex items-center justify-between text-sm"), - div( - cls("w-0 flex-1 flex items-center"), - Icons.solid - .paperclip() - .amend(svg.cls := "flex-shrink-0 text-gray-400"), - span(cls("ml-2 flex-1 w-0 truncate"), m.name) - ), - a( - href(m.url), - cls("font-medium text-indigo-600 hover:text-indigo-500"), - "Otevřít" - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala deleted file mode 100644 index ac59db8..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import io.laminext.syntax.core.{*, given} - -def FileList(files: List[File]): HtmlElement = - ul( - role("list"), - cls("border border-gray-200 rounded-md divide-y divide-gray-200"), - files.map(_.toHtml) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala deleted file mode 100644 index 2e8f690..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala +++ /dev/null @@ -1,81 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import io.laminext.syntax.core.{*, given} - -object FilePicker: - sealed trait Event - sealed trait DoneEvent extends Event - case class SelectionUpdated(files: Set[File]) extends DoneEvent - case object SelectionCancelled extends DoneEvent - case object AvailableFilesRequested extends Event - - def apply( - currentFiles: Signal[List[File]], - availableFilesStream: EventStream[List[File]] - )(selectionUpdates: Observer[Event]): HtmlElement = - val (updatesStream, updatesObserver) = EventStream.withObserver[Event] - val selectorOpen = Var[Boolean](false) - - // This sequence tricks browser into displaying modal content centered - // Inspired by modal in headless ui playground - // https://github.com/tailwindlabs/headlessui/blob/fdd26297953080d5ec905dda0bf5ec9607897d86/packages/playground-react/pages/transitions/component-examples/modal.tsx#L78-L79 - inline def browserCenteringModalTrick: Modifier[HtmlElement] = - Seq[Modifier[HtmlElement]]( - span(cls("hidden sm:inline-block sm:h-screen sm:align-middle")), - "​" // Zero width space - ) - - inline def overlay: Modifier[HtmlElement] = - // Page overlay - /* TODO: transition - enter="ease-out duration-300" - enterFrom="opacity-0" - enterTo="opacity-100" - leave="ease-in duration-200" - leaveFrom="opacity-100" - leaveTo="opacity-0" - */ - div( - div( - onClick.mapTo(false) --> selectorOpen.writer, - cls("fixed inset-0 transition-opacity"), - div(cls("absolute inset-0 bg-gray-500 opacity-75")) - ) - ) - - inline def modalSelector: HtmlElement = - div( - cls("fixed inset-0 z-10 overflow-y-auto"), - div( - cls("text-center sm:block sm:p-0"), - overlay, - browserCenteringModalTrick, - child <-- currentFiles.map( - FileSelector(_, availableFilesStream)(updatesObserver) - ) - ) - ) - - div( - updatesStream --> selectionUpdates, - updatesStream.collect { case _: DoneEvent => - false - } --> selectorOpen.writer, - child.maybe <-- selectorOpen.signal.map(isOpen => - if isOpen then Some(modalSelector) else None - ), - div( - cls("flex flex-col space-y-5"), - child.maybe <-- currentFiles.map(files => - if files.isEmpty then None else Some(FileList(files)) - ), - button( - tpe := "button", - cls := "bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500", - "Zvolit soubory", - onClick.mapTo(true) --> selectorOpen.writer - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala deleted file mode 100644 index b43e07a..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala +++ /dev/null @@ -1,74 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.Loading -import io.laminext.syntax.core.{*, given} -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -object FileSelector: - import FilePicker._ - - def apply( - initialFiles: List[File], - availableFilesStream: EventStream[List[File]] - )(selectionUpdates: Observer[Event]): HtmlElement = - val selectedFiles = Var[Set[File]](initialFiles.to(Set)) - val availableFiles = availableFilesStream.startWithNone - // Request the files to display - selectionUpdates.onNext(AvailableFilesRequested) - div( - cls( - "inline-block transform overflow-hidden rounded-lg bg-white text-left align-bottom shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:align-middle" - ), - role("dialog"), - customHtmlAttr("aria.modal", BooleanAsTrueFalseStringCodec)(true), - aria.labelledBy("modal-headline"), - div( - cls("bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"), - div( - cls("sm:flex sm:items-start"), - div( - cls("mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"), - h3( - cls("text-lg font-medium leading-6 text-gray-900"), - idAttr("modal-headline"), - "Výběr souborů" - ) - ) - ), - child <-- availableFiles - .split(_ => ())((_, _, af) => FileTable(af, selectedFiles)) - .map(_.getOrElse(Loading)) - ), - div( - cls("bg-gray-50 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6"), - span( - cls("flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto"), - button( - typ("button"), - cls( - "focus:shadow-outline-green inline-flex w-full justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-base font-medium leading-6 text-white shadow-sm transition duration-150 ease-in-out hover:bg-indigo-500 focus:border-indigo-700 focus:outline-none sm:text-sm sm:leading-5" - ), - "Potvrdit", - composeEvents(onClick)( - _.sample(selectedFiles) - .map(SelectionUpdated(_)) - ) --> selectionUpdates - ) - ), - span( - cls("mt-3 flex w-full rounded-md shadow-sm sm:mt-0 sm:w-auto"), - button( - typ("button"), - cls( - "focus:shadow-outline-blue inline-flex w-full justify-center rounded-md border border-gray-300 bg-white px-4 py-2 text-base font-medium leading-6 text-gray-700 shadow-sm transition duration-150 ease-in-out hover:text-gray-500 focus:border-blue-300 focus:outline-none sm:text-sm sm:leading-5" - ), - "Zrušit", - onClick.mapTo(SelectionCancelled) --> selectionUpdates - ) - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala deleted file mode 100644 index aa11b2b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala +++ /dev/null @@ -1,91 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import works.iterative.ui.components.tailwind.Icons - -def FileTable( - files: Signal[List[File]], - selectedFiles: Var[Set[File]] -): HtmlElement = - val scope = customHtmlAttr("scope", StringAsIsCodec) - - def headerRow: HtmlElement = - val col = scope("col") - val baseM: Modifier[HtmlElement] = Seq(cls("px-6 py-3"), col) - val textH = cls( - "text-left text-xs font-medium text-gray-500 uppercase tracking-wider" - ) - tr( - th(baseM, span(cls("sr-only"), "Vybrat")), - th(baseM, textH, "Název"), - th(baseM, cls("relative"), span(cls("sr-only"), "Otevřít")) - ) - - def tableRow( - f: File, - idx: Int, - selected: Boolean - )(toggleSelection: Observer[Unit]): HtmlElement = - val baseC = cls("px-6 py-4 whitespace-nowrap text-sm") - tr( - cls(if idx % 2 == 0 then "bg-gray-50" else "bg-white"), - td( - cls("font-medium cursor-pointer"), - onClick.mapTo(()) --> toggleSelection, - cls(if selected then "text-green-900" else "text-gray-200"), - Icons.outline.`check-circle`().amend(svg.cls := "mx-auto"), - span(cls("sr-only"), if selected then "Vybráno" else "Nevybráno") - ), - td( - baseC, - cls("font-medium text-gray-900"), - f.name, - onClick.mapTo(()) --> toggleSelection - ), - td( - baseC, - cls("text-right font-medium"), - a( - href(f.url), - target("_blank"), - cls( - "flex items-center space-x-4 text-indigo-600 hover:text-indigo-900" - ), - Icons.outline.`external-link`(), - "Otevřít" - ) - ) - ) - - div( - cls("flex flex-col"), - div(cls("overflow-x-auto sm:-mx-6 lg:-mx-8")), - div( - cls("py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8"), - div( - cls("shadow overflow-hidden border-b border-gray-200 sm:rounded-lg"), - table( - cls("min-w-full divide-y divide-gray-200"), - thead( - cls("bg-gray-50"), - headerRow - ), - tbody( - children <-- files - .map(_.zipWithIndex) - .combineWithFn(selectedFiles)((f, sel) => - f.map((file, idx) => - val active = sel.contains(file) - tableRow(file, idx, active)( - selectedFiles.writer - .contramap(_ => if active then sel - file else sel + file) - ) - ) - ) - ) - ) - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala deleted file mode 100644 index 0fc8796..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala +++ /dev/null @@ -1,32 +0,0 @@ -package works.iterative.ui.components.tailwind - -import CustomAttrs.ariaHidden -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -// TODO: render icon or picture based on img signal -class Avatar($avatarImg: Signal[Option[String]]): - inline def avatarPlaceholder(size: Int): HtmlElement = - div( - cls := s"rounded-full text-indigo-200 bg-indigo-500 h-${size} w-${size} flex items-center justify-center", - Icons.outline.user(size - 2) - ) - - inline def avatarImage(size: Int): Signal[HtmlElement] = - $avatarImg.split(_ => ())((_, _, $url) => - img( - cls := s"w-$size h-$size rounded-full", - src <-- $url, - alt := "" - ) - ).map(_.getOrElse(avatarPlaceholder(size))) - - inline def avatar(size: Int): HtmlElement = - div( - cls := "relative", - child <-- avatarImage(size), - span( - cls := "absolute inset-0 shadow-inner rounded-full", - ariaHidden := true - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala deleted file mode 100644 index bcbed88..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala +++ /dev/null @@ -1,77 +0,0 @@ -package works.iterative.ui.components.tailwind - -enum ColorWeight(value: String): - inline def toCSS: String = value - // To be used for black and white until - // we support better mechanism via extension methods - case w__ extends ColorWeight("") - case w50 extends ColorWeight("50") - case w100 extends ColorWeight("100") - case w200 extends ColorWeight("200") - case w300 extends ColorWeight("300") - case w400 extends ColorWeight("400") - case w500 extends ColorWeight("500") - case w600 extends ColorWeight("600") - case w700 extends ColorWeight("700") - case w800 extends ColorWeight("800") - case w900 extends ColorWeight("900") - -enum Color(name: String): - import ColorWeight._ - - inline def toCSSNoColorWeight(prefix: String): String = - s"${prefix}-${name}" - inline def toCSSWithColorWeight( - prefix: String, - weight: ColorWeight - ): String = - s"${prefix}-${name}-${weight.toCSS}" - inline def toCSS(prefix: String)(weight: ColorWeight): String = - weight match { - case `w__` => toCSSNoColorWeight(prefix) - case _ => toCSSWithColorWeight(prefix, weight) - } - inline def bg: ColorWeight => String = toCSS("bg")(_) - inline def text: ColorWeight => String = toCSS("text")(_) - inline def decoration: ColorWeight => String = toCSS("decoration")(_) - inline def border: ColorWeight => String = toCSS("border")(_) - inline def outline: ColorWeight => String = toCSS("outline")(_) - inline def divide: ColorWeight => String = toCSS("divide")(_) - inline def ring: ColorWeight => String = toCSS("ring")(_) - inline def ringOffset: ColorWeight => String = toCSS("ring-offset")(_) - inline def shadow: ColorWeight => String = toCSS("shadow")(_) - inline def accent: ColorWeight => String = toCSS("accent")(_) - - // TODO: change the "stupid" methods to extension methods - // that will keep the invariants in comments lower - case current extends Color("current") - case inherit extends Color("inherit") - // Not present in for all methods - case transp extends Color("transparent") - // Seen in accent, not preset otherwise - case auto extends Color("auto") - // Black and white do not have weight - case black extends Color("black") - case white extends Color("white") - case slate extends Color("slate") - case gray extends Color("gray") - case zinc extends Color("zinc") - case neutral extends Color("neutral") - case stone extends Color("stone") - case red extends Color("red") - case orange extends Color("orange") - case amber extends Color("amber") - case yellow extends Color("yellow") - case lime extends Color("lime") - case green extends Color("green") - case emerald extends Color("emerald") - case teal extends Color("teal") - case cyan extends Color("cyan") - case sky extends Color("sky") - case blue extends Color("blue") - case indigo extends Color("indigo") - case violet extends Color("violet") - case purple extends Color("purple") - case fuchsia extends Color("fuchsia") - case pink extends Color("pink") - case rose extends Color("rose") diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala deleted file mode 100644 index 6fb453c..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -object CustomAttrs { - // Made a pull request to add aria-current to scala-dom-types, remove after - val ariaCurrent = customHtmlAttr("aria-current", StringAsIsCodec) - val ariaHidden = customHtmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - - val datetime = customHtmlAttr("datetime", StringAsIsCodec) - - object svg { - import com.raquo.laminar.api.L.svg.{*, given} - val ariaHidden = - customSvgAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - } -} diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala deleted file mode 100644 index 6cda48b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala +++ /dev/null @@ -1,28 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -object Display: - - enum Breakpoint: - case sm, md, lg, xl, `2xl` - - enum DisplayClass: - case block, `inline-block`, `inline`, flex, `inline-flex`, table, - `inline-table`, `table-caption` - - object ShowUpFrom: - inline def apply( - br: Breakpoint, - dc: DisplayClass = DisplayClass.block - ): HtmlElement = - div( - cls := "hidden", - cls := s"${br}:${dc}" - ) - - object HideUpTo: - inline def apply(br: Breakpoint): HtmlElement = - div( - cls := s"${br}:hidden" - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala deleted file mode 100644 index 4a8b065..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala +++ /dev/null @@ -1,270 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec -import com.raquo.domtypes.generic.defs.attrs.AriaAttrs -import com.raquo.laminar.api.L.svg.{*, given} -import com.raquo.laminar.builders.SvgBuilders -import com.raquo.laminar.keys.ReactiveSvgAttr - -// TODO: fix sizes, colors, hover and stuff, normalize and amend on call site -object Icons: - object aria: - inline def hidden = CustomAttrs.svg.ariaHidden - - object outline: - val defaultSize: Int = 6 - - inline def bell(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" - ) - ) - - inline def `check-circle`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" - ) - ) - - inline def `document-add`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M9 13h6m-3-3v6m5 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" - ) - ) - - inline def `external-link`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" - ) - ) - - inline def menu(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M4 6h16M4 12h16M4 18h16" - ) - ) - - inline def `status-offline`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M18.364 5.636a9 9 0 010 12.728m0 0l-2.829-2.829m2.829 2.829L21 21M15.536 8.464a5 5 0 010 7.072m0 0l-2.829-2.829m-4.243 2.829a4.978 4.978 0 01-1.414-2.83m-1.414 5.658a9 9 0 01-2.167-9.238m7.824 2.167a1 1 0 111.414 1.414m-1.414-1.414L3 3m8.293 8.293l1.414 1.414" - ) - ) - - inline def user(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" - ) - ) - - inline def x(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M6 18L18 6M6 6l12 12" - ) - ) - - end outline - - object solid: - val defaultSize: Int = 5 - - inline def users(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - d := "M9 6a3 3 0 11-6 0 3 3 0 016 0zM17 6a3 3 0 11-6 0 3 3 0 016 0zM12.93 17c.046-.327.07-.66.07-1a6.97 6.97 0 00-1.5-4.33A5 5 0 0119 16v1h-6.07zM6 11a5 5 0 015 5v1H1v-1a5 5 0 015-5z" - ) - ) - - inline def `location-marker`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M5.05 4.05a7 7 0 119.9 9.9L10 18.9l-4.95-4.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z", - clipRule := "evenodd" - ) - ) - - inline def calendar(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z", - clipRule := "evenodd" - ) - ) - - inline def `chevron-right`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z", - clipRule := "evenodd" - ) - ) - - inline def search(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z", - clipRule := "evenodd" - ) - ) - - inline def filter(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M3 3a1 1 0 011-1h12a1 1 0 011 1v3a1 1 0 01-.293.707L12 11.414V15a1 1 0 01-.293.707l-2 2A1 1 0 018 17v-5.586L3.293 6.707A1 1 0 013 6V3z", - clipRule := "evenodd" - ) - ) - - inline def `arrow-narrow-left`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M7.707 14.707a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 1.414L5.414 9H17a1 1 0 110 2H5.414l2.293 2.293a1 1 0 010 1.414z", - clipRule := "evenodd" - ) - ) - - inline def home(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - d := "M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z" - ) - ) - - inline def paperclip(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size} text-gray-400", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M8 4a3 3 0 00-3 3v4a5 5 0 0010 0V7a1 1 0 112 0v4a7 7 0 11-14 0V7a5 5 0 0110 0v4a3 3 0 11-6 0V7a1 1 0 012 0v4a1 1 0 102 0V7a3 3 0 00-3-3z", - clipRule := "evenodd" - ) - ) - - end solid -end Icons diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/LinkSupport.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/LinkSupport.scala deleted file mode 100644 index 88f0b9a..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/LinkSupport.scala +++ /dev/null @@ -1,12 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.jsdom.defs.events.TypedTargetMouseEvent - -object LinkSupport: - - extension [El <: org.scalajs.dom.EventTarget]( - ep: EventProcessor[TypedTargetMouseEvent[El], TypedTargetMouseEvent[El]] - ) - def noKeyMod = - ep.filter(ev => !(ev.ctrlKey || ev.metaKey || ev.shiftKey || ev.altKey)) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Loader.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Loader.scala deleted file mode 100644 index 5399933..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Loader.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -// TODO: proper loader -def Loading = - div( - cls := "bg-gray-50 overflow-hidden rounded-lg", - div( - cls := "px-4 py-5 sm:p-6", - "Loading..." - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Renderable.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Renderable.scala deleted file mode 100644 index a0fcc5b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Renderable.scala +++ /dev/null @@ -1,6 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -trait Renderable[A]: - extension (a: A) def toHtml: HtmlElement diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/ComboBox.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/ComboBox.scala deleted file mode 100644 index 8c5b8a1..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/ComboBox.scala +++ /dev/null @@ -1,92 +0,0 @@ -package works.iterative.ui.components.tailwind -package form - -import com.raquo.laminar.api.L.{*, given} -import io.laminext.syntax.core.* - -case class ComboBox( - id: String, - options: Signal[List[ComboBox.Option]], - valueUpdates: Observer[List[String]] -) - -object ComboBox: - - extension (m: ComboBox) - def toHtml: HtmlElement = - val isOpen = Var(false) - div( - cls := "relative mt-1", - input( - idAttr := m.id, - tpe := "text", - cls := "w-full rounded-md border border-gray-300 bg-white py-2 pl-3 pr-12 shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 sm:text-sm", - role := "combobox", - aria.controls := "options", - aria.expanded := false, - onClick.mapTo(true) --> isOpen.writer - ), - button( - tpe := "button", - cls := "absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-none", { - import svg.* - svg( - cls := "h-5 w-5 text-gray-400", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - CustomAttrs.svg.ariaHidden := true, - path( - fillRule := "evenodd", - d := "M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z", - clipRule := "evenodd" - ) - ) - } - ), - ul( - cls <-- isOpen.signal.switch("", "hidden"), - cls := "absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm", - idAttr := "options", - role := "listbox", - children <-- m.options.map(_.map(_.toHtml)) - ) - ) - - case class Option(value: String, active: Boolean) - - object Option: - extension (m: Option) - def toHtml: HtmlElement = - li( - cls := "relative cursor-default select-none py-2 pl-8 pr-4", - cls := (if m.active then "text-white bg-indigo-600" - else "text-gray-900"), - idAttr := "option-0", - role := "option", - tabIndex := -1, - span( - cls := "block truncate", - m.value - ), - if m.active then - span( - cls := "absolute inset-y-0 left-0 flex items-center pl-1.5", - cls := (if m.active then "text-white" else "text-indigo-600"), { - import svg.* - svg( - cls := "h-5 w-5", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - CustomAttrs.svg.ariaHidden := true, - path( - fillRule := "evenodd", - d := "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", - clipRule := "evenodd" - ) - ) - } - ) - else emptyNode - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/Form.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/Form.scala deleted file mode 100644 index 1b41dee..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/Form.scala +++ /dev/null @@ -1,17 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} -import org.scalajs.dom -import com.raquo.laminar.nodes.ReactiveHtmlElement - -object Form: - val Body = FormBody - val Section = FormSection - val Row = FormRow - - def apply(body: HtmlElement, buttons: HtmlElement): HtmlElement = - form( - cls := "space-y-8 divide-y divide-gray-200", - body, - buttons - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormBody.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormBody.scala deleted file mode 100644 index 89abc9c..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormBody.scala +++ /dev/null @@ -1,10 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} - -object FormBody: - def apply(sections: HtmlElement*): HtmlElement = - div( - cls := "space-y-8 divide-y divide-gray-200 sm:space-y-5", - sections - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormFields.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormFields.scala deleted file mode 100644 index a2e4cc6..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormFields.scala +++ /dev/null @@ -1,14 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.laminar.nodes.ReactiveHtmlElement -import org.scalajs.dom - -object FormFields: - def apply( - mods: Modifier[ReactiveHtmlElement[dom.HTMLElement]]* - ): HtmlElement = - div( - cls := "mt-6 sm:mt-5 space-y-6 sm:space-y-5", - mods - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormHeader.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormHeader.scala deleted file mode 100644 index 4083841..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormHeader.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} - -object FormHeader: - case class ViewModel(header: String, description: String) - def apply(m: ViewModel): HtmlElement = - div( - h3(cls := "text-lg leading-6 font-medium text-gray-900", m.header), - p(cls := "mt-1 max-w-2xl text-sm text-gray-500", m.description) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormRow.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormRow.scala deleted file mode 100644 index ffc5807..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormRow.scala +++ /dev/null @@ -1,22 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} - -case class FormRow(id: String, label: String, content: Modifier[Div]) - -object FormRow: - - extension (m: FormRow) - def toHtml: HtmlElement = - div( - cls := "sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5", - label( - forId := m.id, - cls := "block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2", - m.label - ), - div( - cls := "mt-1 sm:mt-0 sm:col-span-2", - m.content - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormSection.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormSection.scala deleted file mode 100644 index 50811ee..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormSection.scala +++ /dev/null @@ -1,15 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.laminar.nodes.ReactiveHtmlElement - -object FormSection: - def apply( - header: HtmlElement, - rows: HtmlElement* - ): HtmlElement = - div( - cls := "space-y-6 sm:space-y-5", - header, - rows - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala deleted file mode 100644 index e7e6627..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.akka - -// Base class for all command-processing related exceptions from handlers -sealed abstract class CommandHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandNotAvailable[C, S](cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd není dostupný ve stavu $state" - ) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandRejected[C, S](reason: String, cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd byl ve stavu $state odmítnut s odůvodněním $reason" - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala deleted file mode 100644 index 72a5bf9..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.akka - -sealed abstract class EventHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -case class UnhandledEvent[Event, State](event: Event, state: State) - extends EventHandlerException( - s"Událost $event nastala ve stavu $state bez možnosti zpracování" - ) diff --git a/iw/bom.sc b/iw/bom.sc deleted file mode 100644 index e9266b8..0000000 --- a/iw/bom.sc +++ /dev/null @@ -1,227 +0,0 @@ -import mill._, scalalib._ - -object IWMaterials { - - object Versions { - val akka = "2.6.16" - val akkaHttp = "10.2.4" - val cats = "2.7.0" - val elastic4s = "7.12.2" - val http4s = "0.23.10" - val http4sPac4J = "4.0.0" - val laminar = "0.14.2" - val laminext = laminar - val logbackClassic = "1.2.10" - val pac4j = "5.2.0" - val play = "2.8.8" - val playJson = "2.9.2" - val scalaTest = "3.2.9" - val slick = "3.3.3" - val sttpClient = "3.5.0" - val tapir = "0.20.1" - val urlDsl = "0.4.0" - val waypoint = "0.5.0" - val zio = "2.0.0-RC2" - val zioConfig = "3.0.0-RC2" - val zioInteropCats = "3.3.0-RC2" - val zioJson = "0.3.0-RC3" - val zioLogging = "2.0.0-RC5" - val zioPrelude = "1.0.0-RC10" - val zioZMX = "0.0.11" - } - - object Deps extends AkkaLibs with SlickLibs { - import IWMaterials.{Versions => V} - - val zioOrg = "dev.zio" - - def zioLib(name: String, version: String): Dep = - ivy"$zioOrg::zio-$name::$version" - - lazy val zio: Dep = ivy"$zioOrg::zio:${V.zio}" - - lazy val zioTest: Dep = zioLib("test", V.zio) - lazy val zioTestSbt: Dep = zioLib("test", V.zio) - - lazy val zioConfig: Dep = zioLib("config", V.zioConfig) - lazy val zioConfigTypesafe: Dep = - zioLib("config-typesafe", V.zioConfig) - lazy val zioConfigMagnolia: Dep = - zioLib("config-magnolia", V.zioConfig) - - lazy val zioJson: Dep = zioLib("json", V.zioJson) - lazy val zioLogging: Dep = zioLib("logging", V.zioLogging) - lazy val zioLoggingSlf4j: Dep = - zioLib("logging-slf4j", V.zioLogging) - lazy val zioPrelude: Dep = zioLib("prelude", V.zioPrelude) - lazy val zioStreams: Dep = zioLib("streams", V.zio) - lazy val zioZMX: Dep = zioLib("zmx", V.zioZMX) - lazy val zioInteropCats: Dep = - zioLib("interop-cats", V.zioInteropCats) - - /* What is the equivalent? ZIOModule with prepared test config? - def useZIO(testConf: Configuration*): Agg[Dep] = Agg( - zio, - zioTest, - zioTestSbt, - testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework") - ) - */ - - /* - def useZIOAll(testConf: Configuration*): Seq[Def.Setting[_]] = - useZIO(testConf: _*) ++ Seq( - zioStreams, - zioConfig, - zioConfigTypesafe, - zioConfigMagnolia, - zioJson, - zioZMX, - zioLogging, - zioPrelude - ) - */ - - private val tapirOrg = "com.softwaremill.sttp.tapir" - def tapirLib(name: String): Dep = - ivy"${tapirOrg}::tapir-$name::${V.tapir}" - - lazy val tapirCore: Dep = tapirLib("core") - lazy val tapirZIO: Dep = tapirLib("zio") - lazy val tapirZIOJson: Dep = tapirLib("json-zio") - lazy val tapirSttpClient: Dep = tapirLib("sttp-client") - lazy val tapirCats: Dep = tapirLib("cats") - lazy val tapirZIOHttp4sServer: Dep = tapirLib("zio-http4s-server") - - private val sttpClientOrg = "com.softwaremill.sttp.client3" - def sttpClientLib(name: String): Dep = - ivy"${sttpClientOrg}::${name}:${V.sttpClient}" - - lazy val sttpClientCore: Dep = sttpClientLib("core") - - lazy val http4sBlazeServer: Dep = - ivy"org.http4s::http4s-blaze-server:${V.http4s}" - - lazy val http4sPac4J: Dep = - ivy"org.pac4j::http4s-pac4j:${V.http4sPac4J}" - lazy val pac4jOIDC: Dep = - ivy"org.pac4j:pac4j-oidc:${V.pac4j}" - - lazy val scalaTest: Dep = - ivy"org.scalatest::scalatest:${V.scalaTest}" - lazy val scalaTestPlusScalacheck: Dep = - ivy"org.scalatestplus::scalacheck-1-15:3.2.9.0" - lazy val playScalaTest: Dep = - ivy"org.scalatestplus.play::scalatestplus-play:5.1.0" - - private val playOrg = "com.typesafe.play" - lazy val playMailer: Dep = ivy"${playOrg}::play-mailer:8.0.1" - lazy val playServer: Dep = ivy"${playOrg}::play-server:${V.play}" - lazy val playAkkaServer: Dep = - ivy"${playOrg}::play-akka-http-server:${V.play}" - lazy val play: Dep = ivy"${playOrg}::play:${V.play}" - lazy val playAhcWs: Dep = ivy"${playOrg}::play-ahc-ws:${V.play}" - lazy val playJson: Dep = ivy"${playOrg}::play-json:${V.playJson}" - - private val elastic4sOrg = "com.sksamuel.elastic4s" - lazy val useElastic4S: Agg[Dep] = Agg( - ivy"${elastic4sOrg}::elastic4s-core:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-client-akka:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-http-streams:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-json-play:${V.elastic4s}" - ) - - lazy val laminar: Dep = ivy"com.raquo::laminar::${V.laminar}" - - private def laminext(name: String): Dep = - ivy"io.laminext::$name::${V.laminar}" - - lazy val laminextCore: Dep = laminext("core") - lazy val laminextTailwind: Dep = laminext("tailwind") - lazy val laminextFetch: Dep = laminext("fetch") - lazy val laminextValidationCore: Dep = laminext("validation-core") - lazy val laminextUI: Dep = laminext("ui") - - lazy val waypoint: Dep = - ivy"com.raquo::waypoint::${V.waypoint}" - - lazy val urlDsl: Dep = - ivy"be.doeraene::url-dsl::${V.urlDsl}" - - lazy val scalaJavaTime: Dep = - ivy"io.github.cquiroz::scala-java-time::2.3.0" - - lazy val scalaJavaLocales: Dep = - ivy"io.github.cquiroz::scala-java-locales::1.2.1" - - lazy val logbackClassic: Dep = - ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" - } - - trait AkkaLibs { - - self: SlickLibs => - - object akka { - val V = Versions.akka - val tOrg = "com.typesafe.akka" - val lOrg = "com.lightbend.akka" - - def akkaMod(name: String): Dep = - ivy"$tOrg::akka-$name:$V" - - lazy val actor: Dep = akkaMod("actor") - lazy val actorTyped: Dep = akkaMod("actor-typed") - lazy val stream: Dep = akkaMod("stream") - lazy val persistence: Dep = akkaMod("persistence-typed") - lazy val persistenceQuery: Dep = akkaMod("persistence-query") - lazy val persistenceJdbc: Dep = - ivy"$lOrg::akka-persistence-jdbc:5.0.4" - val persistenceTestKit: Dep = ivy"$tOrg::akka-persistence-testkit:$V" - - object http { - val V = Versions.akkaHttp - - lazy val http: Dep = ivy"$tOrg::akka-http:$V" - lazy val sprayJson: Dep = ivy"$tOrg::akka-http-spray-json:$V" - - } - - object projection { - val V = "1.2.2" - - lazy val core: Dep = - ivy"$lOrg::akka-projection-core:$V" - lazy val eventsourced: Dep = - ivy"$lOrg::akka-projection-eventsourced:$V" - lazy val slick: Dep = - ivy"$lOrg::akka-projection-slick:$V" - lazy val jdbc: Dep = - ivy"$lOrg::akka-projection-jdbc:$V" - } - - object profiles { - // TODO: deal with cross-version for Scala3 (for3Use2_13) - lazy val eventsourcedJdbcProjection: Agg[Dep] = Agg( - persistenceQuery, - projection.core, - projection.eventsourced, - projection.slick, - persistenceJdbc - ) ++ slick.default - } - } - } - - trait SlickLibs { - object slick { - val V = IWMaterials.Versions.slick - val org = "com.typesafe.slick" - - lazy val slick: Dep = ivy"$org::slick:$V" - lazy val hikaricp: Dep = ivy"$org::slick-hikaricp:$V" - lazy val default: Agg[Dep] = Agg(slick, hikaricp) - } - } - -} diff --git a/iw/build.sc b/iw/build.sc deleted file mode 100644 index fbbc3b8..0000000 --- a/iw/build.sc +++ /dev/null @@ -1,144 +0,0 @@ -import mill._, scalalib._, scalajslib._ - -import $file.bom - -object support { - val Deps = bom.IWMaterials.Deps - val Versions = bom.IWMaterials.Versions - - trait CommonModule extends ScalaModule { - def scalaVersion = "3.1.1" - def scalacOptions = Seq( - "-encoding", - "utf8", - "-deprecation", - "-explain-types", - "-explain", - "-feature", - "-language:experimental.macros", - "-language:higherKinds", - "-language:implicitConversions", - "-unchecked", - "-Xfatal-warnings", - "-Ykind-projector" - ) - } - - trait CommonJSModule extends CommonModule with ScalaJSModule { - def scalaJSVersion = "1.8.0" - } - - trait CrossPlatformModule extends Module { outer => - def ivyDeps: T[Agg[Dep]] = Agg[Dep]() - def jsDeps: T[Agg[Dep]] = Agg[Dep]() - def jvmDeps: T[Agg[Dep]] = Agg[Dep]() - def moduleDeps: Seq[Module] = Seq[Module]() - - trait PlatformModule extends CommonModule { - def platform: String - override def millSourcePath = outer.millSourcePath - override def ivyDeps = outer.ivyDeps() ++ (platform match { - case "js" => jsDeps() - case _ => jvmDeps() - }) - override def moduleDeps = outer.moduleDeps.collect { - case m: CrossPlatformModule => - platform match { - case "js" => m.js - case _ => m.jvm - } - case m: JavaModule => m - } - } - - trait JsModule extends PlatformModule with CommonJSModule { - def platform = "js" - } - - trait JvmModule extends PlatformModule { - def platform = "jvm" - } - - val js: JsModule - val jvm: JvmModule - } - - trait PureCrossModule extends CrossPlatformModule { - override object js extends JsModule - override object jvm extends JvmModule - } - - trait PureCrossSbtModule extends CrossPlatformModule { - override object js extends JsModule with SbtModule - override object jvm extends JvmModule with SbtModule - } - - trait FullCrossSbtModule extends CrossPlatformModule { - trait FullSources extends JavaModule { self: PlatformModule => - override def sources = T.sources( - millSourcePath / platform / "src" / "main" / "scala", - millSourcePath / "shared" / "src" / "main" / "scala" - ) - - override def resources = T.sources( - millSourcePath / platform / "src" / "main" / "resources", - millSourcePath / "shared" / "src" / "main" / "resources" - ) - } - - override object js extends JsModule with FullSources - override object jvm extends JvmModule with FullSources - } - -} - -import support._ - -object mongo extends CommonModule { - def ivyDeps = Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - ivy"org.mongodb.scala::mongo-scala-driver:4.2.3".withDottyCompat( - scalaVersion() - ) - ) -} - -object tapir extends FullCrossSbtModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson, Deps.zioJson) - def jvmDeps = Agg(Deps.tapirZIO, Deps.tapirZIOHttp4sServer) - def jsDeps = Agg(Deps.tapirSttpClient) -} - -object akkaPersistence extends CommonModule { - def millSourcePath = build.millSourcePath / "akka-persistence" - def ivyDeps = (Agg( - Deps.akka.persistenceQuery, - Deps.akka.persistenceJdbc, - Deps.akka.projection.core, - Deps.akka.projection.eventsourced, - Deps.akka.projection.slick - ) ++ Deps.slick.default).map(_.withDottyCompat(scalaVersion())) ++ Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - Deps.akka.persistence.withDottyCompat(scalaVersion()), - ivy"com.typesafe.akka::akka-cluster-sharding-typed:${Deps.akka.V}" - .withDottyCompat(scalaVersion()) - ) -} - -object ui extends CommonJSModule { - def ivyDeps = Agg( - Deps.zio, - Deps.laminar, - Deps.zioJson, - Deps.waypoint, - Deps.urlDsl, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) -} diff --git a/iw/domain.sc b/iw/domain.sc deleted file mode 100644 index b09c56b..0000000 --- a/iw/domain.sc +++ /dev/null @@ -1,78 +0,0 @@ -import mill._, scalalib._ - -import $file.{build => ff}, ff.support._ - -trait DomainModule extends Module { - // General extra model deps - def modelModules: Seq[Module] = Seq.empty - // General extra codecs deps - def codecsModules: Seq[Module] = Seq.empty - // General extra endpoints deps - def endpointsModules: Seq[Module] = Seq.empty - - // Implementation deps for repo - def repoModules: Seq[JavaModule] = Seq.empty - - object shared extends Module { - object model extends PureCrossModule { - def moduleDeps = modelModules - def ivyDeps = Agg(Deps.zioPrelude) - } - object codecs extends PureCrossModule { - def moduleDeps = Seq(model) ++ codecsModules - def ivyDeps = Agg(Deps.zioJson) - } - } - - trait CommonProjects extends Module { - object model extends PureCrossModule { - def ivyDeps = Agg(Deps.zioPrelude) - def moduleDeps = Seq(shared.model) - } - object codecs extends PureCrossModule { - def moduleDeps = - Seq(model, shared.model, shared.codecs, ff.tapir) - } - object endpoints extends PureCrossModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson) - def moduleDeps = Seq(model, codecs) ++ endpointsModules - } - object client extends CommonJSModule { - def moduleDeps = Seq(endpoints.js) - } - object components extends CommonJSModule { - def ivyDeps = Agg( - Deps.laminar, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) - def moduleDeps = Seq(model.js, codecs.js, client, ff.ui) - } - } - - object query extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(repo, query.endpoints.jvm) - } - object repo extends CommonModule { - def ivyDeps = Agg(Deps.zio) - def moduleDeps = Seq(model.jvm, codecs.jvm) ++ repoModules - } - object projection extends CommonModule { - def moduleDeps = Seq(repo, ff.akkaPersistence) - } - } - - object command extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(entity, command.endpoints.jvm) - } - object entity extends CommonModule { - def moduleDeps = Seq(model.jvm, codecs.jvm, ff.akkaPersistence) - } - } -} diff --git a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala b/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala deleted file mode 100644 index 0fa3493..0000000 --- a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala +++ /dev/null @@ -1,53 +0,0 @@ -package works.iterative.mongo - -import zio.* -import zio.json.* -import zio.config.* -import org.mongodb.scala.* -import org.mongodb.scala.model.Filters.* -import org.bson.json.JsonObject -import org.mongodb.scala.model.ReplaceOptions -import org.mongodb.scala.bson.conversions.Bson - -case class MongoConfig(uri: String) - -object MongoConfig: - val configDesc = - import ConfigDescriptor.* - nested("MONGO")(string("URI").default("mongodb://localhost:27017")) - .to[MongoConfig] - val fromEnv = ZConfig.fromSystemEnv(configDesc) - -extension (m: MongoClient.type) - def layer: RLayer[MongoConfig, MongoClient] = - ZIO - .serviceWithZIO[MongoConfig](c => Task.attempt(MongoClient(c.uri))) - .toLayer - -class MongoJsonRepository[Elem, Key, Criteria]( - collection: MongoCollection[JsonObject], - toFilter: Criteria => Bson, - idFilter: Elem => (String, Key) -)(using JsonCodec[Elem]) { - def matching(criteria: Criteria): Task[Seq[Elem]] = - val filter = toFilter(criteria) - val query = collection.find(filter) - - for - result <- ZIO.fromFuture(_ => query.toFuture) - proof <- ZIO.collect(result)(j => - ZIO.fromOption(j.getJson.fromJson[Elem].toOption) - ) - yield proof - - def put(elem: Elem): Task[Unit] = - Task.async(cb => - collection - .replaceOne( - equal.tupled(idFilter(elem)), - JsonObject(elem.toJson), - ReplaceOptions().upsert(true) - ) - .subscribe(_ => cb(Task.unit), t => cb(Task.fail(t))) - ) -} diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala deleted file mode 100644 index 68d7196..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala +++ /dev/null @@ -1,12 +0,0 @@ -package works.iterative.tapir - -import sttp.model.Uri - -opaque type BaseUri = Option[Uri] - -object BaseUri: - - def apply(optU: Option[Uri]): BaseUri = optU - def apply(u: Uri): BaseUri = Some(u) - - extension (v: BaseUri) def toUri: Option[Uri] = v diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 653bc51..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,22 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.client.sttp.SttpClientInterpreter -import sttp.tapir.PublicEndpoint -import sttp.tapir.client.sttp.WebSocketToPipe -import scala.concurrent.Future -import sttp.client3.SttpBackend -import sttp.capabilities.WebSockets - -trait CustomTapirPlatformSpecific extends SttpClientInterpreter: - self: CustomTapir => - - type Backend = SttpBackend[Future, WebSockets] - - def makeClient[I, E, O]( - endpoint: PublicEndpoint[I, E, O, Any] - )(using - baseUri: BaseUri, - backend: Backend, - wsToPipe: WebSocketToPipe[Any] - ): I => Future[O] = - toClientThrowErrors(endpoint, baseUri.toUri, backend) diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 2545fbd..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,5 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.ztapir.ZTapir - -trait CustomTapirPlatformSpecific extends ZTapir diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala deleted file mode 100644 index 55e4ca1..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala +++ /dev/null @@ -1,7 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.server.http4s.ztapir.ZHttp4sServerInterpreter - -trait Http4sCustomTapir[Env] - extends CustomTapir - with ZHttp4sServerInterpreter[Env] diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala deleted file mode 100644 index dd52e9b..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala +++ /dev/null @@ -1,14 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.Tapir -import sttp.tapir.json.zio.TapirJsonZio -import sttp.tapir.TapirAliases - -trait CustomTapir - extends Tapir - with TapirJsonZio - with TapirAliases - with CustomTapirPlatformSpecific: - given Schema[ServerError] = Schema.derived - -object CustomTapir extends CustomTapir diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala deleted file mode 100644 index 715591d..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.tapir - -import zio.json.* - -sealed trait ServerError -case class InternalServerError(msg: String) extends ServerError -object InternalServerError: - def fromThrowable(t: Throwable): ServerError = InternalServerError( - t.getMessage - ) - -object ServerError: - given JsonCodec[ServerError] = DeriveJsonCodec.gen diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/File.scala deleted file mode 100644 index 09f6c07..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala +++ /dev/null @@ -1,3 +0,0 @@ -package works.iterative.services.files - -case class File(url: String, name: String) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala deleted file mode 100644 index 46633a5..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala +++ /dev/null @@ -1,25 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.Renderable - -given Renderable[File] with - extension (m: File) - def toHtml: HtmlElement = - li( - cls("pl-3 pr-4 py-3 flex items-center justify-between text-sm"), - div( - cls("w-0 flex-1 flex items-center"), - Icons.solid - .paperclip() - .amend(svg.cls := "flex-shrink-0 text-gray-400"), - span(cls("ml-2 flex-1 w-0 truncate"), m.name) - ), - a( - href(m.url), - cls("font-medium text-indigo-600 hover:text-indigo-500"), - "Otevřít" - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala deleted file mode 100644 index ac59db8..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import io.laminext.syntax.core.{*, given} - -def FileList(files: List[File]): HtmlElement = - ul( - role("list"), - cls("border border-gray-200 rounded-md divide-y divide-gray-200"), - files.map(_.toHtml) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala deleted file mode 100644 index 2e8f690..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala +++ /dev/null @@ -1,81 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import io.laminext.syntax.core.{*, given} - -object FilePicker: - sealed trait Event - sealed trait DoneEvent extends Event - case class SelectionUpdated(files: Set[File]) extends DoneEvent - case object SelectionCancelled extends DoneEvent - case object AvailableFilesRequested extends Event - - def apply( - currentFiles: Signal[List[File]], - availableFilesStream: EventStream[List[File]] - )(selectionUpdates: Observer[Event]): HtmlElement = - val (updatesStream, updatesObserver) = EventStream.withObserver[Event] - val selectorOpen = Var[Boolean](false) - - // This sequence tricks browser into displaying modal content centered - // Inspired by modal in headless ui playground - // https://github.com/tailwindlabs/headlessui/blob/fdd26297953080d5ec905dda0bf5ec9607897d86/packages/playground-react/pages/transitions/component-examples/modal.tsx#L78-L79 - inline def browserCenteringModalTrick: Modifier[HtmlElement] = - Seq[Modifier[HtmlElement]]( - span(cls("hidden sm:inline-block sm:h-screen sm:align-middle")), - "​" // Zero width space - ) - - inline def overlay: Modifier[HtmlElement] = - // Page overlay - /* TODO: transition - enter="ease-out duration-300" - enterFrom="opacity-0" - enterTo="opacity-100" - leave="ease-in duration-200" - leaveFrom="opacity-100" - leaveTo="opacity-0" - */ - div( - div( - onClick.mapTo(false) --> selectorOpen.writer, - cls("fixed inset-0 transition-opacity"), - div(cls("absolute inset-0 bg-gray-500 opacity-75")) - ) - ) - - inline def modalSelector: HtmlElement = - div( - cls("fixed inset-0 z-10 overflow-y-auto"), - div( - cls("text-center sm:block sm:p-0"), - overlay, - browserCenteringModalTrick, - child <-- currentFiles.map( - FileSelector(_, availableFilesStream)(updatesObserver) - ) - ) - ) - - div( - updatesStream --> selectionUpdates, - updatesStream.collect { case _: DoneEvent => - false - } --> selectorOpen.writer, - child.maybe <-- selectorOpen.signal.map(isOpen => - if isOpen then Some(modalSelector) else None - ), - div( - cls("flex flex-col space-y-5"), - child.maybe <-- currentFiles.map(files => - if files.isEmpty then None else Some(FileList(files)) - ), - button( - tpe := "button", - cls := "bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500", - "Zvolit soubory", - onClick.mapTo(true) --> selectorOpen.writer - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala deleted file mode 100644 index b43e07a..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala +++ /dev/null @@ -1,74 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.Loading -import io.laminext.syntax.core.{*, given} -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -object FileSelector: - import FilePicker._ - - def apply( - initialFiles: List[File], - availableFilesStream: EventStream[List[File]] - )(selectionUpdates: Observer[Event]): HtmlElement = - val selectedFiles = Var[Set[File]](initialFiles.to(Set)) - val availableFiles = availableFilesStream.startWithNone - // Request the files to display - selectionUpdates.onNext(AvailableFilesRequested) - div( - cls( - "inline-block transform overflow-hidden rounded-lg bg-white text-left align-bottom shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:align-middle" - ), - role("dialog"), - customHtmlAttr("aria.modal", BooleanAsTrueFalseStringCodec)(true), - aria.labelledBy("modal-headline"), - div( - cls("bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"), - div( - cls("sm:flex sm:items-start"), - div( - cls("mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"), - h3( - cls("text-lg font-medium leading-6 text-gray-900"), - idAttr("modal-headline"), - "Výběr souborů" - ) - ) - ), - child <-- availableFiles - .split(_ => ())((_, _, af) => FileTable(af, selectedFiles)) - .map(_.getOrElse(Loading)) - ), - div( - cls("bg-gray-50 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6"), - span( - cls("flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto"), - button( - typ("button"), - cls( - "focus:shadow-outline-green inline-flex w-full justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-base font-medium leading-6 text-white shadow-sm transition duration-150 ease-in-out hover:bg-indigo-500 focus:border-indigo-700 focus:outline-none sm:text-sm sm:leading-5" - ), - "Potvrdit", - composeEvents(onClick)( - _.sample(selectedFiles) - .map(SelectionUpdated(_)) - ) --> selectionUpdates - ) - ), - span( - cls("mt-3 flex w-full rounded-md shadow-sm sm:mt-0 sm:w-auto"), - button( - typ("button"), - cls( - "focus:shadow-outline-blue inline-flex w-full justify-center rounded-md border border-gray-300 bg-white px-4 py-2 text-base font-medium leading-6 text-gray-700 shadow-sm transition duration-150 ease-in-out hover:text-gray-500 focus:border-blue-300 focus:outline-none sm:text-sm sm:leading-5" - ), - "Zrušit", - onClick.mapTo(SelectionCancelled) --> selectionUpdates - ) - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala deleted file mode 100644 index aa11b2b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala +++ /dev/null @@ -1,91 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import works.iterative.ui.components.tailwind.Icons - -def FileTable( - files: Signal[List[File]], - selectedFiles: Var[Set[File]] -): HtmlElement = - val scope = customHtmlAttr("scope", StringAsIsCodec) - - def headerRow: HtmlElement = - val col = scope("col") - val baseM: Modifier[HtmlElement] = Seq(cls("px-6 py-3"), col) - val textH = cls( - "text-left text-xs font-medium text-gray-500 uppercase tracking-wider" - ) - tr( - th(baseM, span(cls("sr-only"), "Vybrat")), - th(baseM, textH, "Název"), - th(baseM, cls("relative"), span(cls("sr-only"), "Otevřít")) - ) - - def tableRow( - f: File, - idx: Int, - selected: Boolean - )(toggleSelection: Observer[Unit]): HtmlElement = - val baseC = cls("px-6 py-4 whitespace-nowrap text-sm") - tr( - cls(if idx % 2 == 0 then "bg-gray-50" else "bg-white"), - td( - cls("font-medium cursor-pointer"), - onClick.mapTo(()) --> toggleSelection, - cls(if selected then "text-green-900" else "text-gray-200"), - Icons.outline.`check-circle`().amend(svg.cls := "mx-auto"), - span(cls("sr-only"), if selected then "Vybráno" else "Nevybráno") - ), - td( - baseC, - cls("font-medium text-gray-900"), - f.name, - onClick.mapTo(()) --> toggleSelection - ), - td( - baseC, - cls("text-right font-medium"), - a( - href(f.url), - target("_blank"), - cls( - "flex items-center space-x-4 text-indigo-600 hover:text-indigo-900" - ), - Icons.outline.`external-link`(), - "Otevřít" - ) - ) - ) - - div( - cls("flex flex-col"), - div(cls("overflow-x-auto sm:-mx-6 lg:-mx-8")), - div( - cls("py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8"), - div( - cls("shadow overflow-hidden border-b border-gray-200 sm:rounded-lg"), - table( - cls("min-w-full divide-y divide-gray-200"), - thead( - cls("bg-gray-50"), - headerRow - ), - tbody( - children <-- files - .map(_.zipWithIndex) - .combineWithFn(selectedFiles)((f, sel) => - f.map((file, idx) => - val active = sel.contains(file) - tableRow(file, idx, active)( - selectedFiles.writer - .contramap(_ => if active then sel - file else sel + file) - ) - ) - ) - ) - ) - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala deleted file mode 100644 index 0fc8796..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala +++ /dev/null @@ -1,32 +0,0 @@ -package works.iterative.ui.components.tailwind - -import CustomAttrs.ariaHidden -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -// TODO: render icon or picture based on img signal -class Avatar($avatarImg: Signal[Option[String]]): - inline def avatarPlaceholder(size: Int): HtmlElement = - div( - cls := s"rounded-full text-indigo-200 bg-indigo-500 h-${size} w-${size} flex items-center justify-center", - Icons.outline.user(size - 2) - ) - - inline def avatarImage(size: Int): Signal[HtmlElement] = - $avatarImg.split(_ => ())((_, _, $url) => - img( - cls := s"w-$size h-$size rounded-full", - src <-- $url, - alt := "" - ) - ).map(_.getOrElse(avatarPlaceholder(size))) - - inline def avatar(size: Int): HtmlElement = - div( - cls := "relative", - child <-- avatarImage(size), - span( - cls := "absolute inset-0 shadow-inner rounded-full", - ariaHidden := true - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala deleted file mode 100644 index bcbed88..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala +++ /dev/null @@ -1,77 +0,0 @@ -package works.iterative.ui.components.tailwind - -enum ColorWeight(value: String): - inline def toCSS: String = value - // To be used for black and white until - // we support better mechanism via extension methods - case w__ extends ColorWeight("") - case w50 extends ColorWeight("50") - case w100 extends ColorWeight("100") - case w200 extends ColorWeight("200") - case w300 extends ColorWeight("300") - case w400 extends ColorWeight("400") - case w500 extends ColorWeight("500") - case w600 extends ColorWeight("600") - case w700 extends ColorWeight("700") - case w800 extends ColorWeight("800") - case w900 extends ColorWeight("900") - -enum Color(name: String): - import ColorWeight._ - - inline def toCSSNoColorWeight(prefix: String): String = - s"${prefix}-${name}" - inline def toCSSWithColorWeight( - prefix: String, - weight: ColorWeight - ): String = - s"${prefix}-${name}-${weight.toCSS}" - inline def toCSS(prefix: String)(weight: ColorWeight): String = - weight match { - case `w__` => toCSSNoColorWeight(prefix) - case _ => toCSSWithColorWeight(prefix, weight) - } - inline def bg: ColorWeight => String = toCSS("bg")(_) - inline def text: ColorWeight => String = toCSS("text")(_) - inline def decoration: ColorWeight => String = toCSS("decoration")(_) - inline def border: ColorWeight => String = toCSS("border")(_) - inline def outline: ColorWeight => String = toCSS("outline")(_) - inline def divide: ColorWeight => String = toCSS("divide")(_) - inline def ring: ColorWeight => String = toCSS("ring")(_) - inline def ringOffset: ColorWeight => String = toCSS("ring-offset")(_) - inline def shadow: ColorWeight => String = toCSS("shadow")(_) - inline def accent: ColorWeight => String = toCSS("accent")(_) - - // TODO: change the "stupid" methods to extension methods - // that will keep the invariants in comments lower - case current extends Color("current") - case inherit extends Color("inherit") - // Not present in for all methods - case transp extends Color("transparent") - // Seen in accent, not preset otherwise - case auto extends Color("auto") - // Black and white do not have weight - case black extends Color("black") - case white extends Color("white") - case slate extends Color("slate") - case gray extends Color("gray") - case zinc extends Color("zinc") - case neutral extends Color("neutral") - case stone extends Color("stone") - case red extends Color("red") - case orange extends Color("orange") - case amber extends Color("amber") - case yellow extends Color("yellow") - case lime extends Color("lime") - case green extends Color("green") - case emerald extends Color("emerald") - case teal extends Color("teal") - case cyan extends Color("cyan") - case sky extends Color("sky") - case blue extends Color("blue") - case indigo extends Color("indigo") - case violet extends Color("violet") - case purple extends Color("purple") - case fuchsia extends Color("fuchsia") - case pink extends Color("pink") - case rose extends Color("rose") diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala deleted file mode 100644 index 6fb453c..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -object CustomAttrs { - // Made a pull request to add aria-current to scala-dom-types, remove after - val ariaCurrent = customHtmlAttr("aria-current", StringAsIsCodec) - val ariaHidden = customHtmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - - val datetime = customHtmlAttr("datetime", StringAsIsCodec) - - object svg { - import com.raquo.laminar.api.L.svg.{*, given} - val ariaHidden = - customSvgAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - } -} diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala deleted file mode 100644 index 6cda48b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala +++ /dev/null @@ -1,28 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -object Display: - - enum Breakpoint: - case sm, md, lg, xl, `2xl` - - enum DisplayClass: - case block, `inline-block`, `inline`, flex, `inline-flex`, table, - `inline-table`, `table-caption` - - object ShowUpFrom: - inline def apply( - br: Breakpoint, - dc: DisplayClass = DisplayClass.block - ): HtmlElement = - div( - cls := "hidden", - cls := s"${br}:${dc}" - ) - - object HideUpTo: - inline def apply(br: Breakpoint): HtmlElement = - div( - cls := s"${br}:hidden" - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala deleted file mode 100644 index 4a8b065..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala +++ /dev/null @@ -1,270 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec -import com.raquo.domtypes.generic.defs.attrs.AriaAttrs -import com.raquo.laminar.api.L.svg.{*, given} -import com.raquo.laminar.builders.SvgBuilders -import com.raquo.laminar.keys.ReactiveSvgAttr - -// TODO: fix sizes, colors, hover and stuff, normalize and amend on call site -object Icons: - object aria: - inline def hidden = CustomAttrs.svg.ariaHidden - - object outline: - val defaultSize: Int = 6 - - inline def bell(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" - ) - ) - - inline def `check-circle`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" - ) - ) - - inline def `document-add`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M9 13h6m-3-3v6m5 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" - ) - ) - - inline def `external-link`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" - ) - ) - - inline def menu(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M4 6h16M4 12h16M4 18h16" - ) - ) - - inline def `status-offline`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M18.364 5.636a9 9 0 010 12.728m0 0l-2.829-2.829m2.829 2.829L21 21M15.536 8.464a5 5 0 010 7.072m0 0l-2.829-2.829m-4.243 2.829a4.978 4.978 0 01-1.414-2.83m-1.414 5.658a9 9 0 01-2.167-9.238m7.824 2.167a1 1 0 111.414 1.414m-1.414-1.414L3 3m8.293 8.293l1.414 1.414" - ) - ) - - inline def user(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" - ) - ) - - inline def x(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M6 18L18 6M6 6l12 12" - ) - ) - - end outline - - object solid: - val defaultSize: Int = 5 - - inline def users(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - d := "M9 6a3 3 0 11-6 0 3 3 0 016 0zM17 6a3 3 0 11-6 0 3 3 0 016 0zM12.93 17c.046-.327.07-.66.07-1a6.97 6.97 0 00-1.5-4.33A5 5 0 0119 16v1h-6.07zM6 11a5 5 0 015 5v1H1v-1a5 5 0 015-5z" - ) - ) - - inline def `location-marker`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M5.05 4.05a7 7 0 119.9 9.9L10 18.9l-4.95-4.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z", - clipRule := "evenodd" - ) - ) - - inline def calendar(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z", - clipRule := "evenodd" - ) - ) - - inline def `chevron-right`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z", - clipRule := "evenodd" - ) - ) - - inline def search(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z", - clipRule := "evenodd" - ) - ) - - inline def filter(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M3 3a1 1 0 011-1h12a1 1 0 011 1v3a1 1 0 01-.293.707L12 11.414V15a1 1 0 01-.293.707l-2 2A1 1 0 018 17v-5.586L3.293 6.707A1 1 0 013 6V3z", - clipRule := "evenodd" - ) - ) - - inline def `arrow-narrow-left`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M7.707 14.707a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 1.414L5.414 9H17a1 1 0 110 2H5.414l2.293 2.293a1 1 0 010 1.414z", - clipRule := "evenodd" - ) - ) - - inline def home(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - d := "M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z" - ) - ) - - inline def paperclip(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size} text-gray-400", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M8 4a3 3 0 00-3 3v4a5 5 0 0010 0V7a1 1 0 112 0v4a7 7 0 11-14 0V7a5 5 0 0110 0v4a3 3 0 11-6 0V7a1 1 0 012 0v4a1 1 0 102 0V7a3 3 0 00-3-3z", - clipRule := "evenodd" - ) - ) - - end solid -end Icons diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/LinkSupport.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/LinkSupport.scala deleted file mode 100644 index 88f0b9a..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/LinkSupport.scala +++ /dev/null @@ -1,12 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.jsdom.defs.events.TypedTargetMouseEvent - -object LinkSupport: - - extension [El <: org.scalajs.dom.EventTarget]( - ep: EventProcessor[TypedTargetMouseEvent[El], TypedTargetMouseEvent[El]] - ) - def noKeyMod = - ep.filter(ev => !(ev.ctrlKey || ev.metaKey || ev.shiftKey || ev.altKey)) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Loader.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Loader.scala deleted file mode 100644 index 5399933..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Loader.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -// TODO: proper loader -def Loading = - div( - cls := "bg-gray-50 overflow-hidden rounded-lg", - div( - cls := "px-4 py-5 sm:p-6", - "Loading..." - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Renderable.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Renderable.scala deleted file mode 100644 index a0fcc5b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Renderable.scala +++ /dev/null @@ -1,6 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -trait Renderable[A]: - extension (a: A) def toHtml: HtmlElement diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/ComboBox.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/ComboBox.scala deleted file mode 100644 index 8c5b8a1..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/ComboBox.scala +++ /dev/null @@ -1,92 +0,0 @@ -package works.iterative.ui.components.tailwind -package form - -import com.raquo.laminar.api.L.{*, given} -import io.laminext.syntax.core.* - -case class ComboBox( - id: String, - options: Signal[List[ComboBox.Option]], - valueUpdates: Observer[List[String]] -) - -object ComboBox: - - extension (m: ComboBox) - def toHtml: HtmlElement = - val isOpen = Var(false) - div( - cls := "relative mt-1", - input( - idAttr := m.id, - tpe := "text", - cls := "w-full rounded-md border border-gray-300 bg-white py-2 pl-3 pr-12 shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 sm:text-sm", - role := "combobox", - aria.controls := "options", - aria.expanded := false, - onClick.mapTo(true) --> isOpen.writer - ), - button( - tpe := "button", - cls := "absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-none", { - import svg.* - svg( - cls := "h-5 w-5 text-gray-400", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - CustomAttrs.svg.ariaHidden := true, - path( - fillRule := "evenodd", - d := "M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z", - clipRule := "evenodd" - ) - ) - } - ), - ul( - cls <-- isOpen.signal.switch("", "hidden"), - cls := "absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm", - idAttr := "options", - role := "listbox", - children <-- m.options.map(_.map(_.toHtml)) - ) - ) - - case class Option(value: String, active: Boolean) - - object Option: - extension (m: Option) - def toHtml: HtmlElement = - li( - cls := "relative cursor-default select-none py-2 pl-8 pr-4", - cls := (if m.active then "text-white bg-indigo-600" - else "text-gray-900"), - idAttr := "option-0", - role := "option", - tabIndex := -1, - span( - cls := "block truncate", - m.value - ), - if m.active then - span( - cls := "absolute inset-y-0 left-0 flex items-center pl-1.5", - cls := (if m.active then "text-white" else "text-indigo-600"), { - import svg.* - svg( - cls := "h-5 w-5", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - CustomAttrs.svg.ariaHidden := true, - path( - fillRule := "evenodd", - d := "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", - clipRule := "evenodd" - ) - ) - } - ) - else emptyNode - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/Form.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/Form.scala deleted file mode 100644 index 1b41dee..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/Form.scala +++ /dev/null @@ -1,17 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} -import org.scalajs.dom -import com.raquo.laminar.nodes.ReactiveHtmlElement - -object Form: - val Body = FormBody - val Section = FormSection - val Row = FormRow - - def apply(body: HtmlElement, buttons: HtmlElement): HtmlElement = - form( - cls := "space-y-8 divide-y divide-gray-200", - body, - buttons - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormBody.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormBody.scala deleted file mode 100644 index 89abc9c..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormBody.scala +++ /dev/null @@ -1,10 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} - -object FormBody: - def apply(sections: HtmlElement*): HtmlElement = - div( - cls := "space-y-8 divide-y divide-gray-200 sm:space-y-5", - sections - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormFields.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormFields.scala deleted file mode 100644 index a2e4cc6..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormFields.scala +++ /dev/null @@ -1,14 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.laminar.nodes.ReactiveHtmlElement -import org.scalajs.dom - -object FormFields: - def apply( - mods: Modifier[ReactiveHtmlElement[dom.HTMLElement]]* - ): HtmlElement = - div( - cls := "mt-6 sm:mt-5 space-y-6 sm:space-y-5", - mods - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormHeader.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormHeader.scala deleted file mode 100644 index 4083841..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormHeader.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} - -object FormHeader: - case class ViewModel(header: String, description: String) - def apply(m: ViewModel): HtmlElement = - div( - h3(cls := "text-lg leading-6 font-medium text-gray-900", m.header), - p(cls := "mt-1 max-w-2xl text-sm text-gray-500", m.description) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormRow.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormRow.scala deleted file mode 100644 index ffc5807..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormRow.scala +++ /dev/null @@ -1,22 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} - -case class FormRow(id: String, label: String, content: Modifier[Div]) - -object FormRow: - - extension (m: FormRow) - def toHtml: HtmlElement = - div( - cls := "sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5", - label( - forId := m.id, - cls := "block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2", - m.label - ), - div( - cls := "mt-1 sm:mt-0 sm:col-span-2", - m.content - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormSection.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormSection.scala deleted file mode 100644 index 50811ee..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormSection.scala +++ /dev/null @@ -1,15 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.laminar.nodes.ReactiveHtmlElement - -object FormSection: - def apply( - header: HtmlElement, - rows: HtmlElement* - ): HtmlElement = - div( - cls := "space-y-6 sm:space-y-5", - header, - rows - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/BaseList.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/BaseList.scala deleted file mode 100644 index 66506e9..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/BaseList.scala +++ /dev/null @@ -1,142 +0,0 @@ -package works.iterative.ui.components.tailwind -package list - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.waypoint.Router -import com.raquo.laminar.builders.HtmlTag -import org.scalajs.dom - -trait ListItem: - def key: String - def title: Modifier[HtmlElement] - def topRight: Modifier[HtmlElement] - def bottomLeft: Modifier[HtmlElement] - def bottomRight: Modifier[HtmlElement] - -trait ListRenderable[Item]: - extension (x: Item) def asItem: ListItem - -trait Navigable[Item]: - extension (x: Item) def navigate: Modifier[HtmlElement] - -object BaseList: - enum Color: - case Green, Yellow, Red - - case class IconText(text: HtmlElement, icon: SvgElement) - case class Tag(text: String, color: Color) - case class Row( - id: String, - title: String, - tag: Tag, - leftProps: List[IconText], - rightProp: IconText - ) - - trait AsRow[Data]: - extension (d: Data) def asRow: Row - - class RowListItem(d: Row) extends ListItem: - - def key: String = d.id - - def title: Modifier[HtmlElement] = d.title - - def topRight: Modifier[HtmlElement] = - inline def colorClass(color: Color): (String, Boolean) = - val c = color.toString.toLowerCase - s"bg-$c-100 text-$c-800" -> (d.tag.color == color) - - inline def colors = Map(Color.values.map(colorClass(_)): _*) - - p( - cls := "px-2 inline-flex text-xs leading-5 font-semibold rounded-full", - cls := colors, - d.tag.text - ) - - def bottomLeft: Modifier[HtmlElement] = - div( - cls := "sm:flex", - d.leftProps.zipWithIndex.map { case (i, idx) => - p( - cls := Map("mt-2 sm:mt-0 sm:ml-6" -> (idx == 0)), - cls := "flex items-center text-sm text-gray-500", - i.icon, - i.text - ) - } - ) - - def bottomRight: Modifier[HtmlElement] = - div( - cls := "mt-2 flex items-center text-sm text-gray-500 sm:mt-0", - d.rightProp.icon, - d.rightProp.text - ) - - object Row: - given asRowRenderable[T: AsRow]: ListRenderable[T] with - extension (d: T) def asItem = new RowListItem(d.asRow) - - end Row - -class BaseList[RowData: ListRenderable]: - - protected def containerElement: HtmlTag[dom.html.Element] = div - protected def containerMods(rowData: RowData): Modifier[HtmlElement] = - emptyMod - protected def farRight: Modifier[HtmlElement] = emptyMod - - def render($data: Signal[List[RowData]]): HtmlElement = - ul( - role := "list", - cls := "divide-y divide-gray-200", - children <-- $data.split(_.asItem.key)((_, d, _) => row(d)) - ) - - private def row(d: RowData): HtmlElement = - val data = d.asItem - li( - containerElement( - containerMods(d), - cls := "block hover:bg-gray-50", - div( - cls := "px-4 py-4 sm:px-6 items-center flex", - div( - cls := "min-w-0 flex-1 pr-4", - div( - cls := "flex items-center justify-between", - p( - cls := "text-sm font-medium text-indigo-600 truncate", - data.title - ), - div( - cls := "ml-2 flex-shrink-0 flex", - data.topRight - ) - ), - div( - cls := "mt-2 sm:flex sm:justify-between", - data.bottomLeft, - data.bottomRight - ) - ), - farRight - ) - ) - ) - -trait NavigableList[RowData: Navigable, Page](using router: Router[Page]) - extends BaseList[RowData]: - - override protected def containerElement: HtmlTag[dom.html.Element] = a - override protected def containerMods( - rowData: RowData - ): Modifier[HtmlElement] = - rowData.navigate - override protected def farRight: Modifier[HtmlElement] = - div( - cls := "flex-shrink-0", - Icons.solid.`chevron-right`().amend(svg.cls := "text-gray-400") - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala deleted file mode 100644 index e7e6627..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.akka - -// Base class for all command-processing related exceptions from handlers -sealed abstract class CommandHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandNotAvailable[C, S](cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd není dostupný ve stavu $state" - ) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandRejected[C, S](reason: String, cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd byl ve stavu $state odmítnut s odůvodněním $reason" - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala deleted file mode 100644 index 72a5bf9..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.akka - -sealed abstract class EventHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -case class UnhandledEvent[Event, State](event: Event, state: State) - extends EventHandlerException( - s"Událost $event nastala ve stavu $state bez možnosti zpracování" - ) diff --git a/iw/bom.sc b/iw/bom.sc deleted file mode 100644 index e9266b8..0000000 --- a/iw/bom.sc +++ /dev/null @@ -1,227 +0,0 @@ -import mill._, scalalib._ - -object IWMaterials { - - object Versions { - val akka = "2.6.16" - val akkaHttp = "10.2.4" - val cats = "2.7.0" - val elastic4s = "7.12.2" - val http4s = "0.23.10" - val http4sPac4J = "4.0.0" - val laminar = "0.14.2" - val laminext = laminar - val logbackClassic = "1.2.10" - val pac4j = "5.2.0" - val play = "2.8.8" - val playJson = "2.9.2" - val scalaTest = "3.2.9" - val slick = "3.3.3" - val sttpClient = "3.5.0" - val tapir = "0.20.1" - val urlDsl = "0.4.0" - val waypoint = "0.5.0" - val zio = "2.0.0-RC2" - val zioConfig = "3.0.0-RC2" - val zioInteropCats = "3.3.0-RC2" - val zioJson = "0.3.0-RC3" - val zioLogging = "2.0.0-RC5" - val zioPrelude = "1.0.0-RC10" - val zioZMX = "0.0.11" - } - - object Deps extends AkkaLibs with SlickLibs { - import IWMaterials.{Versions => V} - - val zioOrg = "dev.zio" - - def zioLib(name: String, version: String): Dep = - ivy"$zioOrg::zio-$name::$version" - - lazy val zio: Dep = ivy"$zioOrg::zio:${V.zio}" - - lazy val zioTest: Dep = zioLib("test", V.zio) - lazy val zioTestSbt: Dep = zioLib("test", V.zio) - - lazy val zioConfig: Dep = zioLib("config", V.zioConfig) - lazy val zioConfigTypesafe: Dep = - zioLib("config-typesafe", V.zioConfig) - lazy val zioConfigMagnolia: Dep = - zioLib("config-magnolia", V.zioConfig) - - lazy val zioJson: Dep = zioLib("json", V.zioJson) - lazy val zioLogging: Dep = zioLib("logging", V.zioLogging) - lazy val zioLoggingSlf4j: Dep = - zioLib("logging-slf4j", V.zioLogging) - lazy val zioPrelude: Dep = zioLib("prelude", V.zioPrelude) - lazy val zioStreams: Dep = zioLib("streams", V.zio) - lazy val zioZMX: Dep = zioLib("zmx", V.zioZMX) - lazy val zioInteropCats: Dep = - zioLib("interop-cats", V.zioInteropCats) - - /* What is the equivalent? ZIOModule with prepared test config? - def useZIO(testConf: Configuration*): Agg[Dep] = Agg( - zio, - zioTest, - zioTestSbt, - testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework") - ) - */ - - /* - def useZIOAll(testConf: Configuration*): Seq[Def.Setting[_]] = - useZIO(testConf: _*) ++ Seq( - zioStreams, - zioConfig, - zioConfigTypesafe, - zioConfigMagnolia, - zioJson, - zioZMX, - zioLogging, - zioPrelude - ) - */ - - private val tapirOrg = "com.softwaremill.sttp.tapir" - def tapirLib(name: String): Dep = - ivy"${tapirOrg}::tapir-$name::${V.tapir}" - - lazy val tapirCore: Dep = tapirLib("core") - lazy val tapirZIO: Dep = tapirLib("zio") - lazy val tapirZIOJson: Dep = tapirLib("json-zio") - lazy val tapirSttpClient: Dep = tapirLib("sttp-client") - lazy val tapirCats: Dep = tapirLib("cats") - lazy val tapirZIOHttp4sServer: Dep = tapirLib("zio-http4s-server") - - private val sttpClientOrg = "com.softwaremill.sttp.client3" - def sttpClientLib(name: String): Dep = - ivy"${sttpClientOrg}::${name}:${V.sttpClient}" - - lazy val sttpClientCore: Dep = sttpClientLib("core") - - lazy val http4sBlazeServer: Dep = - ivy"org.http4s::http4s-blaze-server:${V.http4s}" - - lazy val http4sPac4J: Dep = - ivy"org.pac4j::http4s-pac4j:${V.http4sPac4J}" - lazy val pac4jOIDC: Dep = - ivy"org.pac4j:pac4j-oidc:${V.pac4j}" - - lazy val scalaTest: Dep = - ivy"org.scalatest::scalatest:${V.scalaTest}" - lazy val scalaTestPlusScalacheck: Dep = - ivy"org.scalatestplus::scalacheck-1-15:3.2.9.0" - lazy val playScalaTest: Dep = - ivy"org.scalatestplus.play::scalatestplus-play:5.1.0" - - private val playOrg = "com.typesafe.play" - lazy val playMailer: Dep = ivy"${playOrg}::play-mailer:8.0.1" - lazy val playServer: Dep = ivy"${playOrg}::play-server:${V.play}" - lazy val playAkkaServer: Dep = - ivy"${playOrg}::play-akka-http-server:${V.play}" - lazy val play: Dep = ivy"${playOrg}::play:${V.play}" - lazy val playAhcWs: Dep = ivy"${playOrg}::play-ahc-ws:${V.play}" - lazy val playJson: Dep = ivy"${playOrg}::play-json:${V.playJson}" - - private val elastic4sOrg = "com.sksamuel.elastic4s" - lazy val useElastic4S: Agg[Dep] = Agg( - ivy"${elastic4sOrg}::elastic4s-core:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-client-akka:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-http-streams:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-json-play:${V.elastic4s}" - ) - - lazy val laminar: Dep = ivy"com.raquo::laminar::${V.laminar}" - - private def laminext(name: String): Dep = - ivy"io.laminext::$name::${V.laminar}" - - lazy val laminextCore: Dep = laminext("core") - lazy val laminextTailwind: Dep = laminext("tailwind") - lazy val laminextFetch: Dep = laminext("fetch") - lazy val laminextValidationCore: Dep = laminext("validation-core") - lazy val laminextUI: Dep = laminext("ui") - - lazy val waypoint: Dep = - ivy"com.raquo::waypoint::${V.waypoint}" - - lazy val urlDsl: Dep = - ivy"be.doeraene::url-dsl::${V.urlDsl}" - - lazy val scalaJavaTime: Dep = - ivy"io.github.cquiroz::scala-java-time::2.3.0" - - lazy val scalaJavaLocales: Dep = - ivy"io.github.cquiroz::scala-java-locales::1.2.1" - - lazy val logbackClassic: Dep = - ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" - } - - trait AkkaLibs { - - self: SlickLibs => - - object akka { - val V = Versions.akka - val tOrg = "com.typesafe.akka" - val lOrg = "com.lightbend.akka" - - def akkaMod(name: String): Dep = - ivy"$tOrg::akka-$name:$V" - - lazy val actor: Dep = akkaMod("actor") - lazy val actorTyped: Dep = akkaMod("actor-typed") - lazy val stream: Dep = akkaMod("stream") - lazy val persistence: Dep = akkaMod("persistence-typed") - lazy val persistenceQuery: Dep = akkaMod("persistence-query") - lazy val persistenceJdbc: Dep = - ivy"$lOrg::akka-persistence-jdbc:5.0.4" - val persistenceTestKit: Dep = ivy"$tOrg::akka-persistence-testkit:$V" - - object http { - val V = Versions.akkaHttp - - lazy val http: Dep = ivy"$tOrg::akka-http:$V" - lazy val sprayJson: Dep = ivy"$tOrg::akka-http-spray-json:$V" - - } - - object projection { - val V = "1.2.2" - - lazy val core: Dep = - ivy"$lOrg::akka-projection-core:$V" - lazy val eventsourced: Dep = - ivy"$lOrg::akka-projection-eventsourced:$V" - lazy val slick: Dep = - ivy"$lOrg::akka-projection-slick:$V" - lazy val jdbc: Dep = - ivy"$lOrg::akka-projection-jdbc:$V" - } - - object profiles { - // TODO: deal with cross-version for Scala3 (for3Use2_13) - lazy val eventsourcedJdbcProjection: Agg[Dep] = Agg( - persistenceQuery, - projection.core, - projection.eventsourced, - projection.slick, - persistenceJdbc - ) ++ slick.default - } - } - } - - trait SlickLibs { - object slick { - val V = IWMaterials.Versions.slick - val org = "com.typesafe.slick" - - lazy val slick: Dep = ivy"$org::slick:$V" - lazy val hikaricp: Dep = ivy"$org::slick-hikaricp:$V" - lazy val default: Agg[Dep] = Agg(slick, hikaricp) - } - } - -} diff --git a/iw/build.sc b/iw/build.sc deleted file mode 100644 index fbbc3b8..0000000 --- a/iw/build.sc +++ /dev/null @@ -1,144 +0,0 @@ -import mill._, scalalib._, scalajslib._ - -import $file.bom - -object support { - val Deps = bom.IWMaterials.Deps - val Versions = bom.IWMaterials.Versions - - trait CommonModule extends ScalaModule { - def scalaVersion = "3.1.1" - def scalacOptions = Seq( - "-encoding", - "utf8", - "-deprecation", - "-explain-types", - "-explain", - "-feature", - "-language:experimental.macros", - "-language:higherKinds", - "-language:implicitConversions", - "-unchecked", - "-Xfatal-warnings", - "-Ykind-projector" - ) - } - - trait CommonJSModule extends CommonModule with ScalaJSModule { - def scalaJSVersion = "1.8.0" - } - - trait CrossPlatformModule extends Module { outer => - def ivyDeps: T[Agg[Dep]] = Agg[Dep]() - def jsDeps: T[Agg[Dep]] = Agg[Dep]() - def jvmDeps: T[Agg[Dep]] = Agg[Dep]() - def moduleDeps: Seq[Module] = Seq[Module]() - - trait PlatformModule extends CommonModule { - def platform: String - override def millSourcePath = outer.millSourcePath - override def ivyDeps = outer.ivyDeps() ++ (platform match { - case "js" => jsDeps() - case _ => jvmDeps() - }) - override def moduleDeps = outer.moduleDeps.collect { - case m: CrossPlatformModule => - platform match { - case "js" => m.js - case _ => m.jvm - } - case m: JavaModule => m - } - } - - trait JsModule extends PlatformModule with CommonJSModule { - def platform = "js" - } - - trait JvmModule extends PlatformModule { - def platform = "jvm" - } - - val js: JsModule - val jvm: JvmModule - } - - trait PureCrossModule extends CrossPlatformModule { - override object js extends JsModule - override object jvm extends JvmModule - } - - trait PureCrossSbtModule extends CrossPlatformModule { - override object js extends JsModule with SbtModule - override object jvm extends JvmModule with SbtModule - } - - trait FullCrossSbtModule extends CrossPlatformModule { - trait FullSources extends JavaModule { self: PlatformModule => - override def sources = T.sources( - millSourcePath / platform / "src" / "main" / "scala", - millSourcePath / "shared" / "src" / "main" / "scala" - ) - - override def resources = T.sources( - millSourcePath / platform / "src" / "main" / "resources", - millSourcePath / "shared" / "src" / "main" / "resources" - ) - } - - override object js extends JsModule with FullSources - override object jvm extends JvmModule with FullSources - } - -} - -import support._ - -object mongo extends CommonModule { - def ivyDeps = Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - ivy"org.mongodb.scala::mongo-scala-driver:4.2.3".withDottyCompat( - scalaVersion() - ) - ) -} - -object tapir extends FullCrossSbtModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson, Deps.zioJson) - def jvmDeps = Agg(Deps.tapirZIO, Deps.tapirZIOHttp4sServer) - def jsDeps = Agg(Deps.tapirSttpClient) -} - -object akkaPersistence extends CommonModule { - def millSourcePath = build.millSourcePath / "akka-persistence" - def ivyDeps = (Agg( - Deps.akka.persistenceQuery, - Deps.akka.persistenceJdbc, - Deps.akka.projection.core, - Deps.akka.projection.eventsourced, - Deps.akka.projection.slick - ) ++ Deps.slick.default).map(_.withDottyCompat(scalaVersion())) ++ Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - Deps.akka.persistence.withDottyCompat(scalaVersion()), - ivy"com.typesafe.akka::akka-cluster-sharding-typed:${Deps.akka.V}" - .withDottyCompat(scalaVersion()) - ) -} - -object ui extends CommonJSModule { - def ivyDeps = Agg( - Deps.zio, - Deps.laminar, - Deps.zioJson, - Deps.waypoint, - Deps.urlDsl, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) -} diff --git a/iw/domain.sc b/iw/domain.sc deleted file mode 100644 index b09c56b..0000000 --- a/iw/domain.sc +++ /dev/null @@ -1,78 +0,0 @@ -import mill._, scalalib._ - -import $file.{build => ff}, ff.support._ - -trait DomainModule extends Module { - // General extra model deps - def modelModules: Seq[Module] = Seq.empty - // General extra codecs deps - def codecsModules: Seq[Module] = Seq.empty - // General extra endpoints deps - def endpointsModules: Seq[Module] = Seq.empty - - // Implementation deps for repo - def repoModules: Seq[JavaModule] = Seq.empty - - object shared extends Module { - object model extends PureCrossModule { - def moduleDeps = modelModules - def ivyDeps = Agg(Deps.zioPrelude) - } - object codecs extends PureCrossModule { - def moduleDeps = Seq(model) ++ codecsModules - def ivyDeps = Agg(Deps.zioJson) - } - } - - trait CommonProjects extends Module { - object model extends PureCrossModule { - def ivyDeps = Agg(Deps.zioPrelude) - def moduleDeps = Seq(shared.model) - } - object codecs extends PureCrossModule { - def moduleDeps = - Seq(model, shared.model, shared.codecs, ff.tapir) - } - object endpoints extends PureCrossModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson) - def moduleDeps = Seq(model, codecs) ++ endpointsModules - } - object client extends CommonJSModule { - def moduleDeps = Seq(endpoints.js) - } - object components extends CommonJSModule { - def ivyDeps = Agg( - Deps.laminar, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) - def moduleDeps = Seq(model.js, codecs.js, client, ff.ui) - } - } - - object query extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(repo, query.endpoints.jvm) - } - object repo extends CommonModule { - def ivyDeps = Agg(Deps.zio) - def moduleDeps = Seq(model.jvm, codecs.jvm) ++ repoModules - } - object projection extends CommonModule { - def moduleDeps = Seq(repo, ff.akkaPersistence) - } - } - - object command extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(entity, command.endpoints.jvm) - } - object entity extends CommonModule { - def moduleDeps = Seq(model.jvm, codecs.jvm, ff.akkaPersistence) - } - } -} diff --git a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala b/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala deleted file mode 100644 index 0fa3493..0000000 --- a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala +++ /dev/null @@ -1,53 +0,0 @@ -package works.iterative.mongo - -import zio.* -import zio.json.* -import zio.config.* -import org.mongodb.scala.* -import org.mongodb.scala.model.Filters.* -import org.bson.json.JsonObject -import org.mongodb.scala.model.ReplaceOptions -import org.mongodb.scala.bson.conversions.Bson - -case class MongoConfig(uri: String) - -object MongoConfig: - val configDesc = - import ConfigDescriptor.* - nested("MONGO")(string("URI").default("mongodb://localhost:27017")) - .to[MongoConfig] - val fromEnv = ZConfig.fromSystemEnv(configDesc) - -extension (m: MongoClient.type) - def layer: RLayer[MongoConfig, MongoClient] = - ZIO - .serviceWithZIO[MongoConfig](c => Task.attempt(MongoClient(c.uri))) - .toLayer - -class MongoJsonRepository[Elem, Key, Criteria]( - collection: MongoCollection[JsonObject], - toFilter: Criteria => Bson, - idFilter: Elem => (String, Key) -)(using JsonCodec[Elem]) { - def matching(criteria: Criteria): Task[Seq[Elem]] = - val filter = toFilter(criteria) - val query = collection.find(filter) - - for - result <- ZIO.fromFuture(_ => query.toFuture) - proof <- ZIO.collect(result)(j => - ZIO.fromOption(j.getJson.fromJson[Elem].toOption) - ) - yield proof - - def put(elem: Elem): Task[Unit] = - Task.async(cb => - collection - .replaceOne( - equal.tupled(idFilter(elem)), - JsonObject(elem.toJson), - ReplaceOptions().upsert(true) - ) - .subscribe(_ => cb(Task.unit), t => cb(Task.fail(t))) - ) -} diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala deleted file mode 100644 index 68d7196..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala +++ /dev/null @@ -1,12 +0,0 @@ -package works.iterative.tapir - -import sttp.model.Uri - -opaque type BaseUri = Option[Uri] - -object BaseUri: - - def apply(optU: Option[Uri]): BaseUri = optU - def apply(u: Uri): BaseUri = Some(u) - - extension (v: BaseUri) def toUri: Option[Uri] = v diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 653bc51..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,22 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.client.sttp.SttpClientInterpreter -import sttp.tapir.PublicEndpoint -import sttp.tapir.client.sttp.WebSocketToPipe -import scala.concurrent.Future -import sttp.client3.SttpBackend -import sttp.capabilities.WebSockets - -trait CustomTapirPlatformSpecific extends SttpClientInterpreter: - self: CustomTapir => - - type Backend = SttpBackend[Future, WebSockets] - - def makeClient[I, E, O]( - endpoint: PublicEndpoint[I, E, O, Any] - )(using - baseUri: BaseUri, - backend: Backend, - wsToPipe: WebSocketToPipe[Any] - ): I => Future[O] = - toClientThrowErrors(endpoint, baseUri.toUri, backend) diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 2545fbd..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,5 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.ztapir.ZTapir - -trait CustomTapirPlatformSpecific extends ZTapir diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala deleted file mode 100644 index 55e4ca1..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala +++ /dev/null @@ -1,7 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.server.http4s.ztapir.ZHttp4sServerInterpreter - -trait Http4sCustomTapir[Env] - extends CustomTapir - with ZHttp4sServerInterpreter[Env] diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala deleted file mode 100644 index dd52e9b..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala +++ /dev/null @@ -1,14 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.Tapir -import sttp.tapir.json.zio.TapirJsonZio -import sttp.tapir.TapirAliases - -trait CustomTapir - extends Tapir - with TapirJsonZio - with TapirAliases - with CustomTapirPlatformSpecific: - given Schema[ServerError] = Schema.derived - -object CustomTapir extends CustomTapir diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala deleted file mode 100644 index 715591d..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.tapir - -import zio.json.* - -sealed trait ServerError -case class InternalServerError(msg: String) extends ServerError -object InternalServerError: - def fromThrowable(t: Throwable): ServerError = InternalServerError( - t.getMessage - ) - -object ServerError: - given JsonCodec[ServerError] = DeriveJsonCodec.gen diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/File.scala deleted file mode 100644 index 09f6c07..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala +++ /dev/null @@ -1,3 +0,0 @@ -package works.iterative.services.files - -case class File(url: String, name: String) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala deleted file mode 100644 index 46633a5..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala +++ /dev/null @@ -1,25 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.Renderable - -given Renderable[File] with - extension (m: File) - def toHtml: HtmlElement = - li( - cls("pl-3 pr-4 py-3 flex items-center justify-between text-sm"), - div( - cls("w-0 flex-1 flex items-center"), - Icons.solid - .paperclip() - .amend(svg.cls := "flex-shrink-0 text-gray-400"), - span(cls("ml-2 flex-1 w-0 truncate"), m.name) - ), - a( - href(m.url), - cls("font-medium text-indigo-600 hover:text-indigo-500"), - "Otevřít" - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala deleted file mode 100644 index ac59db8..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import io.laminext.syntax.core.{*, given} - -def FileList(files: List[File]): HtmlElement = - ul( - role("list"), - cls("border border-gray-200 rounded-md divide-y divide-gray-200"), - files.map(_.toHtml) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala deleted file mode 100644 index 2e8f690..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala +++ /dev/null @@ -1,81 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import io.laminext.syntax.core.{*, given} - -object FilePicker: - sealed trait Event - sealed trait DoneEvent extends Event - case class SelectionUpdated(files: Set[File]) extends DoneEvent - case object SelectionCancelled extends DoneEvent - case object AvailableFilesRequested extends Event - - def apply( - currentFiles: Signal[List[File]], - availableFilesStream: EventStream[List[File]] - )(selectionUpdates: Observer[Event]): HtmlElement = - val (updatesStream, updatesObserver) = EventStream.withObserver[Event] - val selectorOpen = Var[Boolean](false) - - // This sequence tricks browser into displaying modal content centered - // Inspired by modal in headless ui playground - // https://github.com/tailwindlabs/headlessui/blob/fdd26297953080d5ec905dda0bf5ec9607897d86/packages/playground-react/pages/transitions/component-examples/modal.tsx#L78-L79 - inline def browserCenteringModalTrick: Modifier[HtmlElement] = - Seq[Modifier[HtmlElement]]( - span(cls("hidden sm:inline-block sm:h-screen sm:align-middle")), - "​" // Zero width space - ) - - inline def overlay: Modifier[HtmlElement] = - // Page overlay - /* TODO: transition - enter="ease-out duration-300" - enterFrom="opacity-0" - enterTo="opacity-100" - leave="ease-in duration-200" - leaveFrom="opacity-100" - leaveTo="opacity-0" - */ - div( - div( - onClick.mapTo(false) --> selectorOpen.writer, - cls("fixed inset-0 transition-opacity"), - div(cls("absolute inset-0 bg-gray-500 opacity-75")) - ) - ) - - inline def modalSelector: HtmlElement = - div( - cls("fixed inset-0 z-10 overflow-y-auto"), - div( - cls("text-center sm:block sm:p-0"), - overlay, - browserCenteringModalTrick, - child <-- currentFiles.map( - FileSelector(_, availableFilesStream)(updatesObserver) - ) - ) - ) - - div( - updatesStream --> selectionUpdates, - updatesStream.collect { case _: DoneEvent => - false - } --> selectorOpen.writer, - child.maybe <-- selectorOpen.signal.map(isOpen => - if isOpen then Some(modalSelector) else None - ), - div( - cls("flex flex-col space-y-5"), - child.maybe <-- currentFiles.map(files => - if files.isEmpty then None else Some(FileList(files)) - ), - button( - tpe := "button", - cls := "bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500", - "Zvolit soubory", - onClick.mapTo(true) --> selectorOpen.writer - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala deleted file mode 100644 index b43e07a..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala +++ /dev/null @@ -1,74 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.Loading -import io.laminext.syntax.core.{*, given} -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -object FileSelector: - import FilePicker._ - - def apply( - initialFiles: List[File], - availableFilesStream: EventStream[List[File]] - )(selectionUpdates: Observer[Event]): HtmlElement = - val selectedFiles = Var[Set[File]](initialFiles.to(Set)) - val availableFiles = availableFilesStream.startWithNone - // Request the files to display - selectionUpdates.onNext(AvailableFilesRequested) - div( - cls( - "inline-block transform overflow-hidden rounded-lg bg-white text-left align-bottom shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:align-middle" - ), - role("dialog"), - customHtmlAttr("aria.modal", BooleanAsTrueFalseStringCodec)(true), - aria.labelledBy("modal-headline"), - div( - cls("bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"), - div( - cls("sm:flex sm:items-start"), - div( - cls("mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"), - h3( - cls("text-lg font-medium leading-6 text-gray-900"), - idAttr("modal-headline"), - "Výběr souborů" - ) - ) - ), - child <-- availableFiles - .split(_ => ())((_, _, af) => FileTable(af, selectedFiles)) - .map(_.getOrElse(Loading)) - ), - div( - cls("bg-gray-50 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6"), - span( - cls("flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto"), - button( - typ("button"), - cls( - "focus:shadow-outline-green inline-flex w-full justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-base font-medium leading-6 text-white shadow-sm transition duration-150 ease-in-out hover:bg-indigo-500 focus:border-indigo-700 focus:outline-none sm:text-sm sm:leading-5" - ), - "Potvrdit", - composeEvents(onClick)( - _.sample(selectedFiles) - .map(SelectionUpdated(_)) - ) --> selectionUpdates - ) - ), - span( - cls("mt-3 flex w-full rounded-md shadow-sm sm:mt-0 sm:w-auto"), - button( - typ("button"), - cls( - "focus:shadow-outline-blue inline-flex w-full justify-center rounded-md border border-gray-300 bg-white px-4 py-2 text-base font-medium leading-6 text-gray-700 shadow-sm transition duration-150 ease-in-out hover:text-gray-500 focus:border-blue-300 focus:outline-none sm:text-sm sm:leading-5" - ), - "Zrušit", - onClick.mapTo(SelectionCancelled) --> selectionUpdates - ) - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala deleted file mode 100644 index aa11b2b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala +++ /dev/null @@ -1,91 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import works.iterative.ui.components.tailwind.Icons - -def FileTable( - files: Signal[List[File]], - selectedFiles: Var[Set[File]] -): HtmlElement = - val scope = customHtmlAttr("scope", StringAsIsCodec) - - def headerRow: HtmlElement = - val col = scope("col") - val baseM: Modifier[HtmlElement] = Seq(cls("px-6 py-3"), col) - val textH = cls( - "text-left text-xs font-medium text-gray-500 uppercase tracking-wider" - ) - tr( - th(baseM, span(cls("sr-only"), "Vybrat")), - th(baseM, textH, "Název"), - th(baseM, cls("relative"), span(cls("sr-only"), "Otevřít")) - ) - - def tableRow( - f: File, - idx: Int, - selected: Boolean - )(toggleSelection: Observer[Unit]): HtmlElement = - val baseC = cls("px-6 py-4 whitespace-nowrap text-sm") - tr( - cls(if idx % 2 == 0 then "bg-gray-50" else "bg-white"), - td( - cls("font-medium cursor-pointer"), - onClick.mapTo(()) --> toggleSelection, - cls(if selected then "text-green-900" else "text-gray-200"), - Icons.outline.`check-circle`().amend(svg.cls := "mx-auto"), - span(cls("sr-only"), if selected then "Vybráno" else "Nevybráno") - ), - td( - baseC, - cls("font-medium text-gray-900"), - f.name, - onClick.mapTo(()) --> toggleSelection - ), - td( - baseC, - cls("text-right font-medium"), - a( - href(f.url), - target("_blank"), - cls( - "flex items-center space-x-4 text-indigo-600 hover:text-indigo-900" - ), - Icons.outline.`external-link`(), - "Otevřít" - ) - ) - ) - - div( - cls("flex flex-col"), - div(cls("overflow-x-auto sm:-mx-6 lg:-mx-8")), - div( - cls("py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8"), - div( - cls("shadow overflow-hidden border-b border-gray-200 sm:rounded-lg"), - table( - cls("min-w-full divide-y divide-gray-200"), - thead( - cls("bg-gray-50"), - headerRow - ), - tbody( - children <-- files - .map(_.zipWithIndex) - .combineWithFn(selectedFiles)((f, sel) => - f.map((file, idx) => - val active = sel.contains(file) - tableRow(file, idx, active)( - selectedFiles.writer - .contramap(_ => if active then sel - file else sel + file) - ) - ) - ) - ) - ) - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala deleted file mode 100644 index 0fc8796..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala +++ /dev/null @@ -1,32 +0,0 @@ -package works.iterative.ui.components.tailwind - -import CustomAttrs.ariaHidden -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -// TODO: render icon or picture based on img signal -class Avatar($avatarImg: Signal[Option[String]]): - inline def avatarPlaceholder(size: Int): HtmlElement = - div( - cls := s"rounded-full text-indigo-200 bg-indigo-500 h-${size} w-${size} flex items-center justify-center", - Icons.outline.user(size - 2) - ) - - inline def avatarImage(size: Int): Signal[HtmlElement] = - $avatarImg.split(_ => ())((_, _, $url) => - img( - cls := s"w-$size h-$size rounded-full", - src <-- $url, - alt := "" - ) - ).map(_.getOrElse(avatarPlaceholder(size))) - - inline def avatar(size: Int): HtmlElement = - div( - cls := "relative", - child <-- avatarImage(size), - span( - cls := "absolute inset-0 shadow-inner rounded-full", - ariaHidden := true - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala deleted file mode 100644 index bcbed88..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala +++ /dev/null @@ -1,77 +0,0 @@ -package works.iterative.ui.components.tailwind - -enum ColorWeight(value: String): - inline def toCSS: String = value - // To be used for black and white until - // we support better mechanism via extension methods - case w__ extends ColorWeight("") - case w50 extends ColorWeight("50") - case w100 extends ColorWeight("100") - case w200 extends ColorWeight("200") - case w300 extends ColorWeight("300") - case w400 extends ColorWeight("400") - case w500 extends ColorWeight("500") - case w600 extends ColorWeight("600") - case w700 extends ColorWeight("700") - case w800 extends ColorWeight("800") - case w900 extends ColorWeight("900") - -enum Color(name: String): - import ColorWeight._ - - inline def toCSSNoColorWeight(prefix: String): String = - s"${prefix}-${name}" - inline def toCSSWithColorWeight( - prefix: String, - weight: ColorWeight - ): String = - s"${prefix}-${name}-${weight.toCSS}" - inline def toCSS(prefix: String)(weight: ColorWeight): String = - weight match { - case `w__` => toCSSNoColorWeight(prefix) - case _ => toCSSWithColorWeight(prefix, weight) - } - inline def bg: ColorWeight => String = toCSS("bg")(_) - inline def text: ColorWeight => String = toCSS("text")(_) - inline def decoration: ColorWeight => String = toCSS("decoration")(_) - inline def border: ColorWeight => String = toCSS("border")(_) - inline def outline: ColorWeight => String = toCSS("outline")(_) - inline def divide: ColorWeight => String = toCSS("divide")(_) - inline def ring: ColorWeight => String = toCSS("ring")(_) - inline def ringOffset: ColorWeight => String = toCSS("ring-offset")(_) - inline def shadow: ColorWeight => String = toCSS("shadow")(_) - inline def accent: ColorWeight => String = toCSS("accent")(_) - - // TODO: change the "stupid" methods to extension methods - // that will keep the invariants in comments lower - case current extends Color("current") - case inherit extends Color("inherit") - // Not present in for all methods - case transp extends Color("transparent") - // Seen in accent, not preset otherwise - case auto extends Color("auto") - // Black and white do not have weight - case black extends Color("black") - case white extends Color("white") - case slate extends Color("slate") - case gray extends Color("gray") - case zinc extends Color("zinc") - case neutral extends Color("neutral") - case stone extends Color("stone") - case red extends Color("red") - case orange extends Color("orange") - case amber extends Color("amber") - case yellow extends Color("yellow") - case lime extends Color("lime") - case green extends Color("green") - case emerald extends Color("emerald") - case teal extends Color("teal") - case cyan extends Color("cyan") - case sky extends Color("sky") - case blue extends Color("blue") - case indigo extends Color("indigo") - case violet extends Color("violet") - case purple extends Color("purple") - case fuchsia extends Color("fuchsia") - case pink extends Color("pink") - case rose extends Color("rose") diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala deleted file mode 100644 index 6fb453c..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -object CustomAttrs { - // Made a pull request to add aria-current to scala-dom-types, remove after - val ariaCurrent = customHtmlAttr("aria-current", StringAsIsCodec) - val ariaHidden = customHtmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - - val datetime = customHtmlAttr("datetime", StringAsIsCodec) - - object svg { - import com.raquo.laminar.api.L.svg.{*, given} - val ariaHidden = - customSvgAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - } -} diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala deleted file mode 100644 index 6cda48b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala +++ /dev/null @@ -1,28 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -object Display: - - enum Breakpoint: - case sm, md, lg, xl, `2xl` - - enum DisplayClass: - case block, `inline-block`, `inline`, flex, `inline-flex`, table, - `inline-table`, `table-caption` - - object ShowUpFrom: - inline def apply( - br: Breakpoint, - dc: DisplayClass = DisplayClass.block - ): HtmlElement = - div( - cls := "hidden", - cls := s"${br}:${dc}" - ) - - object HideUpTo: - inline def apply(br: Breakpoint): HtmlElement = - div( - cls := s"${br}:hidden" - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala deleted file mode 100644 index 4a8b065..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala +++ /dev/null @@ -1,270 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec -import com.raquo.domtypes.generic.defs.attrs.AriaAttrs -import com.raquo.laminar.api.L.svg.{*, given} -import com.raquo.laminar.builders.SvgBuilders -import com.raquo.laminar.keys.ReactiveSvgAttr - -// TODO: fix sizes, colors, hover and stuff, normalize and amend on call site -object Icons: - object aria: - inline def hidden = CustomAttrs.svg.ariaHidden - - object outline: - val defaultSize: Int = 6 - - inline def bell(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" - ) - ) - - inline def `check-circle`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" - ) - ) - - inline def `document-add`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M9 13h6m-3-3v6m5 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" - ) - ) - - inline def `external-link`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" - ) - ) - - inline def menu(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M4 6h16M4 12h16M4 18h16" - ) - ) - - inline def `status-offline`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M18.364 5.636a9 9 0 010 12.728m0 0l-2.829-2.829m2.829 2.829L21 21M15.536 8.464a5 5 0 010 7.072m0 0l-2.829-2.829m-4.243 2.829a4.978 4.978 0 01-1.414-2.83m-1.414 5.658a9 9 0 01-2.167-9.238m7.824 2.167a1 1 0 111.414 1.414m-1.414-1.414L3 3m8.293 8.293l1.414 1.414" - ) - ) - - inline def user(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" - ) - ) - - inline def x(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M6 18L18 6M6 6l12 12" - ) - ) - - end outline - - object solid: - val defaultSize: Int = 5 - - inline def users(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - d := "M9 6a3 3 0 11-6 0 3 3 0 016 0zM17 6a3 3 0 11-6 0 3 3 0 016 0zM12.93 17c.046-.327.07-.66.07-1a6.97 6.97 0 00-1.5-4.33A5 5 0 0119 16v1h-6.07zM6 11a5 5 0 015 5v1H1v-1a5 5 0 015-5z" - ) - ) - - inline def `location-marker`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M5.05 4.05a7 7 0 119.9 9.9L10 18.9l-4.95-4.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z", - clipRule := "evenodd" - ) - ) - - inline def calendar(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z", - clipRule := "evenodd" - ) - ) - - inline def `chevron-right`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z", - clipRule := "evenodd" - ) - ) - - inline def search(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z", - clipRule := "evenodd" - ) - ) - - inline def filter(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M3 3a1 1 0 011-1h12a1 1 0 011 1v3a1 1 0 01-.293.707L12 11.414V15a1 1 0 01-.293.707l-2 2A1 1 0 018 17v-5.586L3.293 6.707A1 1 0 013 6V3z", - clipRule := "evenodd" - ) - ) - - inline def `arrow-narrow-left`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M7.707 14.707a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 1.414L5.414 9H17a1 1 0 110 2H5.414l2.293 2.293a1 1 0 010 1.414z", - clipRule := "evenodd" - ) - ) - - inline def home(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - d := "M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z" - ) - ) - - inline def paperclip(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size} text-gray-400", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M8 4a3 3 0 00-3 3v4a5 5 0 0010 0V7a1 1 0 112 0v4a7 7 0 11-14 0V7a5 5 0 0110 0v4a3 3 0 11-6 0V7a1 1 0 012 0v4a1 1 0 102 0V7a3 3 0 00-3-3z", - clipRule := "evenodd" - ) - ) - - end solid -end Icons diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/LinkSupport.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/LinkSupport.scala deleted file mode 100644 index 88f0b9a..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/LinkSupport.scala +++ /dev/null @@ -1,12 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.jsdom.defs.events.TypedTargetMouseEvent - -object LinkSupport: - - extension [El <: org.scalajs.dom.EventTarget]( - ep: EventProcessor[TypedTargetMouseEvent[El], TypedTargetMouseEvent[El]] - ) - def noKeyMod = - ep.filter(ev => !(ev.ctrlKey || ev.metaKey || ev.shiftKey || ev.altKey)) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Loader.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Loader.scala deleted file mode 100644 index 5399933..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Loader.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -// TODO: proper loader -def Loading = - div( - cls := "bg-gray-50 overflow-hidden rounded-lg", - div( - cls := "px-4 py-5 sm:p-6", - "Loading..." - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Renderable.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Renderable.scala deleted file mode 100644 index a0fcc5b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Renderable.scala +++ /dev/null @@ -1,6 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -trait Renderable[A]: - extension (a: A) def toHtml: HtmlElement diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/ComboBox.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/ComboBox.scala deleted file mode 100644 index 8c5b8a1..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/ComboBox.scala +++ /dev/null @@ -1,92 +0,0 @@ -package works.iterative.ui.components.tailwind -package form - -import com.raquo.laminar.api.L.{*, given} -import io.laminext.syntax.core.* - -case class ComboBox( - id: String, - options: Signal[List[ComboBox.Option]], - valueUpdates: Observer[List[String]] -) - -object ComboBox: - - extension (m: ComboBox) - def toHtml: HtmlElement = - val isOpen = Var(false) - div( - cls := "relative mt-1", - input( - idAttr := m.id, - tpe := "text", - cls := "w-full rounded-md border border-gray-300 bg-white py-2 pl-3 pr-12 shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 sm:text-sm", - role := "combobox", - aria.controls := "options", - aria.expanded := false, - onClick.mapTo(true) --> isOpen.writer - ), - button( - tpe := "button", - cls := "absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-none", { - import svg.* - svg( - cls := "h-5 w-5 text-gray-400", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - CustomAttrs.svg.ariaHidden := true, - path( - fillRule := "evenodd", - d := "M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z", - clipRule := "evenodd" - ) - ) - } - ), - ul( - cls <-- isOpen.signal.switch("", "hidden"), - cls := "absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm", - idAttr := "options", - role := "listbox", - children <-- m.options.map(_.map(_.toHtml)) - ) - ) - - case class Option(value: String, active: Boolean) - - object Option: - extension (m: Option) - def toHtml: HtmlElement = - li( - cls := "relative cursor-default select-none py-2 pl-8 pr-4", - cls := (if m.active then "text-white bg-indigo-600" - else "text-gray-900"), - idAttr := "option-0", - role := "option", - tabIndex := -1, - span( - cls := "block truncate", - m.value - ), - if m.active then - span( - cls := "absolute inset-y-0 left-0 flex items-center pl-1.5", - cls := (if m.active then "text-white" else "text-indigo-600"), { - import svg.* - svg( - cls := "h-5 w-5", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - CustomAttrs.svg.ariaHidden := true, - path( - fillRule := "evenodd", - d := "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", - clipRule := "evenodd" - ) - ) - } - ) - else emptyNode - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/Form.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/Form.scala deleted file mode 100644 index 1b41dee..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/Form.scala +++ /dev/null @@ -1,17 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} -import org.scalajs.dom -import com.raquo.laminar.nodes.ReactiveHtmlElement - -object Form: - val Body = FormBody - val Section = FormSection - val Row = FormRow - - def apply(body: HtmlElement, buttons: HtmlElement): HtmlElement = - form( - cls := "space-y-8 divide-y divide-gray-200", - body, - buttons - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormBody.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormBody.scala deleted file mode 100644 index 89abc9c..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormBody.scala +++ /dev/null @@ -1,10 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} - -object FormBody: - def apply(sections: HtmlElement*): HtmlElement = - div( - cls := "space-y-8 divide-y divide-gray-200 sm:space-y-5", - sections - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormFields.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormFields.scala deleted file mode 100644 index a2e4cc6..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormFields.scala +++ /dev/null @@ -1,14 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.laminar.nodes.ReactiveHtmlElement -import org.scalajs.dom - -object FormFields: - def apply( - mods: Modifier[ReactiveHtmlElement[dom.HTMLElement]]* - ): HtmlElement = - div( - cls := "mt-6 sm:mt-5 space-y-6 sm:space-y-5", - mods - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormHeader.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormHeader.scala deleted file mode 100644 index 4083841..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormHeader.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} - -object FormHeader: - case class ViewModel(header: String, description: String) - def apply(m: ViewModel): HtmlElement = - div( - h3(cls := "text-lg leading-6 font-medium text-gray-900", m.header), - p(cls := "mt-1 max-w-2xl text-sm text-gray-500", m.description) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormRow.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormRow.scala deleted file mode 100644 index ffc5807..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormRow.scala +++ /dev/null @@ -1,22 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} - -case class FormRow(id: String, label: String, content: Modifier[Div]) - -object FormRow: - - extension (m: FormRow) - def toHtml: HtmlElement = - div( - cls := "sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5", - label( - forId := m.id, - cls := "block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2", - m.label - ), - div( - cls := "mt-1 sm:mt-0 sm:col-span-2", - m.content - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormSection.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormSection.scala deleted file mode 100644 index 50811ee..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormSection.scala +++ /dev/null @@ -1,15 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.laminar.nodes.ReactiveHtmlElement - -object FormSection: - def apply( - header: HtmlElement, - rows: HtmlElement* - ): HtmlElement = - div( - cls := "space-y-6 sm:space-y-5", - header, - rows - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/BaseList.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/BaseList.scala deleted file mode 100644 index 66506e9..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/BaseList.scala +++ /dev/null @@ -1,142 +0,0 @@ -package works.iterative.ui.components.tailwind -package list - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.waypoint.Router -import com.raquo.laminar.builders.HtmlTag -import org.scalajs.dom - -trait ListItem: - def key: String - def title: Modifier[HtmlElement] - def topRight: Modifier[HtmlElement] - def bottomLeft: Modifier[HtmlElement] - def bottomRight: Modifier[HtmlElement] - -trait ListRenderable[Item]: - extension (x: Item) def asItem: ListItem - -trait Navigable[Item]: - extension (x: Item) def navigate: Modifier[HtmlElement] - -object BaseList: - enum Color: - case Green, Yellow, Red - - case class IconText(text: HtmlElement, icon: SvgElement) - case class Tag(text: String, color: Color) - case class Row( - id: String, - title: String, - tag: Tag, - leftProps: List[IconText], - rightProp: IconText - ) - - trait AsRow[Data]: - extension (d: Data) def asRow: Row - - class RowListItem(d: Row) extends ListItem: - - def key: String = d.id - - def title: Modifier[HtmlElement] = d.title - - def topRight: Modifier[HtmlElement] = - inline def colorClass(color: Color): (String, Boolean) = - val c = color.toString.toLowerCase - s"bg-$c-100 text-$c-800" -> (d.tag.color == color) - - inline def colors = Map(Color.values.map(colorClass(_)): _*) - - p( - cls := "px-2 inline-flex text-xs leading-5 font-semibold rounded-full", - cls := colors, - d.tag.text - ) - - def bottomLeft: Modifier[HtmlElement] = - div( - cls := "sm:flex", - d.leftProps.zipWithIndex.map { case (i, idx) => - p( - cls := Map("mt-2 sm:mt-0 sm:ml-6" -> (idx == 0)), - cls := "flex items-center text-sm text-gray-500", - i.icon, - i.text - ) - } - ) - - def bottomRight: Modifier[HtmlElement] = - div( - cls := "mt-2 flex items-center text-sm text-gray-500 sm:mt-0", - d.rightProp.icon, - d.rightProp.text - ) - - object Row: - given asRowRenderable[T: AsRow]: ListRenderable[T] with - extension (d: T) def asItem = new RowListItem(d.asRow) - - end Row - -class BaseList[RowData: ListRenderable]: - - protected def containerElement: HtmlTag[dom.html.Element] = div - protected def containerMods(rowData: RowData): Modifier[HtmlElement] = - emptyMod - protected def farRight: Modifier[HtmlElement] = emptyMod - - def render($data: Signal[List[RowData]]): HtmlElement = - ul( - role := "list", - cls := "divide-y divide-gray-200", - children <-- $data.split(_.asItem.key)((_, d, _) => row(d)) - ) - - private def row(d: RowData): HtmlElement = - val data = d.asItem - li( - containerElement( - containerMods(d), - cls := "block hover:bg-gray-50", - div( - cls := "px-4 py-4 sm:px-6 items-center flex", - div( - cls := "min-w-0 flex-1 pr-4", - div( - cls := "flex items-center justify-between", - p( - cls := "text-sm font-medium text-indigo-600 truncate", - data.title - ), - div( - cls := "ml-2 flex-shrink-0 flex", - data.topRight - ) - ), - div( - cls := "mt-2 sm:flex sm:justify-between", - data.bottomLeft, - data.bottomRight - ) - ), - farRight - ) - ) - ) - -trait NavigableList[RowData: Navigable, Page](using router: Router[Page]) - extends BaseList[RowData]: - - override protected def containerElement: HtmlTag[dom.html.Element] = a - override protected def containerMods( - rowData: RowData - ): Modifier[HtmlElement] = - rowData.navigate - override protected def farRight: Modifier[HtmlElement] = - div( - cls := "flex-shrink-0", - Icons.solid.`chevron-right`().amend(svg.cls := "text-gray-400") - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/IconText.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/IconText.scala deleted file mode 100644 index 9f5a7b1..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/IconText.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.ui.components.tailwind -package list - -import com.raquo.laminar.api.L.{*, given} -import org.scalajs.dom -import com.raquo.laminar.builders.HtmlTag - -object IconText: - case class ViewModel(text: HtmlElement, icon: SvgElement) - def render($m: Signal[ViewModel]): HtmlElement = render($m, div) - def render( - $m: Signal[ViewModel], - container: HtmlTag[dom.html.Element] - ): HtmlElement = - container( - cls := "flex items-center text-sm text-gray-500", - child <-- $m.map(_.icon), - child <-- $m.map(_.text) - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala deleted file mode 100644 index e7e6627..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.akka - -// Base class for all command-processing related exceptions from handlers -sealed abstract class CommandHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandNotAvailable[C, S](cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd není dostupný ve stavu $state" - ) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandRejected[C, S](reason: String, cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd byl ve stavu $state odmítnut s odůvodněním $reason" - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala deleted file mode 100644 index 72a5bf9..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.akka - -sealed abstract class EventHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -case class UnhandledEvent[Event, State](event: Event, state: State) - extends EventHandlerException( - s"Událost $event nastala ve stavu $state bez možnosti zpracování" - ) diff --git a/iw/bom.sc b/iw/bom.sc deleted file mode 100644 index e9266b8..0000000 --- a/iw/bom.sc +++ /dev/null @@ -1,227 +0,0 @@ -import mill._, scalalib._ - -object IWMaterials { - - object Versions { - val akka = "2.6.16" - val akkaHttp = "10.2.4" - val cats = "2.7.0" - val elastic4s = "7.12.2" - val http4s = "0.23.10" - val http4sPac4J = "4.0.0" - val laminar = "0.14.2" - val laminext = laminar - val logbackClassic = "1.2.10" - val pac4j = "5.2.0" - val play = "2.8.8" - val playJson = "2.9.2" - val scalaTest = "3.2.9" - val slick = "3.3.3" - val sttpClient = "3.5.0" - val tapir = "0.20.1" - val urlDsl = "0.4.0" - val waypoint = "0.5.0" - val zio = "2.0.0-RC2" - val zioConfig = "3.0.0-RC2" - val zioInteropCats = "3.3.0-RC2" - val zioJson = "0.3.0-RC3" - val zioLogging = "2.0.0-RC5" - val zioPrelude = "1.0.0-RC10" - val zioZMX = "0.0.11" - } - - object Deps extends AkkaLibs with SlickLibs { - import IWMaterials.{Versions => V} - - val zioOrg = "dev.zio" - - def zioLib(name: String, version: String): Dep = - ivy"$zioOrg::zio-$name::$version" - - lazy val zio: Dep = ivy"$zioOrg::zio:${V.zio}" - - lazy val zioTest: Dep = zioLib("test", V.zio) - lazy val zioTestSbt: Dep = zioLib("test", V.zio) - - lazy val zioConfig: Dep = zioLib("config", V.zioConfig) - lazy val zioConfigTypesafe: Dep = - zioLib("config-typesafe", V.zioConfig) - lazy val zioConfigMagnolia: Dep = - zioLib("config-magnolia", V.zioConfig) - - lazy val zioJson: Dep = zioLib("json", V.zioJson) - lazy val zioLogging: Dep = zioLib("logging", V.zioLogging) - lazy val zioLoggingSlf4j: Dep = - zioLib("logging-slf4j", V.zioLogging) - lazy val zioPrelude: Dep = zioLib("prelude", V.zioPrelude) - lazy val zioStreams: Dep = zioLib("streams", V.zio) - lazy val zioZMX: Dep = zioLib("zmx", V.zioZMX) - lazy val zioInteropCats: Dep = - zioLib("interop-cats", V.zioInteropCats) - - /* What is the equivalent? ZIOModule with prepared test config? - def useZIO(testConf: Configuration*): Agg[Dep] = Agg( - zio, - zioTest, - zioTestSbt, - testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework") - ) - */ - - /* - def useZIOAll(testConf: Configuration*): Seq[Def.Setting[_]] = - useZIO(testConf: _*) ++ Seq( - zioStreams, - zioConfig, - zioConfigTypesafe, - zioConfigMagnolia, - zioJson, - zioZMX, - zioLogging, - zioPrelude - ) - */ - - private val tapirOrg = "com.softwaremill.sttp.tapir" - def tapirLib(name: String): Dep = - ivy"${tapirOrg}::tapir-$name::${V.tapir}" - - lazy val tapirCore: Dep = tapirLib("core") - lazy val tapirZIO: Dep = tapirLib("zio") - lazy val tapirZIOJson: Dep = tapirLib("json-zio") - lazy val tapirSttpClient: Dep = tapirLib("sttp-client") - lazy val tapirCats: Dep = tapirLib("cats") - lazy val tapirZIOHttp4sServer: Dep = tapirLib("zio-http4s-server") - - private val sttpClientOrg = "com.softwaremill.sttp.client3" - def sttpClientLib(name: String): Dep = - ivy"${sttpClientOrg}::${name}:${V.sttpClient}" - - lazy val sttpClientCore: Dep = sttpClientLib("core") - - lazy val http4sBlazeServer: Dep = - ivy"org.http4s::http4s-blaze-server:${V.http4s}" - - lazy val http4sPac4J: Dep = - ivy"org.pac4j::http4s-pac4j:${V.http4sPac4J}" - lazy val pac4jOIDC: Dep = - ivy"org.pac4j:pac4j-oidc:${V.pac4j}" - - lazy val scalaTest: Dep = - ivy"org.scalatest::scalatest:${V.scalaTest}" - lazy val scalaTestPlusScalacheck: Dep = - ivy"org.scalatestplus::scalacheck-1-15:3.2.9.0" - lazy val playScalaTest: Dep = - ivy"org.scalatestplus.play::scalatestplus-play:5.1.0" - - private val playOrg = "com.typesafe.play" - lazy val playMailer: Dep = ivy"${playOrg}::play-mailer:8.0.1" - lazy val playServer: Dep = ivy"${playOrg}::play-server:${V.play}" - lazy val playAkkaServer: Dep = - ivy"${playOrg}::play-akka-http-server:${V.play}" - lazy val play: Dep = ivy"${playOrg}::play:${V.play}" - lazy val playAhcWs: Dep = ivy"${playOrg}::play-ahc-ws:${V.play}" - lazy val playJson: Dep = ivy"${playOrg}::play-json:${V.playJson}" - - private val elastic4sOrg = "com.sksamuel.elastic4s" - lazy val useElastic4S: Agg[Dep] = Agg( - ivy"${elastic4sOrg}::elastic4s-core:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-client-akka:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-http-streams:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-json-play:${V.elastic4s}" - ) - - lazy val laminar: Dep = ivy"com.raquo::laminar::${V.laminar}" - - private def laminext(name: String): Dep = - ivy"io.laminext::$name::${V.laminar}" - - lazy val laminextCore: Dep = laminext("core") - lazy val laminextTailwind: Dep = laminext("tailwind") - lazy val laminextFetch: Dep = laminext("fetch") - lazy val laminextValidationCore: Dep = laminext("validation-core") - lazy val laminextUI: Dep = laminext("ui") - - lazy val waypoint: Dep = - ivy"com.raquo::waypoint::${V.waypoint}" - - lazy val urlDsl: Dep = - ivy"be.doeraene::url-dsl::${V.urlDsl}" - - lazy val scalaJavaTime: Dep = - ivy"io.github.cquiroz::scala-java-time::2.3.0" - - lazy val scalaJavaLocales: Dep = - ivy"io.github.cquiroz::scala-java-locales::1.2.1" - - lazy val logbackClassic: Dep = - ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" - } - - trait AkkaLibs { - - self: SlickLibs => - - object akka { - val V = Versions.akka - val tOrg = "com.typesafe.akka" - val lOrg = "com.lightbend.akka" - - def akkaMod(name: String): Dep = - ivy"$tOrg::akka-$name:$V" - - lazy val actor: Dep = akkaMod("actor") - lazy val actorTyped: Dep = akkaMod("actor-typed") - lazy val stream: Dep = akkaMod("stream") - lazy val persistence: Dep = akkaMod("persistence-typed") - lazy val persistenceQuery: Dep = akkaMod("persistence-query") - lazy val persistenceJdbc: Dep = - ivy"$lOrg::akka-persistence-jdbc:5.0.4" - val persistenceTestKit: Dep = ivy"$tOrg::akka-persistence-testkit:$V" - - object http { - val V = Versions.akkaHttp - - lazy val http: Dep = ivy"$tOrg::akka-http:$V" - lazy val sprayJson: Dep = ivy"$tOrg::akka-http-spray-json:$V" - - } - - object projection { - val V = "1.2.2" - - lazy val core: Dep = - ivy"$lOrg::akka-projection-core:$V" - lazy val eventsourced: Dep = - ivy"$lOrg::akka-projection-eventsourced:$V" - lazy val slick: Dep = - ivy"$lOrg::akka-projection-slick:$V" - lazy val jdbc: Dep = - ivy"$lOrg::akka-projection-jdbc:$V" - } - - object profiles { - // TODO: deal with cross-version for Scala3 (for3Use2_13) - lazy val eventsourcedJdbcProjection: Agg[Dep] = Agg( - persistenceQuery, - projection.core, - projection.eventsourced, - projection.slick, - persistenceJdbc - ) ++ slick.default - } - } - } - - trait SlickLibs { - object slick { - val V = IWMaterials.Versions.slick - val org = "com.typesafe.slick" - - lazy val slick: Dep = ivy"$org::slick:$V" - lazy val hikaricp: Dep = ivy"$org::slick-hikaricp:$V" - lazy val default: Agg[Dep] = Agg(slick, hikaricp) - } - } - -} diff --git a/iw/build.sc b/iw/build.sc deleted file mode 100644 index fbbc3b8..0000000 --- a/iw/build.sc +++ /dev/null @@ -1,144 +0,0 @@ -import mill._, scalalib._, scalajslib._ - -import $file.bom - -object support { - val Deps = bom.IWMaterials.Deps - val Versions = bom.IWMaterials.Versions - - trait CommonModule extends ScalaModule { - def scalaVersion = "3.1.1" - def scalacOptions = Seq( - "-encoding", - "utf8", - "-deprecation", - "-explain-types", - "-explain", - "-feature", - "-language:experimental.macros", - "-language:higherKinds", - "-language:implicitConversions", - "-unchecked", - "-Xfatal-warnings", - "-Ykind-projector" - ) - } - - trait CommonJSModule extends CommonModule with ScalaJSModule { - def scalaJSVersion = "1.8.0" - } - - trait CrossPlatformModule extends Module { outer => - def ivyDeps: T[Agg[Dep]] = Agg[Dep]() - def jsDeps: T[Agg[Dep]] = Agg[Dep]() - def jvmDeps: T[Agg[Dep]] = Agg[Dep]() - def moduleDeps: Seq[Module] = Seq[Module]() - - trait PlatformModule extends CommonModule { - def platform: String - override def millSourcePath = outer.millSourcePath - override def ivyDeps = outer.ivyDeps() ++ (platform match { - case "js" => jsDeps() - case _ => jvmDeps() - }) - override def moduleDeps = outer.moduleDeps.collect { - case m: CrossPlatformModule => - platform match { - case "js" => m.js - case _ => m.jvm - } - case m: JavaModule => m - } - } - - trait JsModule extends PlatformModule with CommonJSModule { - def platform = "js" - } - - trait JvmModule extends PlatformModule { - def platform = "jvm" - } - - val js: JsModule - val jvm: JvmModule - } - - trait PureCrossModule extends CrossPlatformModule { - override object js extends JsModule - override object jvm extends JvmModule - } - - trait PureCrossSbtModule extends CrossPlatformModule { - override object js extends JsModule with SbtModule - override object jvm extends JvmModule with SbtModule - } - - trait FullCrossSbtModule extends CrossPlatformModule { - trait FullSources extends JavaModule { self: PlatformModule => - override def sources = T.sources( - millSourcePath / platform / "src" / "main" / "scala", - millSourcePath / "shared" / "src" / "main" / "scala" - ) - - override def resources = T.sources( - millSourcePath / platform / "src" / "main" / "resources", - millSourcePath / "shared" / "src" / "main" / "resources" - ) - } - - override object js extends JsModule with FullSources - override object jvm extends JvmModule with FullSources - } - -} - -import support._ - -object mongo extends CommonModule { - def ivyDeps = Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - ivy"org.mongodb.scala::mongo-scala-driver:4.2.3".withDottyCompat( - scalaVersion() - ) - ) -} - -object tapir extends FullCrossSbtModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson, Deps.zioJson) - def jvmDeps = Agg(Deps.tapirZIO, Deps.tapirZIOHttp4sServer) - def jsDeps = Agg(Deps.tapirSttpClient) -} - -object akkaPersistence extends CommonModule { - def millSourcePath = build.millSourcePath / "akka-persistence" - def ivyDeps = (Agg( - Deps.akka.persistenceQuery, - Deps.akka.persistenceJdbc, - Deps.akka.projection.core, - Deps.akka.projection.eventsourced, - Deps.akka.projection.slick - ) ++ Deps.slick.default).map(_.withDottyCompat(scalaVersion())) ++ Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - Deps.akka.persistence.withDottyCompat(scalaVersion()), - ivy"com.typesafe.akka::akka-cluster-sharding-typed:${Deps.akka.V}" - .withDottyCompat(scalaVersion()) - ) -} - -object ui extends CommonJSModule { - def ivyDeps = Agg( - Deps.zio, - Deps.laminar, - Deps.zioJson, - Deps.waypoint, - Deps.urlDsl, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) -} diff --git a/iw/domain.sc b/iw/domain.sc deleted file mode 100644 index b09c56b..0000000 --- a/iw/domain.sc +++ /dev/null @@ -1,78 +0,0 @@ -import mill._, scalalib._ - -import $file.{build => ff}, ff.support._ - -trait DomainModule extends Module { - // General extra model deps - def modelModules: Seq[Module] = Seq.empty - // General extra codecs deps - def codecsModules: Seq[Module] = Seq.empty - // General extra endpoints deps - def endpointsModules: Seq[Module] = Seq.empty - - // Implementation deps for repo - def repoModules: Seq[JavaModule] = Seq.empty - - object shared extends Module { - object model extends PureCrossModule { - def moduleDeps = modelModules - def ivyDeps = Agg(Deps.zioPrelude) - } - object codecs extends PureCrossModule { - def moduleDeps = Seq(model) ++ codecsModules - def ivyDeps = Agg(Deps.zioJson) - } - } - - trait CommonProjects extends Module { - object model extends PureCrossModule { - def ivyDeps = Agg(Deps.zioPrelude) - def moduleDeps = Seq(shared.model) - } - object codecs extends PureCrossModule { - def moduleDeps = - Seq(model, shared.model, shared.codecs, ff.tapir) - } - object endpoints extends PureCrossModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson) - def moduleDeps = Seq(model, codecs) ++ endpointsModules - } - object client extends CommonJSModule { - def moduleDeps = Seq(endpoints.js) - } - object components extends CommonJSModule { - def ivyDeps = Agg( - Deps.laminar, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) - def moduleDeps = Seq(model.js, codecs.js, client, ff.ui) - } - } - - object query extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(repo, query.endpoints.jvm) - } - object repo extends CommonModule { - def ivyDeps = Agg(Deps.zio) - def moduleDeps = Seq(model.jvm, codecs.jvm) ++ repoModules - } - object projection extends CommonModule { - def moduleDeps = Seq(repo, ff.akkaPersistence) - } - } - - object command extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(entity, command.endpoints.jvm) - } - object entity extends CommonModule { - def moduleDeps = Seq(model.jvm, codecs.jvm, ff.akkaPersistence) - } - } -} diff --git a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala b/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala deleted file mode 100644 index 0fa3493..0000000 --- a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala +++ /dev/null @@ -1,53 +0,0 @@ -package works.iterative.mongo - -import zio.* -import zio.json.* -import zio.config.* -import org.mongodb.scala.* -import org.mongodb.scala.model.Filters.* -import org.bson.json.JsonObject -import org.mongodb.scala.model.ReplaceOptions -import org.mongodb.scala.bson.conversions.Bson - -case class MongoConfig(uri: String) - -object MongoConfig: - val configDesc = - import ConfigDescriptor.* - nested("MONGO")(string("URI").default("mongodb://localhost:27017")) - .to[MongoConfig] - val fromEnv = ZConfig.fromSystemEnv(configDesc) - -extension (m: MongoClient.type) - def layer: RLayer[MongoConfig, MongoClient] = - ZIO - .serviceWithZIO[MongoConfig](c => Task.attempt(MongoClient(c.uri))) - .toLayer - -class MongoJsonRepository[Elem, Key, Criteria]( - collection: MongoCollection[JsonObject], - toFilter: Criteria => Bson, - idFilter: Elem => (String, Key) -)(using JsonCodec[Elem]) { - def matching(criteria: Criteria): Task[Seq[Elem]] = - val filter = toFilter(criteria) - val query = collection.find(filter) - - for - result <- ZIO.fromFuture(_ => query.toFuture) - proof <- ZIO.collect(result)(j => - ZIO.fromOption(j.getJson.fromJson[Elem].toOption) - ) - yield proof - - def put(elem: Elem): Task[Unit] = - Task.async(cb => - collection - .replaceOne( - equal.tupled(idFilter(elem)), - JsonObject(elem.toJson), - ReplaceOptions().upsert(true) - ) - .subscribe(_ => cb(Task.unit), t => cb(Task.fail(t))) - ) -} diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala deleted file mode 100644 index 68d7196..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala +++ /dev/null @@ -1,12 +0,0 @@ -package works.iterative.tapir - -import sttp.model.Uri - -opaque type BaseUri = Option[Uri] - -object BaseUri: - - def apply(optU: Option[Uri]): BaseUri = optU - def apply(u: Uri): BaseUri = Some(u) - - extension (v: BaseUri) def toUri: Option[Uri] = v diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 653bc51..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,22 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.client.sttp.SttpClientInterpreter -import sttp.tapir.PublicEndpoint -import sttp.tapir.client.sttp.WebSocketToPipe -import scala.concurrent.Future -import sttp.client3.SttpBackend -import sttp.capabilities.WebSockets - -trait CustomTapirPlatformSpecific extends SttpClientInterpreter: - self: CustomTapir => - - type Backend = SttpBackend[Future, WebSockets] - - def makeClient[I, E, O]( - endpoint: PublicEndpoint[I, E, O, Any] - )(using - baseUri: BaseUri, - backend: Backend, - wsToPipe: WebSocketToPipe[Any] - ): I => Future[O] = - toClientThrowErrors(endpoint, baseUri.toUri, backend) diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 2545fbd..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,5 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.ztapir.ZTapir - -trait CustomTapirPlatformSpecific extends ZTapir diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala deleted file mode 100644 index 55e4ca1..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala +++ /dev/null @@ -1,7 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.server.http4s.ztapir.ZHttp4sServerInterpreter - -trait Http4sCustomTapir[Env] - extends CustomTapir - with ZHttp4sServerInterpreter[Env] diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala deleted file mode 100644 index dd52e9b..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala +++ /dev/null @@ -1,14 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.Tapir -import sttp.tapir.json.zio.TapirJsonZio -import sttp.tapir.TapirAliases - -trait CustomTapir - extends Tapir - with TapirJsonZio - with TapirAliases - with CustomTapirPlatformSpecific: - given Schema[ServerError] = Schema.derived - -object CustomTapir extends CustomTapir diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala deleted file mode 100644 index 715591d..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.tapir - -import zio.json.* - -sealed trait ServerError -case class InternalServerError(msg: String) extends ServerError -object InternalServerError: - def fromThrowable(t: Throwable): ServerError = InternalServerError( - t.getMessage - ) - -object ServerError: - given JsonCodec[ServerError] = DeriveJsonCodec.gen diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/File.scala deleted file mode 100644 index 09f6c07..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala +++ /dev/null @@ -1,3 +0,0 @@ -package works.iterative.services.files - -case class File(url: String, name: String) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala deleted file mode 100644 index 46633a5..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala +++ /dev/null @@ -1,25 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.Renderable - -given Renderable[File] with - extension (m: File) - def toHtml: HtmlElement = - li( - cls("pl-3 pr-4 py-3 flex items-center justify-between text-sm"), - div( - cls("w-0 flex-1 flex items-center"), - Icons.solid - .paperclip() - .amend(svg.cls := "flex-shrink-0 text-gray-400"), - span(cls("ml-2 flex-1 w-0 truncate"), m.name) - ), - a( - href(m.url), - cls("font-medium text-indigo-600 hover:text-indigo-500"), - "Otevřít" - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala deleted file mode 100644 index ac59db8..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import io.laminext.syntax.core.{*, given} - -def FileList(files: List[File]): HtmlElement = - ul( - role("list"), - cls("border border-gray-200 rounded-md divide-y divide-gray-200"), - files.map(_.toHtml) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala deleted file mode 100644 index 2e8f690..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala +++ /dev/null @@ -1,81 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import io.laminext.syntax.core.{*, given} - -object FilePicker: - sealed trait Event - sealed trait DoneEvent extends Event - case class SelectionUpdated(files: Set[File]) extends DoneEvent - case object SelectionCancelled extends DoneEvent - case object AvailableFilesRequested extends Event - - def apply( - currentFiles: Signal[List[File]], - availableFilesStream: EventStream[List[File]] - )(selectionUpdates: Observer[Event]): HtmlElement = - val (updatesStream, updatesObserver) = EventStream.withObserver[Event] - val selectorOpen = Var[Boolean](false) - - // This sequence tricks browser into displaying modal content centered - // Inspired by modal in headless ui playground - // https://github.com/tailwindlabs/headlessui/blob/fdd26297953080d5ec905dda0bf5ec9607897d86/packages/playground-react/pages/transitions/component-examples/modal.tsx#L78-L79 - inline def browserCenteringModalTrick: Modifier[HtmlElement] = - Seq[Modifier[HtmlElement]]( - span(cls("hidden sm:inline-block sm:h-screen sm:align-middle")), - "​" // Zero width space - ) - - inline def overlay: Modifier[HtmlElement] = - // Page overlay - /* TODO: transition - enter="ease-out duration-300" - enterFrom="opacity-0" - enterTo="opacity-100" - leave="ease-in duration-200" - leaveFrom="opacity-100" - leaveTo="opacity-0" - */ - div( - div( - onClick.mapTo(false) --> selectorOpen.writer, - cls("fixed inset-0 transition-opacity"), - div(cls("absolute inset-0 bg-gray-500 opacity-75")) - ) - ) - - inline def modalSelector: HtmlElement = - div( - cls("fixed inset-0 z-10 overflow-y-auto"), - div( - cls("text-center sm:block sm:p-0"), - overlay, - browserCenteringModalTrick, - child <-- currentFiles.map( - FileSelector(_, availableFilesStream)(updatesObserver) - ) - ) - ) - - div( - updatesStream --> selectionUpdates, - updatesStream.collect { case _: DoneEvent => - false - } --> selectorOpen.writer, - child.maybe <-- selectorOpen.signal.map(isOpen => - if isOpen then Some(modalSelector) else None - ), - div( - cls("flex flex-col space-y-5"), - child.maybe <-- currentFiles.map(files => - if files.isEmpty then None else Some(FileList(files)) - ), - button( - tpe := "button", - cls := "bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500", - "Zvolit soubory", - onClick.mapTo(true) --> selectorOpen.writer - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala deleted file mode 100644 index b43e07a..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala +++ /dev/null @@ -1,74 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.Loading -import io.laminext.syntax.core.{*, given} -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -object FileSelector: - import FilePicker._ - - def apply( - initialFiles: List[File], - availableFilesStream: EventStream[List[File]] - )(selectionUpdates: Observer[Event]): HtmlElement = - val selectedFiles = Var[Set[File]](initialFiles.to(Set)) - val availableFiles = availableFilesStream.startWithNone - // Request the files to display - selectionUpdates.onNext(AvailableFilesRequested) - div( - cls( - "inline-block transform overflow-hidden rounded-lg bg-white text-left align-bottom shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:align-middle" - ), - role("dialog"), - customHtmlAttr("aria.modal", BooleanAsTrueFalseStringCodec)(true), - aria.labelledBy("modal-headline"), - div( - cls("bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"), - div( - cls("sm:flex sm:items-start"), - div( - cls("mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"), - h3( - cls("text-lg font-medium leading-6 text-gray-900"), - idAttr("modal-headline"), - "Výběr souborů" - ) - ) - ), - child <-- availableFiles - .split(_ => ())((_, _, af) => FileTable(af, selectedFiles)) - .map(_.getOrElse(Loading)) - ), - div( - cls("bg-gray-50 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6"), - span( - cls("flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto"), - button( - typ("button"), - cls( - "focus:shadow-outline-green inline-flex w-full justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-base font-medium leading-6 text-white shadow-sm transition duration-150 ease-in-out hover:bg-indigo-500 focus:border-indigo-700 focus:outline-none sm:text-sm sm:leading-5" - ), - "Potvrdit", - composeEvents(onClick)( - _.sample(selectedFiles) - .map(SelectionUpdated(_)) - ) --> selectionUpdates - ) - ), - span( - cls("mt-3 flex w-full rounded-md shadow-sm sm:mt-0 sm:w-auto"), - button( - typ("button"), - cls( - "focus:shadow-outline-blue inline-flex w-full justify-center rounded-md border border-gray-300 bg-white px-4 py-2 text-base font-medium leading-6 text-gray-700 shadow-sm transition duration-150 ease-in-out hover:text-gray-500 focus:border-blue-300 focus:outline-none sm:text-sm sm:leading-5" - ), - "Zrušit", - onClick.mapTo(SelectionCancelled) --> selectionUpdates - ) - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala deleted file mode 100644 index aa11b2b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala +++ /dev/null @@ -1,91 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import works.iterative.ui.components.tailwind.Icons - -def FileTable( - files: Signal[List[File]], - selectedFiles: Var[Set[File]] -): HtmlElement = - val scope = customHtmlAttr("scope", StringAsIsCodec) - - def headerRow: HtmlElement = - val col = scope("col") - val baseM: Modifier[HtmlElement] = Seq(cls("px-6 py-3"), col) - val textH = cls( - "text-left text-xs font-medium text-gray-500 uppercase tracking-wider" - ) - tr( - th(baseM, span(cls("sr-only"), "Vybrat")), - th(baseM, textH, "Název"), - th(baseM, cls("relative"), span(cls("sr-only"), "Otevřít")) - ) - - def tableRow( - f: File, - idx: Int, - selected: Boolean - )(toggleSelection: Observer[Unit]): HtmlElement = - val baseC = cls("px-6 py-4 whitespace-nowrap text-sm") - tr( - cls(if idx % 2 == 0 then "bg-gray-50" else "bg-white"), - td( - cls("font-medium cursor-pointer"), - onClick.mapTo(()) --> toggleSelection, - cls(if selected then "text-green-900" else "text-gray-200"), - Icons.outline.`check-circle`().amend(svg.cls := "mx-auto"), - span(cls("sr-only"), if selected then "Vybráno" else "Nevybráno") - ), - td( - baseC, - cls("font-medium text-gray-900"), - f.name, - onClick.mapTo(()) --> toggleSelection - ), - td( - baseC, - cls("text-right font-medium"), - a( - href(f.url), - target("_blank"), - cls( - "flex items-center space-x-4 text-indigo-600 hover:text-indigo-900" - ), - Icons.outline.`external-link`(), - "Otevřít" - ) - ) - ) - - div( - cls("flex flex-col"), - div(cls("overflow-x-auto sm:-mx-6 lg:-mx-8")), - div( - cls("py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8"), - div( - cls("shadow overflow-hidden border-b border-gray-200 sm:rounded-lg"), - table( - cls("min-w-full divide-y divide-gray-200"), - thead( - cls("bg-gray-50"), - headerRow - ), - tbody( - children <-- files - .map(_.zipWithIndex) - .combineWithFn(selectedFiles)((f, sel) => - f.map((file, idx) => - val active = sel.contains(file) - tableRow(file, idx, active)( - selectedFiles.writer - .contramap(_ => if active then sel - file else sel + file) - ) - ) - ) - ) - ) - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala deleted file mode 100644 index 0fc8796..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala +++ /dev/null @@ -1,32 +0,0 @@ -package works.iterative.ui.components.tailwind - -import CustomAttrs.ariaHidden -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -// TODO: render icon or picture based on img signal -class Avatar($avatarImg: Signal[Option[String]]): - inline def avatarPlaceholder(size: Int): HtmlElement = - div( - cls := s"rounded-full text-indigo-200 bg-indigo-500 h-${size} w-${size} flex items-center justify-center", - Icons.outline.user(size - 2) - ) - - inline def avatarImage(size: Int): Signal[HtmlElement] = - $avatarImg.split(_ => ())((_, _, $url) => - img( - cls := s"w-$size h-$size rounded-full", - src <-- $url, - alt := "" - ) - ).map(_.getOrElse(avatarPlaceholder(size))) - - inline def avatar(size: Int): HtmlElement = - div( - cls := "relative", - child <-- avatarImage(size), - span( - cls := "absolute inset-0 shadow-inner rounded-full", - ariaHidden := true - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala deleted file mode 100644 index bcbed88..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala +++ /dev/null @@ -1,77 +0,0 @@ -package works.iterative.ui.components.tailwind - -enum ColorWeight(value: String): - inline def toCSS: String = value - // To be used for black and white until - // we support better mechanism via extension methods - case w__ extends ColorWeight("") - case w50 extends ColorWeight("50") - case w100 extends ColorWeight("100") - case w200 extends ColorWeight("200") - case w300 extends ColorWeight("300") - case w400 extends ColorWeight("400") - case w500 extends ColorWeight("500") - case w600 extends ColorWeight("600") - case w700 extends ColorWeight("700") - case w800 extends ColorWeight("800") - case w900 extends ColorWeight("900") - -enum Color(name: String): - import ColorWeight._ - - inline def toCSSNoColorWeight(prefix: String): String = - s"${prefix}-${name}" - inline def toCSSWithColorWeight( - prefix: String, - weight: ColorWeight - ): String = - s"${prefix}-${name}-${weight.toCSS}" - inline def toCSS(prefix: String)(weight: ColorWeight): String = - weight match { - case `w__` => toCSSNoColorWeight(prefix) - case _ => toCSSWithColorWeight(prefix, weight) - } - inline def bg: ColorWeight => String = toCSS("bg")(_) - inline def text: ColorWeight => String = toCSS("text")(_) - inline def decoration: ColorWeight => String = toCSS("decoration")(_) - inline def border: ColorWeight => String = toCSS("border")(_) - inline def outline: ColorWeight => String = toCSS("outline")(_) - inline def divide: ColorWeight => String = toCSS("divide")(_) - inline def ring: ColorWeight => String = toCSS("ring")(_) - inline def ringOffset: ColorWeight => String = toCSS("ring-offset")(_) - inline def shadow: ColorWeight => String = toCSS("shadow")(_) - inline def accent: ColorWeight => String = toCSS("accent")(_) - - // TODO: change the "stupid" methods to extension methods - // that will keep the invariants in comments lower - case current extends Color("current") - case inherit extends Color("inherit") - // Not present in for all methods - case transp extends Color("transparent") - // Seen in accent, not preset otherwise - case auto extends Color("auto") - // Black and white do not have weight - case black extends Color("black") - case white extends Color("white") - case slate extends Color("slate") - case gray extends Color("gray") - case zinc extends Color("zinc") - case neutral extends Color("neutral") - case stone extends Color("stone") - case red extends Color("red") - case orange extends Color("orange") - case amber extends Color("amber") - case yellow extends Color("yellow") - case lime extends Color("lime") - case green extends Color("green") - case emerald extends Color("emerald") - case teal extends Color("teal") - case cyan extends Color("cyan") - case sky extends Color("sky") - case blue extends Color("blue") - case indigo extends Color("indigo") - case violet extends Color("violet") - case purple extends Color("purple") - case fuchsia extends Color("fuchsia") - case pink extends Color("pink") - case rose extends Color("rose") diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala deleted file mode 100644 index 6fb453c..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -object CustomAttrs { - // Made a pull request to add aria-current to scala-dom-types, remove after - val ariaCurrent = customHtmlAttr("aria-current", StringAsIsCodec) - val ariaHidden = customHtmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - - val datetime = customHtmlAttr("datetime", StringAsIsCodec) - - object svg { - import com.raquo.laminar.api.L.svg.{*, given} - val ariaHidden = - customSvgAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - } -} diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala deleted file mode 100644 index 6cda48b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala +++ /dev/null @@ -1,28 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -object Display: - - enum Breakpoint: - case sm, md, lg, xl, `2xl` - - enum DisplayClass: - case block, `inline-block`, `inline`, flex, `inline-flex`, table, - `inline-table`, `table-caption` - - object ShowUpFrom: - inline def apply( - br: Breakpoint, - dc: DisplayClass = DisplayClass.block - ): HtmlElement = - div( - cls := "hidden", - cls := s"${br}:${dc}" - ) - - object HideUpTo: - inline def apply(br: Breakpoint): HtmlElement = - div( - cls := s"${br}:hidden" - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala deleted file mode 100644 index 4a8b065..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala +++ /dev/null @@ -1,270 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec -import com.raquo.domtypes.generic.defs.attrs.AriaAttrs -import com.raquo.laminar.api.L.svg.{*, given} -import com.raquo.laminar.builders.SvgBuilders -import com.raquo.laminar.keys.ReactiveSvgAttr - -// TODO: fix sizes, colors, hover and stuff, normalize and amend on call site -object Icons: - object aria: - inline def hidden = CustomAttrs.svg.ariaHidden - - object outline: - val defaultSize: Int = 6 - - inline def bell(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" - ) - ) - - inline def `check-circle`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" - ) - ) - - inline def `document-add`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M9 13h6m-3-3v6m5 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" - ) - ) - - inline def `external-link`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" - ) - ) - - inline def menu(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M4 6h16M4 12h16M4 18h16" - ) - ) - - inline def `status-offline`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M18.364 5.636a9 9 0 010 12.728m0 0l-2.829-2.829m2.829 2.829L21 21M15.536 8.464a5 5 0 010 7.072m0 0l-2.829-2.829m-4.243 2.829a4.978 4.978 0 01-1.414-2.83m-1.414 5.658a9 9 0 01-2.167-9.238m7.824 2.167a1 1 0 111.414 1.414m-1.414-1.414L3 3m8.293 8.293l1.414 1.414" - ) - ) - - inline def user(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" - ) - ) - - inline def x(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M6 18L18 6M6 6l12 12" - ) - ) - - end outline - - object solid: - val defaultSize: Int = 5 - - inline def users(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - d := "M9 6a3 3 0 11-6 0 3 3 0 016 0zM17 6a3 3 0 11-6 0 3 3 0 016 0zM12.93 17c.046-.327.07-.66.07-1a6.97 6.97 0 00-1.5-4.33A5 5 0 0119 16v1h-6.07zM6 11a5 5 0 015 5v1H1v-1a5 5 0 015-5z" - ) - ) - - inline def `location-marker`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M5.05 4.05a7 7 0 119.9 9.9L10 18.9l-4.95-4.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z", - clipRule := "evenodd" - ) - ) - - inline def calendar(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z", - clipRule := "evenodd" - ) - ) - - inline def `chevron-right`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z", - clipRule := "evenodd" - ) - ) - - inline def search(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z", - clipRule := "evenodd" - ) - ) - - inline def filter(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M3 3a1 1 0 011-1h12a1 1 0 011 1v3a1 1 0 01-.293.707L12 11.414V15a1 1 0 01-.293.707l-2 2A1 1 0 018 17v-5.586L3.293 6.707A1 1 0 013 6V3z", - clipRule := "evenodd" - ) - ) - - inline def `arrow-narrow-left`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M7.707 14.707a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 1.414L5.414 9H17a1 1 0 110 2H5.414l2.293 2.293a1 1 0 010 1.414z", - clipRule := "evenodd" - ) - ) - - inline def home(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - d := "M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z" - ) - ) - - inline def paperclip(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size} text-gray-400", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M8 4a3 3 0 00-3 3v4a5 5 0 0010 0V7a1 1 0 112 0v4a7 7 0 11-14 0V7a5 5 0 0110 0v4a3 3 0 11-6 0V7a1 1 0 012 0v4a1 1 0 102 0V7a3 3 0 00-3-3z", - clipRule := "evenodd" - ) - ) - - end solid -end Icons diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/LinkSupport.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/LinkSupport.scala deleted file mode 100644 index 88f0b9a..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/LinkSupport.scala +++ /dev/null @@ -1,12 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.jsdom.defs.events.TypedTargetMouseEvent - -object LinkSupport: - - extension [El <: org.scalajs.dom.EventTarget]( - ep: EventProcessor[TypedTargetMouseEvent[El], TypedTargetMouseEvent[El]] - ) - def noKeyMod = - ep.filter(ev => !(ev.ctrlKey || ev.metaKey || ev.shiftKey || ev.altKey)) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Loader.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Loader.scala deleted file mode 100644 index 5399933..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Loader.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -// TODO: proper loader -def Loading = - div( - cls := "bg-gray-50 overflow-hidden rounded-lg", - div( - cls := "px-4 py-5 sm:p-6", - "Loading..." - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Renderable.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Renderable.scala deleted file mode 100644 index a0fcc5b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Renderable.scala +++ /dev/null @@ -1,6 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -trait Renderable[A]: - extension (a: A) def toHtml: HtmlElement diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/ComboBox.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/ComboBox.scala deleted file mode 100644 index 8c5b8a1..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/ComboBox.scala +++ /dev/null @@ -1,92 +0,0 @@ -package works.iterative.ui.components.tailwind -package form - -import com.raquo.laminar.api.L.{*, given} -import io.laminext.syntax.core.* - -case class ComboBox( - id: String, - options: Signal[List[ComboBox.Option]], - valueUpdates: Observer[List[String]] -) - -object ComboBox: - - extension (m: ComboBox) - def toHtml: HtmlElement = - val isOpen = Var(false) - div( - cls := "relative mt-1", - input( - idAttr := m.id, - tpe := "text", - cls := "w-full rounded-md border border-gray-300 bg-white py-2 pl-3 pr-12 shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 sm:text-sm", - role := "combobox", - aria.controls := "options", - aria.expanded := false, - onClick.mapTo(true) --> isOpen.writer - ), - button( - tpe := "button", - cls := "absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-none", { - import svg.* - svg( - cls := "h-5 w-5 text-gray-400", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - CustomAttrs.svg.ariaHidden := true, - path( - fillRule := "evenodd", - d := "M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z", - clipRule := "evenodd" - ) - ) - } - ), - ul( - cls <-- isOpen.signal.switch("", "hidden"), - cls := "absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm", - idAttr := "options", - role := "listbox", - children <-- m.options.map(_.map(_.toHtml)) - ) - ) - - case class Option(value: String, active: Boolean) - - object Option: - extension (m: Option) - def toHtml: HtmlElement = - li( - cls := "relative cursor-default select-none py-2 pl-8 pr-4", - cls := (if m.active then "text-white bg-indigo-600" - else "text-gray-900"), - idAttr := "option-0", - role := "option", - tabIndex := -1, - span( - cls := "block truncate", - m.value - ), - if m.active then - span( - cls := "absolute inset-y-0 left-0 flex items-center pl-1.5", - cls := (if m.active then "text-white" else "text-indigo-600"), { - import svg.* - svg( - cls := "h-5 w-5", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - CustomAttrs.svg.ariaHidden := true, - path( - fillRule := "evenodd", - d := "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", - clipRule := "evenodd" - ) - ) - } - ) - else emptyNode - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/Form.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/Form.scala deleted file mode 100644 index 1b41dee..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/Form.scala +++ /dev/null @@ -1,17 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} -import org.scalajs.dom -import com.raquo.laminar.nodes.ReactiveHtmlElement - -object Form: - val Body = FormBody - val Section = FormSection - val Row = FormRow - - def apply(body: HtmlElement, buttons: HtmlElement): HtmlElement = - form( - cls := "space-y-8 divide-y divide-gray-200", - body, - buttons - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormBody.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormBody.scala deleted file mode 100644 index 89abc9c..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormBody.scala +++ /dev/null @@ -1,10 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} - -object FormBody: - def apply(sections: HtmlElement*): HtmlElement = - div( - cls := "space-y-8 divide-y divide-gray-200 sm:space-y-5", - sections - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormFields.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormFields.scala deleted file mode 100644 index a2e4cc6..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormFields.scala +++ /dev/null @@ -1,14 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.laminar.nodes.ReactiveHtmlElement -import org.scalajs.dom - -object FormFields: - def apply( - mods: Modifier[ReactiveHtmlElement[dom.HTMLElement]]* - ): HtmlElement = - div( - cls := "mt-6 sm:mt-5 space-y-6 sm:space-y-5", - mods - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormHeader.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormHeader.scala deleted file mode 100644 index 4083841..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormHeader.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} - -object FormHeader: - case class ViewModel(header: String, description: String) - def apply(m: ViewModel): HtmlElement = - div( - h3(cls := "text-lg leading-6 font-medium text-gray-900", m.header), - p(cls := "mt-1 max-w-2xl text-sm text-gray-500", m.description) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormRow.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormRow.scala deleted file mode 100644 index ffc5807..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormRow.scala +++ /dev/null @@ -1,22 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} - -case class FormRow(id: String, label: String, content: Modifier[Div]) - -object FormRow: - - extension (m: FormRow) - def toHtml: HtmlElement = - div( - cls := "sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5", - label( - forId := m.id, - cls := "block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2", - m.label - ), - div( - cls := "mt-1 sm:mt-0 sm:col-span-2", - m.content - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormSection.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormSection.scala deleted file mode 100644 index 50811ee..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormSection.scala +++ /dev/null @@ -1,15 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.laminar.nodes.ReactiveHtmlElement - -object FormSection: - def apply( - header: HtmlElement, - rows: HtmlElement* - ): HtmlElement = - div( - cls := "space-y-6 sm:space-y-5", - header, - rows - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/BaseList.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/BaseList.scala deleted file mode 100644 index 66506e9..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/BaseList.scala +++ /dev/null @@ -1,142 +0,0 @@ -package works.iterative.ui.components.tailwind -package list - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.waypoint.Router -import com.raquo.laminar.builders.HtmlTag -import org.scalajs.dom - -trait ListItem: - def key: String - def title: Modifier[HtmlElement] - def topRight: Modifier[HtmlElement] - def bottomLeft: Modifier[HtmlElement] - def bottomRight: Modifier[HtmlElement] - -trait ListRenderable[Item]: - extension (x: Item) def asItem: ListItem - -trait Navigable[Item]: - extension (x: Item) def navigate: Modifier[HtmlElement] - -object BaseList: - enum Color: - case Green, Yellow, Red - - case class IconText(text: HtmlElement, icon: SvgElement) - case class Tag(text: String, color: Color) - case class Row( - id: String, - title: String, - tag: Tag, - leftProps: List[IconText], - rightProp: IconText - ) - - trait AsRow[Data]: - extension (d: Data) def asRow: Row - - class RowListItem(d: Row) extends ListItem: - - def key: String = d.id - - def title: Modifier[HtmlElement] = d.title - - def topRight: Modifier[HtmlElement] = - inline def colorClass(color: Color): (String, Boolean) = - val c = color.toString.toLowerCase - s"bg-$c-100 text-$c-800" -> (d.tag.color == color) - - inline def colors = Map(Color.values.map(colorClass(_)): _*) - - p( - cls := "px-2 inline-flex text-xs leading-5 font-semibold rounded-full", - cls := colors, - d.tag.text - ) - - def bottomLeft: Modifier[HtmlElement] = - div( - cls := "sm:flex", - d.leftProps.zipWithIndex.map { case (i, idx) => - p( - cls := Map("mt-2 sm:mt-0 sm:ml-6" -> (idx == 0)), - cls := "flex items-center text-sm text-gray-500", - i.icon, - i.text - ) - } - ) - - def bottomRight: Modifier[HtmlElement] = - div( - cls := "mt-2 flex items-center text-sm text-gray-500 sm:mt-0", - d.rightProp.icon, - d.rightProp.text - ) - - object Row: - given asRowRenderable[T: AsRow]: ListRenderable[T] with - extension (d: T) def asItem = new RowListItem(d.asRow) - - end Row - -class BaseList[RowData: ListRenderable]: - - protected def containerElement: HtmlTag[dom.html.Element] = div - protected def containerMods(rowData: RowData): Modifier[HtmlElement] = - emptyMod - protected def farRight: Modifier[HtmlElement] = emptyMod - - def render($data: Signal[List[RowData]]): HtmlElement = - ul( - role := "list", - cls := "divide-y divide-gray-200", - children <-- $data.split(_.asItem.key)((_, d, _) => row(d)) - ) - - private def row(d: RowData): HtmlElement = - val data = d.asItem - li( - containerElement( - containerMods(d), - cls := "block hover:bg-gray-50", - div( - cls := "px-4 py-4 sm:px-6 items-center flex", - div( - cls := "min-w-0 flex-1 pr-4", - div( - cls := "flex items-center justify-between", - p( - cls := "text-sm font-medium text-indigo-600 truncate", - data.title - ), - div( - cls := "ml-2 flex-shrink-0 flex", - data.topRight - ) - ), - div( - cls := "mt-2 sm:flex sm:justify-between", - data.bottomLeft, - data.bottomRight - ) - ), - farRight - ) - ) - ) - -trait NavigableList[RowData: Navigable, Page](using router: Router[Page]) - extends BaseList[RowData]: - - override protected def containerElement: HtmlTag[dom.html.Element] = a - override protected def containerMods( - rowData: RowData - ): Modifier[HtmlElement] = - rowData.navigate - override protected def farRight: Modifier[HtmlElement] = - div( - cls := "flex-shrink-0", - Icons.solid.`chevron-right`().amend(svg.cls := "text-gray-400") - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/IconText.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/IconText.scala deleted file mode 100644 index 9f5a7b1..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/IconText.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.ui.components.tailwind -package list - -import com.raquo.laminar.api.L.{*, given} -import org.scalajs.dom -import com.raquo.laminar.builders.HtmlTag - -object IconText: - case class ViewModel(text: HtmlElement, icon: SvgElement) - def render($m: Signal[ViewModel]): HtmlElement = render($m, div) - def render( - $m: Signal[ViewModel], - container: HtmlTag[dom.html.Element] - ): HtmlElement = - container( - cls := "flex items-center text-sm text-gray-500", - child <-- $m.map(_.icon), - child <-- $m.map(_.text) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/ListRow.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/ListRow.scala deleted file mode 100644 index e5236a2..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/ListRow.scala +++ /dev/null @@ -1,47 +0,0 @@ -package works.iterative.ui.components.tailwind -package list - -import com.raquo.laminar.api.L.{*, given} -import org.scalajs.dom - -object ListRow: - case class ViewModel( - title: String, - topRight: Modifier[HtmlElement], - bottomLeft: Modifier[HtmlElement], - bottomRight: Modifier[HtmlElement], - farRight: Modifier[HtmlElement], - containerElement: HtmlElement = div() - ) - - def apply($m: Signal[ViewModel]): HtmlElement = - li( - child <-- $m.map(m => - m.containerElement.amend( - cls := "block hover:bg-gray-50", - div( - cls := "px-4 py-4 sm:px-6 items-center flex", - div( - cls := "min-w-0 flex-1 pr-4", - div( - cls := "flex items-center justify-between", - p( - cls := "text-sm font-medium text-indigo-600 truncate", - m.title - ), - div( - cls := "ml-2 flex-shrink-0 flex", - m.topRight - ) - ), - div( - cls := "mt-2 sm:flex sm:justify-between", - m.bottomLeft, - m.bottomRight - ) - ), - m.farRight - ) - ) - ) - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala deleted file mode 100644 index e7e6627..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.akka - -// Base class for all command-processing related exceptions from handlers -sealed abstract class CommandHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandNotAvailable[C, S](cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd není dostupný ve stavu $state" - ) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandRejected[C, S](reason: String, cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd byl ve stavu $state odmítnut s odůvodněním $reason" - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala deleted file mode 100644 index 72a5bf9..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.akka - -sealed abstract class EventHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -case class UnhandledEvent[Event, State](event: Event, state: State) - extends EventHandlerException( - s"Událost $event nastala ve stavu $state bez možnosti zpracování" - ) diff --git a/iw/bom.sc b/iw/bom.sc deleted file mode 100644 index e9266b8..0000000 --- a/iw/bom.sc +++ /dev/null @@ -1,227 +0,0 @@ -import mill._, scalalib._ - -object IWMaterials { - - object Versions { - val akka = "2.6.16" - val akkaHttp = "10.2.4" - val cats = "2.7.0" - val elastic4s = "7.12.2" - val http4s = "0.23.10" - val http4sPac4J = "4.0.0" - val laminar = "0.14.2" - val laminext = laminar - val logbackClassic = "1.2.10" - val pac4j = "5.2.0" - val play = "2.8.8" - val playJson = "2.9.2" - val scalaTest = "3.2.9" - val slick = "3.3.3" - val sttpClient = "3.5.0" - val tapir = "0.20.1" - val urlDsl = "0.4.0" - val waypoint = "0.5.0" - val zio = "2.0.0-RC2" - val zioConfig = "3.0.0-RC2" - val zioInteropCats = "3.3.0-RC2" - val zioJson = "0.3.0-RC3" - val zioLogging = "2.0.0-RC5" - val zioPrelude = "1.0.0-RC10" - val zioZMX = "0.0.11" - } - - object Deps extends AkkaLibs with SlickLibs { - import IWMaterials.{Versions => V} - - val zioOrg = "dev.zio" - - def zioLib(name: String, version: String): Dep = - ivy"$zioOrg::zio-$name::$version" - - lazy val zio: Dep = ivy"$zioOrg::zio:${V.zio}" - - lazy val zioTest: Dep = zioLib("test", V.zio) - lazy val zioTestSbt: Dep = zioLib("test", V.zio) - - lazy val zioConfig: Dep = zioLib("config", V.zioConfig) - lazy val zioConfigTypesafe: Dep = - zioLib("config-typesafe", V.zioConfig) - lazy val zioConfigMagnolia: Dep = - zioLib("config-magnolia", V.zioConfig) - - lazy val zioJson: Dep = zioLib("json", V.zioJson) - lazy val zioLogging: Dep = zioLib("logging", V.zioLogging) - lazy val zioLoggingSlf4j: Dep = - zioLib("logging-slf4j", V.zioLogging) - lazy val zioPrelude: Dep = zioLib("prelude", V.zioPrelude) - lazy val zioStreams: Dep = zioLib("streams", V.zio) - lazy val zioZMX: Dep = zioLib("zmx", V.zioZMX) - lazy val zioInteropCats: Dep = - zioLib("interop-cats", V.zioInteropCats) - - /* What is the equivalent? ZIOModule with prepared test config? - def useZIO(testConf: Configuration*): Agg[Dep] = Agg( - zio, - zioTest, - zioTestSbt, - testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework") - ) - */ - - /* - def useZIOAll(testConf: Configuration*): Seq[Def.Setting[_]] = - useZIO(testConf: _*) ++ Seq( - zioStreams, - zioConfig, - zioConfigTypesafe, - zioConfigMagnolia, - zioJson, - zioZMX, - zioLogging, - zioPrelude - ) - */ - - private val tapirOrg = "com.softwaremill.sttp.tapir" - def tapirLib(name: String): Dep = - ivy"${tapirOrg}::tapir-$name::${V.tapir}" - - lazy val tapirCore: Dep = tapirLib("core") - lazy val tapirZIO: Dep = tapirLib("zio") - lazy val tapirZIOJson: Dep = tapirLib("json-zio") - lazy val tapirSttpClient: Dep = tapirLib("sttp-client") - lazy val tapirCats: Dep = tapirLib("cats") - lazy val tapirZIOHttp4sServer: Dep = tapirLib("zio-http4s-server") - - private val sttpClientOrg = "com.softwaremill.sttp.client3" - def sttpClientLib(name: String): Dep = - ivy"${sttpClientOrg}::${name}:${V.sttpClient}" - - lazy val sttpClientCore: Dep = sttpClientLib("core") - - lazy val http4sBlazeServer: Dep = - ivy"org.http4s::http4s-blaze-server:${V.http4s}" - - lazy val http4sPac4J: Dep = - ivy"org.pac4j::http4s-pac4j:${V.http4sPac4J}" - lazy val pac4jOIDC: Dep = - ivy"org.pac4j:pac4j-oidc:${V.pac4j}" - - lazy val scalaTest: Dep = - ivy"org.scalatest::scalatest:${V.scalaTest}" - lazy val scalaTestPlusScalacheck: Dep = - ivy"org.scalatestplus::scalacheck-1-15:3.2.9.0" - lazy val playScalaTest: Dep = - ivy"org.scalatestplus.play::scalatestplus-play:5.1.0" - - private val playOrg = "com.typesafe.play" - lazy val playMailer: Dep = ivy"${playOrg}::play-mailer:8.0.1" - lazy val playServer: Dep = ivy"${playOrg}::play-server:${V.play}" - lazy val playAkkaServer: Dep = - ivy"${playOrg}::play-akka-http-server:${V.play}" - lazy val play: Dep = ivy"${playOrg}::play:${V.play}" - lazy val playAhcWs: Dep = ivy"${playOrg}::play-ahc-ws:${V.play}" - lazy val playJson: Dep = ivy"${playOrg}::play-json:${V.playJson}" - - private val elastic4sOrg = "com.sksamuel.elastic4s" - lazy val useElastic4S: Agg[Dep] = Agg( - ivy"${elastic4sOrg}::elastic4s-core:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-client-akka:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-http-streams:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-json-play:${V.elastic4s}" - ) - - lazy val laminar: Dep = ivy"com.raquo::laminar::${V.laminar}" - - private def laminext(name: String): Dep = - ivy"io.laminext::$name::${V.laminar}" - - lazy val laminextCore: Dep = laminext("core") - lazy val laminextTailwind: Dep = laminext("tailwind") - lazy val laminextFetch: Dep = laminext("fetch") - lazy val laminextValidationCore: Dep = laminext("validation-core") - lazy val laminextUI: Dep = laminext("ui") - - lazy val waypoint: Dep = - ivy"com.raquo::waypoint::${V.waypoint}" - - lazy val urlDsl: Dep = - ivy"be.doeraene::url-dsl::${V.urlDsl}" - - lazy val scalaJavaTime: Dep = - ivy"io.github.cquiroz::scala-java-time::2.3.0" - - lazy val scalaJavaLocales: Dep = - ivy"io.github.cquiroz::scala-java-locales::1.2.1" - - lazy val logbackClassic: Dep = - ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" - } - - trait AkkaLibs { - - self: SlickLibs => - - object akka { - val V = Versions.akka - val tOrg = "com.typesafe.akka" - val lOrg = "com.lightbend.akka" - - def akkaMod(name: String): Dep = - ivy"$tOrg::akka-$name:$V" - - lazy val actor: Dep = akkaMod("actor") - lazy val actorTyped: Dep = akkaMod("actor-typed") - lazy val stream: Dep = akkaMod("stream") - lazy val persistence: Dep = akkaMod("persistence-typed") - lazy val persistenceQuery: Dep = akkaMod("persistence-query") - lazy val persistenceJdbc: Dep = - ivy"$lOrg::akka-persistence-jdbc:5.0.4" - val persistenceTestKit: Dep = ivy"$tOrg::akka-persistence-testkit:$V" - - object http { - val V = Versions.akkaHttp - - lazy val http: Dep = ivy"$tOrg::akka-http:$V" - lazy val sprayJson: Dep = ivy"$tOrg::akka-http-spray-json:$V" - - } - - object projection { - val V = "1.2.2" - - lazy val core: Dep = - ivy"$lOrg::akka-projection-core:$V" - lazy val eventsourced: Dep = - ivy"$lOrg::akka-projection-eventsourced:$V" - lazy val slick: Dep = - ivy"$lOrg::akka-projection-slick:$V" - lazy val jdbc: Dep = - ivy"$lOrg::akka-projection-jdbc:$V" - } - - object profiles { - // TODO: deal with cross-version for Scala3 (for3Use2_13) - lazy val eventsourcedJdbcProjection: Agg[Dep] = Agg( - persistenceQuery, - projection.core, - projection.eventsourced, - projection.slick, - persistenceJdbc - ) ++ slick.default - } - } - } - - trait SlickLibs { - object slick { - val V = IWMaterials.Versions.slick - val org = "com.typesafe.slick" - - lazy val slick: Dep = ivy"$org::slick:$V" - lazy val hikaricp: Dep = ivy"$org::slick-hikaricp:$V" - lazy val default: Agg[Dep] = Agg(slick, hikaricp) - } - } - -} diff --git a/iw/build.sc b/iw/build.sc deleted file mode 100644 index fbbc3b8..0000000 --- a/iw/build.sc +++ /dev/null @@ -1,144 +0,0 @@ -import mill._, scalalib._, scalajslib._ - -import $file.bom - -object support { - val Deps = bom.IWMaterials.Deps - val Versions = bom.IWMaterials.Versions - - trait CommonModule extends ScalaModule { - def scalaVersion = "3.1.1" - def scalacOptions = Seq( - "-encoding", - "utf8", - "-deprecation", - "-explain-types", - "-explain", - "-feature", - "-language:experimental.macros", - "-language:higherKinds", - "-language:implicitConversions", - "-unchecked", - "-Xfatal-warnings", - "-Ykind-projector" - ) - } - - trait CommonJSModule extends CommonModule with ScalaJSModule { - def scalaJSVersion = "1.8.0" - } - - trait CrossPlatformModule extends Module { outer => - def ivyDeps: T[Agg[Dep]] = Agg[Dep]() - def jsDeps: T[Agg[Dep]] = Agg[Dep]() - def jvmDeps: T[Agg[Dep]] = Agg[Dep]() - def moduleDeps: Seq[Module] = Seq[Module]() - - trait PlatformModule extends CommonModule { - def platform: String - override def millSourcePath = outer.millSourcePath - override def ivyDeps = outer.ivyDeps() ++ (platform match { - case "js" => jsDeps() - case _ => jvmDeps() - }) - override def moduleDeps = outer.moduleDeps.collect { - case m: CrossPlatformModule => - platform match { - case "js" => m.js - case _ => m.jvm - } - case m: JavaModule => m - } - } - - trait JsModule extends PlatformModule with CommonJSModule { - def platform = "js" - } - - trait JvmModule extends PlatformModule { - def platform = "jvm" - } - - val js: JsModule - val jvm: JvmModule - } - - trait PureCrossModule extends CrossPlatformModule { - override object js extends JsModule - override object jvm extends JvmModule - } - - trait PureCrossSbtModule extends CrossPlatformModule { - override object js extends JsModule with SbtModule - override object jvm extends JvmModule with SbtModule - } - - trait FullCrossSbtModule extends CrossPlatformModule { - trait FullSources extends JavaModule { self: PlatformModule => - override def sources = T.sources( - millSourcePath / platform / "src" / "main" / "scala", - millSourcePath / "shared" / "src" / "main" / "scala" - ) - - override def resources = T.sources( - millSourcePath / platform / "src" / "main" / "resources", - millSourcePath / "shared" / "src" / "main" / "resources" - ) - } - - override object js extends JsModule with FullSources - override object jvm extends JvmModule with FullSources - } - -} - -import support._ - -object mongo extends CommonModule { - def ivyDeps = Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - ivy"org.mongodb.scala::mongo-scala-driver:4.2.3".withDottyCompat( - scalaVersion() - ) - ) -} - -object tapir extends FullCrossSbtModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson, Deps.zioJson) - def jvmDeps = Agg(Deps.tapirZIO, Deps.tapirZIOHttp4sServer) - def jsDeps = Agg(Deps.tapirSttpClient) -} - -object akkaPersistence extends CommonModule { - def millSourcePath = build.millSourcePath / "akka-persistence" - def ivyDeps = (Agg( - Deps.akka.persistenceQuery, - Deps.akka.persistenceJdbc, - Deps.akka.projection.core, - Deps.akka.projection.eventsourced, - Deps.akka.projection.slick - ) ++ Deps.slick.default).map(_.withDottyCompat(scalaVersion())) ++ Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - Deps.akka.persistence.withDottyCompat(scalaVersion()), - ivy"com.typesafe.akka::akka-cluster-sharding-typed:${Deps.akka.V}" - .withDottyCompat(scalaVersion()) - ) -} - -object ui extends CommonJSModule { - def ivyDeps = Agg( - Deps.zio, - Deps.laminar, - Deps.zioJson, - Deps.waypoint, - Deps.urlDsl, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) -} diff --git a/iw/domain.sc b/iw/domain.sc deleted file mode 100644 index b09c56b..0000000 --- a/iw/domain.sc +++ /dev/null @@ -1,78 +0,0 @@ -import mill._, scalalib._ - -import $file.{build => ff}, ff.support._ - -trait DomainModule extends Module { - // General extra model deps - def modelModules: Seq[Module] = Seq.empty - // General extra codecs deps - def codecsModules: Seq[Module] = Seq.empty - // General extra endpoints deps - def endpointsModules: Seq[Module] = Seq.empty - - // Implementation deps for repo - def repoModules: Seq[JavaModule] = Seq.empty - - object shared extends Module { - object model extends PureCrossModule { - def moduleDeps = modelModules - def ivyDeps = Agg(Deps.zioPrelude) - } - object codecs extends PureCrossModule { - def moduleDeps = Seq(model) ++ codecsModules - def ivyDeps = Agg(Deps.zioJson) - } - } - - trait CommonProjects extends Module { - object model extends PureCrossModule { - def ivyDeps = Agg(Deps.zioPrelude) - def moduleDeps = Seq(shared.model) - } - object codecs extends PureCrossModule { - def moduleDeps = - Seq(model, shared.model, shared.codecs, ff.tapir) - } - object endpoints extends PureCrossModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson) - def moduleDeps = Seq(model, codecs) ++ endpointsModules - } - object client extends CommonJSModule { - def moduleDeps = Seq(endpoints.js) - } - object components extends CommonJSModule { - def ivyDeps = Agg( - Deps.laminar, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) - def moduleDeps = Seq(model.js, codecs.js, client, ff.ui) - } - } - - object query extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(repo, query.endpoints.jvm) - } - object repo extends CommonModule { - def ivyDeps = Agg(Deps.zio) - def moduleDeps = Seq(model.jvm, codecs.jvm) ++ repoModules - } - object projection extends CommonModule { - def moduleDeps = Seq(repo, ff.akkaPersistence) - } - } - - object command extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(entity, command.endpoints.jvm) - } - object entity extends CommonModule { - def moduleDeps = Seq(model.jvm, codecs.jvm, ff.akkaPersistence) - } - } -} diff --git a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala b/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala deleted file mode 100644 index 0fa3493..0000000 --- a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala +++ /dev/null @@ -1,53 +0,0 @@ -package works.iterative.mongo - -import zio.* -import zio.json.* -import zio.config.* -import org.mongodb.scala.* -import org.mongodb.scala.model.Filters.* -import org.bson.json.JsonObject -import org.mongodb.scala.model.ReplaceOptions -import org.mongodb.scala.bson.conversions.Bson - -case class MongoConfig(uri: String) - -object MongoConfig: - val configDesc = - import ConfigDescriptor.* - nested("MONGO")(string("URI").default("mongodb://localhost:27017")) - .to[MongoConfig] - val fromEnv = ZConfig.fromSystemEnv(configDesc) - -extension (m: MongoClient.type) - def layer: RLayer[MongoConfig, MongoClient] = - ZIO - .serviceWithZIO[MongoConfig](c => Task.attempt(MongoClient(c.uri))) - .toLayer - -class MongoJsonRepository[Elem, Key, Criteria]( - collection: MongoCollection[JsonObject], - toFilter: Criteria => Bson, - idFilter: Elem => (String, Key) -)(using JsonCodec[Elem]) { - def matching(criteria: Criteria): Task[Seq[Elem]] = - val filter = toFilter(criteria) - val query = collection.find(filter) - - for - result <- ZIO.fromFuture(_ => query.toFuture) - proof <- ZIO.collect(result)(j => - ZIO.fromOption(j.getJson.fromJson[Elem].toOption) - ) - yield proof - - def put(elem: Elem): Task[Unit] = - Task.async(cb => - collection - .replaceOne( - equal.tupled(idFilter(elem)), - JsonObject(elem.toJson), - ReplaceOptions().upsert(true) - ) - .subscribe(_ => cb(Task.unit), t => cb(Task.fail(t))) - ) -} diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala deleted file mode 100644 index 68d7196..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala +++ /dev/null @@ -1,12 +0,0 @@ -package works.iterative.tapir - -import sttp.model.Uri - -opaque type BaseUri = Option[Uri] - -object BaseUri: - - def apply(optU: Option[Uri]): BaseUri = optU - def apply(u: Uri): BaseUri = Some(u) - - extension (v: BaseUri) def toUri: Option[Uri] = v diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 653bc51..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,22 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.client.sttp.SttpClientInterpreter -import sttp.tapir.PublicEndpoint -import sttp.tapir.client.sttp.WebSocketToPipe -import scala.concurrent.Future -import sttp.client3.SttpBackend -import sttp.capabilities.WebSockets - -trait CustomTapirPlatformSpecific extends SttpClientInterpreter: - self: CustomTapir => - - type Backend = SttpBackend[Future, WebSockets] - - def makeClient[I, E, O]( - endpoint: PublicEndpoint[I, E, O, Any] - )(using - baseUri: BaseUri, - backend: Backend, - wsToPipe: WebSocketToPipe[Any] - ): I => Future[O] = - toClientThrowErrors(endpoint, baseUri.toUri, backend) diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 2545fbd..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,5 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.ztapir.ZTapir - -trait CustomTapirPlatformSpecific extends ZTapir diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala deleted file mode 100644 index 55e4ca1..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala +++ /dev/null @@ -1,7 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.server.http4s.ztapir.ZHttp4sServerInterpreter - -trait Http4sCustomTapir[Env] - extends CustomTapir - with ZHttp4sServerInterpreter[Env] diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala deleted file mode 100644 index dd52e9b..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala +++ /dev/null @@ -1,14 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.Tapir -import sttp.tapir.json.zio.TapirJsonZio -import sttp.tapir.TapirAliases - -trait CustomTapir - extends Tapir - with TapirJsonZio - with TapirAliases - with CustomTapirPlatformSpecific: - given Schema[ServerError] = Schema.derived - -object CustomTapir extends CustomTapir diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala deleted file mode 100644 index 715591d..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.tapir - -import zio.json.* - -sealed trait ServerError -case class InternalServerError(msg: String) extends ServerError -object InternalServerError: - def fromThrowable(t: Throwable): ServerError = InternalServerError( - t.getMessage - ) - -object ServerError: - given JsonCodec[ServerError] = DeriveJsonCodec.gen diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/File.scala deleted file mode 100644 index 09f6c07..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala +++ /dev/null @@ -1,3 +0,0 @@ -package works.iterative.services.files - -case class File(url: String, name: String) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala deleted file mode 100644 index 46633a5..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala +++ /dev/null @@ -1,25 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.Renderable - -given Renderable[File] with - extension (m: File) - def toHtml: HtmlElement = - li( - cls("pl-3 pr-4 py-3 flex items-center justify-between text-sm"), - div( - cls("w-0 flex-1 flex items-center"), - Icons.solid - .paperclip() - .amend(svg.cls := "flex-shrink-0 text-gray-400"), - span(cls("ml-2 flex-1 w-0 truncate"), m.name) - ), - a( - href(m.url), - cls("font-medium text-indigo-600 hover:text-indigo-500"), - "Otevřít" - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala deleted file mode 100644 index ac59db8..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import io.laminext.syntax.core.{*, given} - -def FileList(files: List[File]): HtmlElement = - ul( - role("list"), - cls("border border-gray-200 rounded-md divide-y divide-gray-200"), - files.map(_.toHtml) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala deleted file mode 100644 index 2e8f690..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala +++ /dev/null @@ -1,81 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import io.laminext.syntax.core.{*, given} - -object FilePicker: - sealed trait Event - sealed trait DoneEvent extends Event - case class SelectionUpdated(files: Set[File]) extends DoneEvent - case object SelectionCancelled extends DoneEvent - case object AvailableFilesRequested extends Event - - def apply( - currentFiles: Signal[List[File]], - availableFilesStream: EventStream[List[File]] - )(selectionUpdates: Observer[Event]): HtmlElement = - val (updatesStream, updatesObserver) = EventStream.withObserver[Event] - val selectorOpen = Var[Boolean](false) - - // This sequence tricks browser into displaying modal content centered - // Inspired by modal in headless ui playground - // https://github.com/tailwindlabs/headlessui/blob/fdd26297953080d5ec905dda0bf5ec9607897d86/packages/playground-react/pages/transitions/component-examples/modal.tsx#L78-L79 - inline def browserCenteringModalTrick: Modifier[HtmlElement] = - Seq[Modifier[HtmlElement]]( - span(cls("hidden sm:inline-block sm:h-screen sm:align-middle")), - "​" // Zero width space - ) - - inline def overlay: Modifier[HtmlElement] = - // Page overlay - /* TODO: transition - enter="ease-out duration-300" - enterFrom="opacity-0" - enterTo="opacity-100" - leave="ease-in duration-200" - leaveFrom="opacity-100" - leaveTo="opacity-0" - */ - div( - div( - onClick.mapTo(false) --> selectorOpen.writer, - cls("fixed inset-0 transition-opacity"), - div(cls("absolute inset-0 bg-gray-500 opacity-75")) - ) - ) - - inline def modalSelector: HtmlElement = - div( - cls("fixed inset-0 z-10 overflow-y-auto"), - div( - cls("text-center sm:block sm:p-0"), - overlay, - browserCenteringModalTrick, - child <-- currentFiles.map( - FileSelector(_, availableFilesStream)(updatesObserver) - ) - ) - ) - - div( - updatesStream --> selectionUpdates, - updatesStream.collect { case _: DoneEvent => - false - } --> selectorOpen.writer, - child.maybe <-- selectorOpen.signal.map(isOpen => - if isOpen then Some(modalSelector) else None - ), - div( - cls("flex flex-col space-y-5"), - child.maybe <-- currentFiles.map(files => - if files.isEmpty then None else Some(FileList(files)) - ), - button( - tpe := "button", - cls := "bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500", - "Zvolit soubory", - onClick.mapTo(true) --> selectorOpen.writer - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala deleted file mode 100644 index b43e07a..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala +++ /dev/null @@ -1,74 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.Loading -import io.laminext.syntax.core.{*, given} -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -object FileSelector: - import FilePicker._ - - def apply( - initialFiles: List[File], - availableFilesStream: EventStream[List[File]] - )(selectionUpdates: Observer[Event]): HtmlElement = - val selectedFiles = Var[Set[File]](initialFiles.to(Set)) - val availableFiles = availableFilesStream.startWithNone - // Request the files to display - selectionUpdates.onNext(AvailableFilesRequested) - div( - cls( - "inline-block transform overflow-hidden rounded-lg bg-white text-left align-bottom shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:align-middle" - ), - role("dialog"), - customHtmlAttr("aria.modal", BooleanAsTrueFalseStringCodec)(true), - aria.labelledBy("modal-headline"), - div( - cls("bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"), - div( - cls("sm:flex sm:items-start"), - div( - cls("mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"), - h3( - cls("text-lg font-medium leading-6 text-gray-900"), - idAttr("modal-headline"), - "Výběr souborů" - ) - ) - ), - child <-- availableFiles - .split(_ => ())((_, _, af) => FileTable(af, selectedFiles)) - .map(_.getOrElse(Loading)) - ), - div( - cls("bg-gray-50 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6"), - span( - cls("flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto"), - button( - typ("button"), - cls( - "focus:shadow-outline-green inline-flex w-full justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-base font-medium leading-6 text-white shadow-sm transition duration-150 ease-in-out hover:bg-indigo-500 focus:border-indigo-700 focus:outline-none sm:text-sm sm:leading-5" - ), - "Potvrdit", - composeEvents(onClick)( - _.sample(selectedFiles) - .map(SelectionUpdated(_)) - ) --> selectionUpdates - ) - ), - span( - cls("mt-3 flex w-full rounded-md shadow-sm sm:mt-0 sm:w-auto"), - button( - typ("button"), - cls( - "focus:shadow-outline-blue inline-flex w-full justify-center rounded-md border border-gray-300 bg-white px-4 py-2 text-base font-medium leading-6 text-gray-700 shadow-sm transition duration-150 ease-in-out hover:text-gray-500 focus:border-blue-300 focus:outline-none sm:text-sm sm:leading-5" - ), - "Zrušit", - onClick.mapTo(SelectionCancelled) --> selectionUpdates - ) - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala deleted file mode 100644 index aa11b2b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala +++ /dev/null @@ -1,91 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import works.iterative.ui.components.tailwind.Icons - -def FileTable( - files: Signal[List[File]], - selectedFiles: Var[Set[File]] -): HtmlElement = - val scope = customHtmlAttr("scope", StringAsIsCodec) - - def headerRow: HtmlElement = - val col = scope("col") - val baseM: Modifier[HtmlElement] = Seq(cls("px-6 py-3"), col) - val textH = cls( - "text-left text-xs font-medium text-gray-500 uppercase tracking-wider" - ) - tr( - th(baseM, span(cls("sr-only"), "Vybrat")), - th(baseM, textH, "Název"), - th(baseM, cls("relative"), span(cls("sr-only"), "Otevřít")) - ) - - def tableRow( - f: File, - idx: Int, - selected: Boolean - )(toggleSelection: Observer[Unit]): HtmlElement = - val baseC = cls("px-6 py-4 whitespace-nowrap text-sm") - tr( - cls(if idx % 2 == 0 then "bg-gray-50" else "bg-white"), - td( - cls("font-medium cursor-pointer"), - onClick.mapTo(()) --> toggleSelection, - cls(if selected then "text-green-900" else "text-gray-200"), - Icons.outline.`check-circle`().amend(svg.cls := "mx-auto"), - span(cls("sr-only"), if selected then "Vybráno" else "Nevybráno") - ), - td( - baseC, - cls("font-medium text-gray-900"), - f.name, - onClick.mapTo(()) --> toggleSelection - ), - td( - baseC, - cls("text-right font-medium"), - a( - href(f.url), - target("_blank"), - cls( - "flex items-center space-x-4 text-indigo-600 hover:text-indigo-900" - ), - Icons.outline.`external-link`(), - "Otevřít" - ) - ) - ) - - div( - cls("flex flex-col"), - div(cls("overflow-x-auto sm:-mx-6 lg:-mx-8")), - div( - cls("py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8"), - div( - cls("shadow overflow-hidden border-b border-gray-200 sm:rounded-lg"), - table( - cls("min-w-full divide-y divide-gray-200"), - thead( - cls("bg-gray-50"), - headerRow - ), - tbody( - children <-- files - .map(_.zipWithIndex) - .combineWithFn(selectedFiles)((f, sel) => - f.map((file, idx) => - val active = sel.contains(file) - tableRow(file, idx, active)( - selectedFiles.writer - .contramap(_ => if active then sel - file else sel + file) - ) - ) - ) - ) - ) - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala deleted file mode 100644 index 0fc8796..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala +++ /dev/null @@ -1,32 +0,0 @@ -package works.iterative.ui.components.tailwind - -import CustomAttrs.ariaHidden -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -// TODO: render icon or picture based on img signal -class Avatar($avatarImg: Signal[Option[String]]): - inline def avatarPlaceholder(size: Int): HtmlElement = - div( - cls := s"rounded-full text-indigo-200 bg-indigo-500 h-${size} w-${size} flex items-center justify-center", - Icons.outline.user(size - 2) - ) - - inline def avatarImage(size: Int): Signal[HtmlElement] = - $avatarImg.split(_ => ())((_, _, $url) => - img( - cls := s"w-$size h-$size rounded-full", - src <-- $url, - alt := "" - ) - ).map(_.getOrElse(avatarPlaceholder(size))) - - inline def avatar(size: Int): HtmlElement = - div( - cls := "relative", - child <-- avatarImage(size), - span( - cls := "absolute inset-0 shadow-inner rounded-full", - ariaHidden := true - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala deleted file mode 100644 index bcbed88..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala +++ /dev/null @@ -1,77 +0,0 @@ -package works.iterative.ui.components.tailwind - -enum ColorWeight(value: String): - inline def toCSS: String = value - // To be used for black and white until - // we support better mechanism via extension methods - case w__ extends ColorWeight("") - case w50 extends ColorWeight("50") - case w100 extends ColorWeight("100") - case w200 extends ColorWeight("200") - case w300 extends ColorWeight("300") - case w400 extends ColorWeight("400") - case w500 extends ColorWeight("500") - case w600 extends ColorWeight("600") - case w700 extends ColorWeight("700") - case w800 extends ColorWeight("800") - case w900 extends ColorWeight("900") - -enum Color(name: String): - import ColorWeight._ - - inline def toCSSNoColorWeight(prefix: String): String = - s"${prefix}-${name}" - inline def toCSSWithColorWeight( - prefix: String, - weight: ColorWeight - ): String = - s"${prefix}-${name}-${weight.toCSS}" - inline def toCSS(prefix: String)(weight: ColorWeight): String = - weight match { - case `w__` => toCSSNoColorWeight(prefix) - case _ => toCSSWithColorWeight(prefix, weight) - } - inline def bg: ColorWeight => String = toCSS("bg")(_) - inline def text: ColorWeight => String = toCSS("text")(_) - inline def decoration: ColorWeight => String = toCSS("decoration")(_) - inline def border: ColorWeight => String = toCSS("border")(_) - inline def outline: ColorWeight => String = toCSS("outline")(_) - inline def divide: ColorWeight => String = toCSS("divide")(_) - inline def ring: ColorWeight => String = toCSS("ring")(_) - inline def ringOffset: ColorWeight => String = toCSS("ring-offset")(_) - inline def shadow: ColorWeight => String = toCSS("shadow")(_) - inline def accent: ColorWeight => String = toCSS("accent")(_) - - // TODO: change the "stupid" methods to extension methods - // that will keep the invariants in comments lower - case current extends Color("current") - case inherit extends Color("inherit") - // Not present in for all methods - case transp extends Color("transparent") - // Seen in accent, not preset otherwise - case auto extends Color("auto") - // Black and white do not have weight - case black extends Color("black") - case white extends Color("white") - case slate extends Color("slate") - case gray extends Color("gray") - case zinc extends Color("zinc") - case neutral extends Color("neutral") - case stone extends Color("stone") - case red extends Color("red") - case orange extends Color("orange") - case amber extends Color("amber") - case yellow extends Color("yellow") - case lime extends Color("lime") - case green extends Color("green") - case emerald extends Color("emerald") - case teal extends Color("teal") - case cyan extends Color("cyan") - case sky extends Color("sky") - case blue extends Color("blue") - case indigo extends Color("indigo") - case violet extends Color("violet") - case purple extends Color("purple") - case fuchsia extends Color("fuchsia") - case pink extends Color("pink") - case rose extends Color("rose") diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala deleted file mode 100644 index 6fb453c..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -object CustomAttrs { - // Made a pull request to add aria-current to scala-dom-types, remove after - val ariaCurrent = customHtmlAttr("aria-current", StringAsIsCodec) - val ariaHidden = customHtmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - - val datetime = customHtmlAttr("datetime", StringAsIsCodec) - - object svg { - import com.raquo.laminar.api.L.svg.{*, given} - val ariaHidden = - customSvgAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - } -} diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala deleted file mode 100644 index 6cda48b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala +++ /dev/null @@ -1,28 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -object Display: - - enum Breakpoint: - case sm, md, lg, xl, `2xl` - - enum DisplayClass: - case block, `inline-block`, `inline`, flex, `inline-flex`, table, - `inline-table`, `table-caption` - - object ShowUpFrom: - inline def apply( - br: Breakpoint, - dc: DisplayClass = DisplayClass.block - ): HtmlElement = - div( - cls := "hidden", - cls := s"${br}:${dc}" - ) - - object HideUpTo: - inline def apply(br: Breakpoint): HtmlElement = - div( - cls := s"${br}:hidden" - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala deleted file mode 100644 index 4a8b065..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala +++ /dev/null @@ -1,270 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec -import com.raquo.domtypes.generic.defs.attrs.AriaAttrs -import com.raquo.laminar.api.L.svg.{*, given} -import com.raquo.laminar.builders.SvgBuilders -import com.raquo.laminar.keys.ReactiveSvgAttr - -// TODO: fix sizes, colors, hover and stuff, normalize and amend on call site -object Icons: - object aria: - inline def hidden = CustomAttrs.svg.ariaHidden - - object outline: - val defaultSize: Int = 6 - - inline def bell(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" - ) - ) - - inline def `check-circle`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" - ) - ) - - inline def `document-add`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M9 13h6m-3-3v6m5 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" - ) - ) - - inline def `external-link`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" - ) - ) - - inline def menu(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M4 6h16M4 12h16M4 18h16" - ) - ) - - inline def `status-offline`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M18.364 5.636a9 9 0 010 12.728m0 0l-2.829-2.829m2.829 2.829L21 21M15.536 8.464a5 5 0 010 7.072m0 0l-2.829-2.829m-4.243 2.829a4.978 4.978 0 01-1.414-2.83m-1.414 5.658a9 9 0 01-2.167-9.238m7.824 2.167a1 1 0 111.414 1.414m-1.414-1.414L3 3m8.293 8.293l1.414 1.414" - ) - ) - - inline def user(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" - ) - ) - - inline def x(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M6 18L18 6M6 6l12 12" - ) - ) - - end outline - - object solid: - val defaultSize: Int = 5 - - inline def users(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - d := "M9 6a3 3 0 11-6 0 3 3 0 016 0zM17 6a3 3 0 11-6 0 3 3 0 016 0zM12.93 17c.046-.327.07-.66.07-1a6.97 6.97 0 00-1.5-4.33A5 5 0 0119 16v1h-6.07zM6 11a5 5 0 015 5v1H1v-1a5 5 0 015-5z" - ) - ) - - inline def `location-marker`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M5.05 4.05a7 7 0 119.9 9.9L10 18.9l-4.95-4.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z", - clipRule := "evenodd" - ) - ) - - inline def calendar(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z", - clipRule := "evenodd" - ) - ) - - inline def `chevron-right`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z", - clipRule := "evenodd" - ) - ) - - inline def search(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z", - clipRule := "evenodd" - ) - ) - - inline def filter(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M3 3a1 1 0 011-1h12a1 1 0 011 1v3a1 1 0 01-.293.707L12 11.414V15a1 1 0 01-.293.707l-2 2A1 1 0 018 17v-5.586L3.293 6.707A1 1 0 013 6V3z", - clipRule := "evenodd" - ) - ) - - inline def `arrow-narrow-left`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M7.707 14.707a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 1.414L5.414 9H17a1 1 0 110 2H5.414l2.293 2.293a1 1 0 010 1.414z", - clipRule := "evenodd" - ) - ) - - inline def home(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - d := "M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z" - ) - ) - - inline def paperclip(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size} text-gray-400", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M8 4a3 3 0 00-3 3v4a5 5 0 0010 0V7a1 1 0 112 0v4a7 7 0 11-14 0V7a5 5 0 0110 0v4a3 3 0 11-6 0V7a1 1 0 012 0v4a1 1 0 102 0V7a3 3 0 00-3-3z", - clipRule := "evenodd" - ) - ) - - end solid -end Icons diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/LinkSupport.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/LinkSupport.scala deleted file mode 100644 index 88f0b9a..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/LinkSupport.scala +++ /dev/null @@ -1,12 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.jsdom.defs.events.TypedTargetMouseEvent - -object LinkSupport: - - extension [El <: org.scalajs.dom.EventTarget]( - ep: EventProcessor[TypedTargetMouseEvent[El], TypedTargetMouseEvent[El]] - ) - def noKeyMod = - ep.filter(ev => !(ev.ctrlKey || ev.metaKey || ev.shiftKey || ev.altKey)) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Loader.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Loader.scala deleted file mode 100644 index 5399933..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Loader.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -// TODO: proper loader -def Loading = - div( - cls := "bg-gray-50 overflow-hidden rounded-lg", - div( - cls := "px-4 py-5 sm:p-6", - "Loading..." - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Renderable.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Renderable.scala deleted file mode 100644 index a0fcc5b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Renderable.scala +++ /dev/null @@ -1,6 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -trait Renderable[A]: - extension (a: A) def toHtml: HtmlElement diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/ComboBox.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/ComboBox.scala deleted file mode 100644 index 8c5b8a1..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/ComboBox.scala +++ /dev/null @@ -1,92 +0,0 @@ -package works.iterative.ui.components.tailwind -package form - -import com.raquo.laminar.api.L.{*, given} -import io.laminext.syntax.core.* - -case class ComboBox( - id: String, - options: Signal[List[ComboBox.Option]], - valueUpdates: Observer[List[String]] -) - -object ComboBox: - - extension (m: ComboBox) - def toHtml: HtmlElement = - val isOpen = Var(false) - div( - cls := "relative mt-1", - input( - idAttr := m.id, - tpe := "text", - cls := "w-full rounded-md border border-gray-300 bg-white py-2 pl-3 pr-12 shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 sm:text-sm", - role := "combobox", - aria.controls := "options", - aria.expanded := false, - onClick.mapTo(true) --> isOpen.writer - ), - button( - tpe := "button", - cls := "absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-none", { - import svg.* - svg( - cls := "h-5 w-5 text-gray-400", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - CustomAttrs.svg.ariaHidden := true, - path( - fillRule := "evenodd", - d := "M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z", - clipRule := "evenodd" - ) - ) - } - ), - ul( - cls <-- isOpen.signal.switch("", "hidden"), - cls := "absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm", - idAttr := "options", - role := "listbox", - children <-- m.options.map(_.map(_.toHtml)) - ) - ) - - case class Option(value: String, active: Boolean) - - object Option: - extension (m: Option) - def toHtml: HtmlElement = - li( - cls := "relative cursor-default select-none py-2 pl-8 pr-4", - cls := (if m.active then "text-white bg-indigo-600" - else "text-gray-900"), - idAttr := "option-0", - role := "option", - tabIndex := -1, - span( - cls := "block truncate", - m.value - ), - if m.active then - span( - cls := "absolute inset-y-0 left-0 flex items-center pl-1.5", - cls := (if m.active then "text-white" else "text-indigo-600"), { - import svg.* - svg( - cls := "h-5 w-5", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - CustomAttrs.svg.ariaHidden := true, - path( - fillRule := "evenodd", - d := "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", - clipRule := "evenodd" - ) - ) - } - ) - else emptyNode - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/Form.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/Form.scala deleted file mode 100644 index 1b41dee..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/Form.scala +++ /dev/null @@ -1,17 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} -import org.scalajs.dom -import com.raquo.laminar.nodes.ReactiveHtmlElement - -object Form: - val Body = FormBody - val Section = FormSection - val Row = FormRow - - def apply(body: HtmlElement, buttons: HtmlElement): HtmlElement = - form( - cls := "space-y-8 divide-y divide-gray-200", - body, - buttons - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormBody.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormBody.scala deleted file mode 100644 index 89abc9c..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormBody.scala +++ /dev/null @@ -1,10 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} - -object FormBody: - def apply(sections: HtmlElement*): HtmlElement = - div( - cls := "space-y-8 divide-y divide-gray-200 sm:space-y-5", - sections - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormFields.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormFields.scala deleted file mode 100644 index a2e4cc6..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormFields.scala +++ /dev/null @@ -1,14 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.laminar.nodes.ReactiveHtmlElement -import org.scalajs.dom - -object FormFields: - def apply( - mods: Modifier[ReactiveHtmlElement[dom.HTMLElement]]* - ): HtmlElement = - div( - cls := "mt-6 sm:mt-5 space-y-6 sm:space-y-5", - mods - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormHeader.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormHeader.scala deleted file mode 100644 index 4083841..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormHeader.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} - -object FormHeader: - case class ViewModel(header: String, description: String) - def apply(m: ViewModel): HtmlElement = - div( - h3(cls := "text-lg leading-6 font-medium text-gray-900", m.header), - p(cls := "mt-1 max-w-2xl text-sm text-gray-500", m.description) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormRow.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormRow.scala deleted file mode 100644 index ffc5807..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormRow.scala +++ /dev/null @@ -1,22 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} - -case class FormRow(id: String, label: String, content: Modifier[Div]) - -object FormRow: - - extension (m: FormRow) - def toHtml: HtmlElement = - div( - cls := "sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5", - label( - forId := m.id, - cls := "block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2", - m.label - ), - div( - cls := "mt-1 sm:mt-0 sm:col-span-2", - m.content - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormSection.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormSection.scala deleted file mode 100644 index 50811ee..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormSection.scala +++ /dev/null @@ -1,15 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.laminar.nodes.ReactiveHtmlElement - -object FormSection: - def apply( - header: HtmlElement, - rows: HtmlElement* - ): HtmlElement = - div( - cls := "space-y-6 sm:space-y-5", - header, - rows - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/BaseList.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/BaseList.scala deleted file mode 100644 index 66506e9..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/BaseList.scala +++ /dev/null @@ -1,142 +0,0 @@ -package works.iterative.ui.components.tailwind -package list - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.waypoint.Router -import com.raquo.laminar.builders.HtmlTag -import org.scalajs.dom - -trait ListItem: - def key: String - def title: Modifier[HtmlElement] - def topRight: Modifier[HtmlElement] - def bottomLeft: Modifier[HtmlElement] - def bottomRight: Modifier[HtmlElement] - -trait ListRenderable[Item]: - extension (x: Item) def asItem: ListItem - -trait Navigable[Item]: - extension (x: Item) def navigate: Modifier[HtmlElement] - -object BaseList: - enum Color: - case Green, Yellow, Red - - case class IconText(text: HtmlElement, icon: SvgElement) - case class Tag(text: String, color: Color) - case class Row( - id: String, - title: String, - tag: Tag, - leftProps: List[IconText], - rightProp: IconText - ) - - trait AsRow[Data]: - extension (d: Data) def asRow: Row - - class RowListItem(d: Row) extends ListItem: - - def key: String = d.id - - def title: Modifier[HtmlElement] = d.title - - def topRight: Modifier[HtmlElement] = - inline def colorClass(color: Color): (String, Boolean) = - val c = color.toString.toLowerCase - s"bg-$c-100 text-$c-800" -> (d.tag.color == color) - - inline def colors = Map(Color.values.map(colorClass(_)): _*) - - p( - cls := "px-2 inline-flex text-xs leading-5 font-semibold rounded-full", - cls := colors, - d.tag.text - ) - - def bottomLeft: Modifier[HtmlElement] = - div( - cls := "sm:flex", - d.leftProps.zipWithIndex.map { case (i, idx) => - p( - cls := Map("mt-2 sm:mt-0 sm:ml-6" -> (idx == 0)), - cls := "flex items-center text-sm text-gray-500", - i.icon, - i.text - ) - } - ) - - def bottomRight: Modifier[HtmlElement] = - div( - cls := "mt-2 flex items-center text-sm text-gray-500 sm:mt-0", - d.rightProp.icon, - d.rightProp.text - ) - - object Row: - given asRowRenderable[T: AsRow]: ListRenderable[T] with - extension (d: T) def asItem = new RowListItem(d.asRow) - - end Row - -class BaseList[RowData: ListRenderable]: - - protected def containerElement: HtmlTag[dom.html.Element] = div - protected def containerMods(rowData: RowData): Modifier[HtmlElement] = - emptyMod - protected def farRight: Modifier[HtmlElement] = emptyMod - - def render($data: Signal[List[RowData]]): HtmlElement = - ul( - role := "list", - cls := "divide-y divide-gray-200", - children <-- $data.split(_.asItem.key)((_, d, _) => row(d)) - ) - - private def row(d: RowData): HtmlElement = - val data = d.asItem - li( - containerElement( - containerMods(d), - cls := "block hover:bg-gray-50", - div( - cls := "px-4 py-4 sm:px-6 items-center flex", - div( - cls := "min-w-0 flex-1 pr-4", - div( - cls := "flex items-center justify-between", - p( - cls := "text-sm font-medium text-indigo-600 truncate", - data.title - ), - div( - cls := "ml-2 flex-shrink-0 flex", - data.topRight - ) - ), - div( - cls := "mt-2 sm:flex sm:justify-between", - data.bottomLeft, - data.bottomRight - ) - ), - farRight - ) - ) - ) - -trait NavigableList[RowData: Navigable, Page](using router: Router[Page]) - extends BaseList[RowData]: - - override protected def containerElement: HtmlTag[dom.html.Element] = a - override protected def containerMods( - rowData: RowData - ): Modifier[HtmlElement] = - rowData.navigate - override protected def farRight: Modifier[HtmlElement] = - div( - cls := "flex-shrink-0", - Icons.solid.`chevron-right`().amend(svg.cls := "text-gray-400") - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/IconText.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/IconText.scala deleted file mode 100644 index 9f5a7b1..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/IconText.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.ui.components.tailwind -package list - -import com.raquo.laminar.api.L.{*, given} -import org.scalajs.dom -import com.raquo.laminar.builders.HtmlTag - -object IconText: - case class ViewModel(text: HtmlElement, icon: SvgElement) - def render($m: Signal[ViewModel]): HtmlElement = render($m, div) - def render( - $m: Signal[ViewModel], - container: HtmlTag[dom.html.Element] - ): HtmlElement = - container( - cls := "flex items-center text-sm text-gray-500", - child <-- $m.map(_.icon), - child <-- $m.map(_.text) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/ListRow.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/ListRow.scala deleted file mode 100644 index e5236a2..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/ListRow.scala +++ /dev/null @@ -1,47 +0,0 @@ -package works.iterative.ui.components.tailwind -package list - -import com.raquo.laminar.api.L.{*, given} -import org.scalajs.dom - -object ListRow: - case class ViewModel( - title: String, - topRight: Modifier[HtmlElement], - bottomLeft: Modifier[HtmlElement], - bottomRight: Modifier[HtmlElement], - farRight: Modifier[HtmlElement], - containerElement: HtmlElement = div() - ) - - def apply($m: Signal[ViewModel]): HtmlElement = - li( - child <-- $m.map(m => - m.containerElement.amend( - cls := "block hover:bg-gray-50", - div( - cls := "px-4 py-4 sm:px-6 items-center flex", - div( - cls := "min-w-0 flex-1 pr-4", - div( - cls := "flex items-center justify-between", - p( - cls := "text-sm font-medium text-indigo-600 truncate", - m.title - ), - div( - cls := "ml-2 flex-shrink-0 flex", - m.topRight - ) - ), - div( - cls := "mt-2 sm:flex sm:justify-between", - m.bottomLeft, - m.bottomRight - ) - ), - m.farRight - ) - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/PropList.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/PropList.scala deleted file mode 100644 index 946ffaf..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/PropList.scala +++ /dev/null @@ -1,16 +0,0 @@ -package works.iterative.ui.components.tailwind -package list - -import com.raquo.laminar.api.L.{*, given} - -object PropList: - type ViewModel = List[HtmlElement] - def render($m: Signal[ViewModel]): HtmlElement = - div( - cls := "sm:flex", - children <-- $m.map(_.zipWithIndex.map { case (i, idx) => - i.amend( - cls := Map("mt-2 sm:mt-0 sm:ml-6" -> (idx == 0)) - ) - }) - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala deleted file mode 100644 index e7e6627..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.akka - -// Base class for all command-processing related exceptions from handlers -sealed abstract class CommandHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandNotAvailable[C, S](cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd není dostupný ve stavu $state" - ) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandRejected[C, S](reason: String, cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd byl ve stavu $state odmítnut s odůvodněním $reason" - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala deleted file mode 100644 index 72a5bf9..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.akka - -sealed abstract class EventHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -case class UnhandledEvent[Event, State](event: Event, state: State) - extends EventHandlerException( - s"Událost $event nastala ve stavu $state bez možnosti zpracování" - ) diff --git a/iw/bom.sc b/iw/bom.sc deleted file mode 100644 index e9266b8..0000000 --- a/iw/bom.sc +++ /dev/null @@ -1,227 +0,0 @@ -import mill._, scalalib._ - -object IWMaterials { - - object Versions { - val akka = "2.6.16" - val akkaHttp = "10.2.4" - val cats = "2.7.0" - val elastic4s = "7.12.2" - val http4s = "0.23.10" - val http4sPac4J = "4.0.0" - val laminar = "0.14.2" - val laminext = laminar - val logbackClassic = "1.2.10" - val pac4j = "5.2.0" - val play = "2.8.8" - val playJson = "2.9.2" - val scalaTest = "3.2.9" - val slick = "3.3.3" - val sttpClient = "3.5.0" - val tapir = "0.20.1" - val urlDsl = "0.4.0" - val waypoint = "0.5.0" - val zio = "2.0.0-RC2" - val zioConfig = "3.0.0-RC2" - val zioInteropCats = "3.3.0-RC2" - val zioJson = "0.3.0-RC3" - val zioLogging = "2.0.0-RC5" - val zioPrelude = "1.0.0-RC10" - val zioZMX = "0.0.11" - } - - object Deps extends AkkaLibs with SlickLibs { - import IWMaterials.{Versions => V} - - val zioOrg = "dev.zio" - - def zioLib(name: String, version: String): Dep = - ivy"$zioOrg::zio-$name::$version" - - lazy val zio: Dep = ivy"$zioOrg::zio:${V.zio}" - - lazy val zioTest: Dep = zioLib("test", V.zio) - lazy val zioTestSbt: Dep = zioLib("test", V.zio) - - lazy val zioConfig: Dep = zioLib("config", V.zioConfig) - lazy val zioConfigTypesafe: Dep = - zioLib("config-typesafe", V.zioConfig) - lazy val zioConfigMagnolia: Dep = - zioLib("config-magnolia", V.zioConfig) - - lazy val zioJson: Dep = zioLib("json", V.zioJson) - lazy val zioLogging: Dep = zioLib("logging", V.zioLogging) - lazy val zioLoggingSlf4j: Dep = - zioLib("logging-slf4j", V.zioLogging) - lazy val zioPrelude: Dep = zioLib("prelude", V.zioPrelude) - lazy val zioStreams: Dep = zioLib("streams", V.zio) - lazy val zioZMX: Dep = zioLib("zmx", V.zioZMX) - lazy val zioInteropCats: Dep = - zioLib("interop-cats", V.zioInteropCats) - - /* What is the equivalent? ZIOModule with prepared test config? - def useZIO(testConf: Configuration*): Agg[Dep] = Agg( - zio, - zioTest, - zioTestSbt, - testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework") - ) - */ - - /* - def useZIOAll(testConf: Configuration*): Seq[Def.Setting[_]] = - useZIO(testConf: _*) ++ Seq( - zioStreams, - zioConfig, - zioConfigTypesafe, - zioConfigMagnolia, - zioJson, - zioZMX, - zioLogging, - zioPrelude - ) - */ - - private val tapirOrg = "com.softwaremill.sttp.tapir" - def tapirLib(name: String): Dep = - ivy"${tapirOrg}::tapir-$name::${V.tapir}" - - lazy val tapirCore: Dep = tapirLib("core") - lazy val tapirZIO: Dep = tapirLib("zio") - lazy val tapirZIOJson: Dep = tapirLib("json-zio") - lazy val tapirSttpClient: Dep = tapirLib("sttp-client") - lazy val tapirCats: Dep = tapirLib("cats") - lazy val tapirZIOHttp4sServer: Dep = tapirLib("zio-http4s-server") - - private val sttpClientOrg = "com.softwaremill.sttp.client3" - def sttpClientLib(name: String): Dep = - ivy"${sttpClientOrg}::${name}:${V.sttpClient}" - - lazy val sttpClientCore: Dep = sttpClientLib("core") - - lazy val http4sBlazeServer: Dep = - ivy"org.http4s::http4s-blaze-server:${V.http4s}" - - lazy val http4sPac4J: Dep = - ivy"org.pac4j::http4s-pac4j:${V.http4sPac4J}" - lazy val pac4jOIDC: Dep = - ivy"org.pac4j:pac4j-oidc:${V.pac4j}" - - lazy val scalaTest: Dep = - ivy"org.scalatest::scalatest:${V.scalaTest}" - lazy val scalaTestPlusScalacheck: Dep = - ivy"org.scalatestplus::scalacheck-1-15:3.2.9.0" - lazy val playScalaTest: Dep = - ivy"org.scalatestplus.play::scalatestplus-play:5.1.0" - - private val playOrg = "com.typesafe.play" - lazy val playMailer: Dep = ivy"${playOrg}::play-mailer:8.0.1" - lazy val playServer: Dep = ivy"${playOrg}::play-server:${V.play}" - lazy val playAkkaServer: Dep = - ivy"${playOrg}::play-akka-http-server:${V.play}" - lazy val play: Dep = ivy"${playOrg}::play:${V.play}" - lazy val playAhcWs: Dep = ivy"${playOrg}::play-ahc-ws:${V.play}" - lazy val playJson: Dep = ivy"${playOrg}::play-json:${V.playJson}" - - private val elastic4sOrg = "com.sksamuel.elastic4s" - lazy val useElastic4S: Agg[Dep] = Agg( - ivy"${elastic4sOrg}::elastic4s-core:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-client-akka:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-http-streams:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-json-play:${V.elastic4s}" - ) - - lazy val laminar: Dep = ivy"com.raquo::laminar::${V.laminar}" - - private def laminext(name: String): Dep = - ivy"io.laminext::$name::${V.laminar}" - - lazy val laminextCore: Dep = laminext("core") - lazy val laminextTailwind: Dep = laminext("tailwind") - lazy val laminextFetch: Dep = laminext("fetch") - lazy val laminextValidationCore: Dep = laminext("validation-core") - lazy val laminextUI: Dep = laminext("ui") - - lazy val waypoint: Dep = - ivy"com.raquo::waypoint::${V.waypoint}" - - lazy val urlDsl: Dep = - ivy"be.doeraene::url-dsl::${V.urlDsl}" - - lazy val scalaJavaTime: Dep = - ivy"io.github.cquiroz::scala-java-time::2.3.0" - - lazy val scalaJavaLocales: Dep = - ivy"io.github.cquiroz::scala-java-locales::1.2.1" - - lazy val logbackClassic: Dep = - ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" - } - - trait AkkaLibs { - - self: SlickLibs => - - object akka { - val V = Versions.akka - val tOrg = "com.typesafe.akka" - val lOrg = "com.lightbend.akka" - - def akkaMod(name: String): Dep = - ivy"$tOrg::akka-$name:$V" - - lazy val actor: Dep = akkaMod("actor") - lazy val actorTyped: Dep = akkaMod("actor-typed") - lazy val stream: Dep = akkaMod("stream") - lazy val persistence: Dep = akkaMod("persistence-typed") - lazy val persistenceQuery: Dep = akkaMod("persistence-query") - lazy val persistenceJdbc: Dep = - ivy"$lOrg::akka-persistence-jdbc:5.0.4" - val persistenceTestKit: Dep = ivy"$tOrg::akka-persistence-testkit:$V" - - object http { - val V = Versions.akkaHttp - - lazy val http: Dep = ivy"$tOrg::akka-http:$V" - lazy val sprayJson: Dep = ivy"$tOrg::akka-http-spray-json:$V" - - } - - object projection { - val V = "1.2.2" - - lazy val core: Dep = - ivy"$lOrg::akka-projection-core:$V" - lazy val eventsourced: Dep = - ivy"$lOrg::akka-projection-eventsourced:$V" - lazy val slick: Dep = - ivy"$lOrg::akka-projection-slick:$V" - lazy val jdbc: Dep = - ivy"$lOrg::akka-projection-jdbc:$V" - } - - object profiles { - // TODO: deal with cross-version for Scala3 (for3Use2_13) - lazy val eventsourcedJdbcProjection: Agg[Dep] = Agg( - persistenceQuery, - projection.core, - projection.eventsourced, - projection.slick, - persistenceJdbc - ) ++ slick.default - } - } - } - - trait SlickLibs { - object slick { - val V = IWMaterials.Versions.slick - val org = "com.typesafe.slick" - - lazy val slick: Dep = ivy"$org::slick:$V" - lazy val hikaricp: Dep = ivy"$org::slick-hikaricp:$V" - lazy val default: Agg[Dep] = Agg(slick, hikaricp) - } - } - -} diff --git a/iw/build.sc b/iw/build.sc deleted file mode 100644 index fbbc3b8..0000000 --- a/iw/build.sc +++ /dev/null @@ -1,144 +0,0 @@ -import mill._, scalalib._, scalajslib._ - -import $file.bom - -object support { - val Deps = bom.IWMaterials.Deps - val Versions = bom.IWMaterials.Versions - - trait CommonModule extends ScalaModule { - def scalaVersion = "3.1.1" - def scalacOptions = Seq( - "-encoding", - "utf8", - "-deprecation", - "-explain-types", - "-explain", - "-feature", - "-language:experimental.macros", - "-language:higherKinds", - "-language:implicitConversions", - "-unchecked", - "-Xfatal-warnings", - "-Ykind-projector" - ) - } - - trait CommonJSModule extends CommonModule with ScalaJSModule { - def scalaJSVersion = "1.8.0" - } - - trait CrossPlatformModule extends Module { outer => - def ivyDeps: T[Agg[Dep]] = Agg[Dep]() - def jsDeps: T[Agg[Dep]] = Agg[Dep]() - def jvmDeps: T[Agg[Dep]] = Agg[Dep]() - def moduleDeps: Seq[Module] = Seq[Module]() - - trait PlatformModule extends CommonModule { - def platform: String - override def millSourcePath = outer.millSourcePath - override def ivyDeps = outer.ivyDeps() ++ (platform match { - case "js" => jsDeps() - case _ => jvmDeps() - }) - override def moduleDeps = outer.moduleDeps.collect { - case m: CrossPlatformModule => - platform match { - case "js" => m.js - case _ => m.jvm - } - case m: JavaModule => m - } - } - - trait JsModule extends PlatformModule with CommonJSModule { - def platform = "js" - } - - trait JvmModule extends PlatformModule { - def platform = "jvm" - } - - val js: JsModule - val jvm: JvmModule - } - - trait PureCrossModule extends CrossPlatformModule { - override object js extends JsModule - override object jvm extends JvmModule - } - - trait PureCrossSbtModule extends CrossPlatformModule { - override object js extends JsModule with SbtModule - override object jvm extends JvmModule with SbtModule - } - - trait FullCrossSbtModule extends CrossPlatformModule { - trait FullSources extends JavaModule { self: PlatformModule => - override def sources = T.sources( - millSourcePath / platform / "src" / "main" / "scala", - millSourcePath / "shared" / "src" / "main" / "scala" - ) - - override def resources = T.sources( - millSourcePath / platform / "src" / "main" / "resources", - millSourcePath / "shared" / "src" / "main" / "resources" - ) - } - - override object js extends JsModule with FullSources - override object jvm extends JvmModule with FullSources - } - -} - -import support._ - -object mongo extends CommonModule { - def ivyDeps = Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - ivy"org.mongodb.scala::mongo-scala-driver:4.2.3".withDottyCompat( - scalaVersion() - ) - ) -} - -object tapir extends FullCrossSbtModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson, Deps.zioJson) - def jvmDeps = Agg(Deps.tapirZIO, Deps.tapirZIOHttp4sServer) - def jsDeps = Agg(Deps.tapirSttpClient) -} - -object akkaPersistence extends CommonModule { - def millSourcePath = build.millSourcePath / "akka-persistence" - def ivyDeps = (Agg( - Deps.akka.persistenceQuery, - Deps.akka.persistenceJdbc, - Deps.akka.projection.core, - Deps.akka.projection.eventsourced, - Deps.akka.projection.slick - ) ++ Deps.slick.default).map(_.withDottyCompat(scalaVersion())) ++ Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - Deps.akka.persistence.withDottyCompat(scalaVersion()), - ivy"com.typesafe.akka::akka-cluster-sharding-typed:${Deps.akka.V}" - .withDottyCompat(scalaVersion()) - ) -} - -object ui extends CommonJSModule { - def ivyDeps = Agg( - Deps.zio, - Deps.laminar, - Deps.zioJson, - Deps.waypoint, - Deps.urlDsl, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) -} diff --git a/iw/domain.sc b/iw/domain.sc deleted file mode 100644 index b09c56b..0000000 --- a/iw/domain.sc +++ /dev/null @@ -1,78 +0,0 @@ -import mill._, scalalib._ - -import $file.{build => ff}, ff.support._ - -trait DomainModule extends Module { - // General extra model deps - def modelModules: Seq[Module] = Seq.empty - // General extra codecs deps - def codecsModules: Seq[Module] = Seq.empty - // General extra endpoints deps - def endpointsModules: Seq[Module] = Seq.empty - - // Implementation deps for repo - def repoModules: Seq[JavaModule] = Seq.empty - - object shared extends Module { - object model extends PureCrossModule { - def moduleDeps = modelModules - def ivyDeps = Agg(Deps.zioPrelude) - } - object codecs extends PureCrossModule { - def moduleDeps = Seq(model) ++ codecsModules - def ivyDeps = Agg(Deps.zioJson) - } - } - - trait CommonProjects extends Module { - object model extends PureCrossModule { - def ivyDeps = Agg(Deps.zioPrelude) - def moduleDeps = Seq(shared.model) - } - object codecs extends PureCrossModule { - def moduleDeps = - Seq(model, shared.model, shared.codecs, ff.tapir) - } - object endpoints extends PureCrossModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson) - def moduleDeps = Seq(model, codecs) ++ endpointsModules - } - object client extends CommonJSModule { - def moduleDeps = Seq(endpoints.js) - } - object components extends CommonJSModule { - def ivyDeps = Agg( - Deps.laminar, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) - def moduleDeps = Seq(model.js, codecs.js, client, ff.ui) - } - } - - object query extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(repo, query.endpoints.jvm) - } - object repo extends CommonModule { - def ivyDeps = Agg(Deps.zio) - def moduleDeps = Seq(model.jvm, codecs.jvm) ++ repoModules - } - object projection extends CommonModule { - def moduleDeps = Seq(repo, ff.akkaPersistence) - } - } - - object command extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(entity, command.endpoints.jvm) - } - object entity extends CommonModule { - def moduleDeps = Seq(model.jvm, codecs.jvm, ff.akkaPersistence) - } - } -} diff --git a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala b/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala deleted file mode 100644 index 0fa3493..0000000 --- a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala +++ /dev/null @@ -1,53 +0,0 @@ -package works.iterative.mongo - -import zio.* -import zio.json.* -import zio.config.* -import org.mongodb.scala.* -import org.mongodb.scala.model.Filters.* -import org.bson.json.JsonObject -import org.mongodb.scala.model.ReplaceOptions -import org.mongodb.scala.bson.conversions.Bson - -case class MongoConfig(uri: String) - -object MongoConfig: - val configDesc = - import ConfigDescriptor.* - nested("MONGO")(string("URI").default("mongodb://localhost:27017")) - .to[MongoConfig] - val fromEnv = ZConfig.fromSystemEnv(configDesc) - -extension (m: MongoClient.type) - def layer: RLayer[MongoConfig, MongoClient] = - ZIO - .serviceWithZIO[MongoConfig](c => Task.attempt(MongoClient(c.uri))) - .toLayer - -class MongoJsonRepository[Elem, Key, Criteria]( - collection: MongoCollection[JsonObject], - toFilter: Criteria => Bson, - idFilter: Elem => (String, Key) -)(using JsonCodec[Elem]) { - def matching(criteria: Criteria): Task[Seq[Elem]] = - val filter = toFilter(criteria) - val query = collection.find(filter) - - for - result <- ZIO.fromFuture(_ => query.toFuture) - proof <- ZIO.collect(result)(j => - ZIO.fromOption(j.getJson.fromJson[Elem].toOption) - ) - yield proof - - def put(elem: Elem): Task[Unit] = - Task.async(cb => - collection - .replaceOne( - equal.tupled(idFilter(elem)), - JsonObject(elem.toJson), - ReplaceOptions().upsert(true) - ) - .subscribe(_ => cb(Task.unit), t => cb(Task.fail(t))) - ) -} diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala deleted file mode 100644 index 68d7196..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala +++ /dev/null @@ -1,12 +0,0 @@ -package works.iterative.tapir - -import sttp.model.Uri - -opaque type BaseUri = Option[Uri] - -object BaseUri: - - def apply(optU: Option[Uri]): BaseUri = optU - def apply(u: Uri): BaseUri = Some(u) - - extension (v: BaseUri) def toUri: Option[Uri] = v diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 653bc51..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,22 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.client.sttp.SttpClientInterpreter -import sttp.tapir.PublicEndpoint -import sttp.tapir.client.sttp.WebSocketToPipe -import scala.concurrent.Future -import sttp.client3.SttpBackend -import sttp.capabilities.WebSockets - -trait CustomTapirPlatformSpecific extends SttpClientInterpreter: - self: CustomTapir => - - type Backend = SttpBackend[Future, WebSockets] - - def makeClient[I, E, O]( - endpoint: PublicEndpoint[I, E, O, Any] - )(using - baseUri: BaseUri, - backend: Backend, - wsToPipe: WebSocketToPipe[Any] - ): I => Future[O] = - toClientThrowErrors(endpoint, baseUri.toUri, backend) diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 2545fbd..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,5 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.ztapir.ZTapir - -trait CustomTapirPlatformSpecific extends ZTapir diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala deleted file mode 100644 index 55e4ca1..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala +++ /dev/null @@ -1,7 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.server.http4s.ztapir.ZHttp4sServerInterpreter - -trait Http4sCustomTapir[Env] - extends CustomTapir - with ZHttp4sServerInterpreter[Env] diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala deleted file mode 100644 index dd52e9b..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala +++ /dev/null @@ -1,14 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.Tapir -import sttp.tapir.json.zio.TapirJsonZio -import sttp.tapir.TapirAliases - -trait CustomTapir - extends Tapir - with TapirJsonZio - with TapirAliases - with CustomTapirPlatformSpecific: - given Schema[ServerError] = Schema.derived - -object CustomTapir extends CustomTapir diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala deleted file mode 100644 index 715591d..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.tapir - -import zio.json.* - -sealed trait ServerError -case class InternalServerError(msg: String) extends ServerError -object InternalServerError: - def fromThrowable(t: Throwable): ServerError = InternalServerError( - t.getMessage - ) - -object ServerError: - given JsonCodec[ServerError] = DeriveJsonCodec.gen diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/File.scala deleted file mode 100644 index 09f6c07..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala +++ /dev/null @@ -1,3 +0,0 @@ -package works.iterative.services.files - -case class File(url: String, name: String) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala deleted file mode 100644 index 46633a5..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala +++ /dev/null @@ -1,25 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.Renderable - -given Renderable[File] with - extension (m: File) - def toHtml: HtmlElement = - li( - cls("pl-3 pr-4 py-3 flex items-center justify-between text-sm"), - div( - cls("w-0 flex-1 flex items-center"), - Icons.solid - .paperclip() - .amend(svg.cls := "flex-shrink-0 text-gray-400"), - span(cls("ml-2 flex-1 w-0 truncate"), m.name) - ), - a( - href(m.url), - cls("font-medium text-indigo-600 hover:text-indigo-500"), - "Otevřít" - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala deleted file mode 100644 index ac59db8..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import io.laminext.syntax.core.{*, given} - -def FileList(files: List[File]): HtmlElement = - ul( - role("list"), - cls("border border-gray-200 rounded-md divide-y divide-gray-200"), - files.map(_.toHtml) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala deleted file mode 100644 index 2e8f690..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala +++ /dev/null @@ -1,81 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import io.laminext.syntax.core.{*, given} - -object FilePicker: - sealed trait Event - sealed trait DoneEvent extends Event - case class SelectionUpdated(files: Set[File]) extends DoneEvent - case object SelectionCancelled extends DoneEvent - case object AvailableFilesRequested extends Event - - def apply( - currentFiles: Signal[List[File]], - availableFilesStream: EventStream[List[File]] - )(selectionUpdates: Observer[Event]): HtmlElement = - val (updatesStream, updatesObserver) = EventStream.withObserver[Event] - val selectorOpen = Var[Boolean](false) - - // This sequence tricks browser into displaying modal content centered - // Inspired by modal in headless ui playground - // https://github.com/tailwindlabs/headlessui/blob/fdd26297953080d5ec905dda0bf5ec9607897d86/packages/playground-react/pages/transitions/component-examples/modal.tsx#L78-L79 - inline def browserCenteringModalTrick: Modifier[HtmlElement] = - Seq[Modifier[HtmlElement]]( - span(cls("hidden sm:inline-block sm:h-screen sm:align-middle")), - "​" // Zero width space - ) - - inline def overlay: Modifier[HtmlElement] = - // Page overlay - /* TODO: transition - enter="ease-out duration-300" - enterFrom="opacity-0" - enterTo="opacity-100" - leave="ease-in duration-200" - leaveFrom="opacity-100" - leaveTo="opacity-0" - */ - div( - div( - onClick.mapTo(false) --> selectorOpen.writer, - cls("fixed inset-0 transition-opacity"), - div(cls("absolute inset-0 bg-gray-500 opacity-75")) - ) - ) - - inline def modalSelector: HtmlElement = - div( - cls("fixed inset-0 z-10 overflow-y-auto"), - div( - cls("text-center sm:block sm:p-0"), - overlay, - browserCenteringModalTrick, - child <-- currentFiles.map( - FileSelector(_, availableFilesStream)(updatesObserver) - ) - ) - ) - - div( - updatesStream --> selectionUpdates, - updatesStream.collect { case _: DoneEvent => - false - } --> selectorOpen.writer, - child.maybe <-- selectorOpen.signal.map(isOpen => - if isOpen then Some(modalSelector) else None - ), - div( - cls("flex flex-col space-y-5"), - child.maybe <-- currentFiles.map(files => - if files.isEmpty then None else Some(FileList(files)) - ), - button( - tpe := "button", - cls := "bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500", - "Zvolit soubory", - onClick.mapTo(true) --> selectorOpen.writer - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala deleted file mode 100644 index b43e07a..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala +++ /dev/null @@ -1,74 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.Loading -import io.laminext.syntax.core.{*, given} -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -object FileSelector: - import FilePicker._ - - def apply( - initialFiles: List[File], - availableFilesStream: EventStream[List[File]] - )(selectionUpdates: Observer[Event]): HtmlElement = - val selectedFiles = Var[Set[File]](initialFiles.to(Set)) - val availableFiles = availableFilesStream.startWithNone - // Request the files to display - selectionUpdates.onNext(AvailableFilesRequested) - div( - cls( - "inline-block transform overflow-hidden rounded-lg bg-white text-left align-bottom shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:align-middle" - ), - role("dialog"), - customHtmlAttr("aria.modal", BooleanAsTrueFalseStringCodec)(true), - aria.labelledBy("modal-headline"), - div( - cls("bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"), - div( - cls("sm:flex sm:items-start"), - div( - cls("mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"), - h3( - cls("text-lg font-medium leading-6 text-gray-900"), - idAttr("modal-headline"), - "Výběr souborů" - ) - ) - ), - child <-- availableFiles - .split(_ => ())((_, _, af) => FileTable(af, selectedFiles)) - .map(_.getOrElse(Loading)) - ), - div( - cls("bg-gray-50 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6"), - span( - cls("flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto"), - button( - typ("button"), - cls( - "focus:shadow-outline-green inline-flex w-full justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-base font-medium leading-6 text-white shadow-sm transition duration-150 ease-in-out hover:bg-indigo-500 focus:border-indigo-700 focus:outline-none sm:text-sm sm:leading-5" - ), - "Potvrdit", - composeEvents(onClick)( - _.sample(selectedFiles) - .map(SelectionUpdated(_)) - ) --> selectionUpdates - ) - ), - span( - cls("mt-3 flex w-full rounded-md shadow-sm sm:mt-0 sm:w-auto"), - button( - typ("button"), - cls( - "focus:shadow-outline-blue inline-flex w-full justify-center rounded-md border border-gray-300 bg-white px-4 py-2 text-base font-medium leading-6 text-gray-700 shadow-sm transition duration-150 ease-in-out hover:text-gray-500 focus:border-blue-300 focus:outline-none sm:text-sm sm:leading-5" - ), - "Zrušit", - onClick.mapTo(SelectionCancelled) --> selectionUpdates - ) - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala deleted file mode 100644 index aa11b2b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala +++ /dev/null @@ -1,91 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import works.iterative.ui.components.tailwind.Icons - -def FileTable( - files: Signal[List[File]], - selectedFiles: Var[Set[File]] -): HtmlElement = - val scope = customHtmlAttr("scope", StringAsIsCodec) - - def headerRow: HtmlElement = - val col = scope("col") - val baseM: Modifier[HtmlElement] = Seq(cls("px-6 py-3"), col) - val textH = cls( - "text-left text-xs font-medium text-gray-500 uppercase tracking-wider" - ) - tr( - th(baseM, span(cls("sr-only"), "Vybrat")), - th(baseM, textH, "Název"), - th(baseM, cls("relative"), span(cls("sr-only"), "Otevřít")) - ) - - def tableRow( - f: File, - idx: Int, - selected: Boolean - )(toggleSelection: Observer[Unit]): HtmlElement = - val baseC = cls("px-6 py-4 whitespace-nowrap text-sm") - tr( - cls(if idx % 2 == 0 then "bg-gray-50" else "bg-white"), - td( - cls("font-medium cursor-pointer"), - onClick.mapTo(()) --> toggleSelection, - cls(if selected then "text-green-900" else "text-gray-200"), - Icons.outline.`check-circle`().amend(svg.cls := "mx-auto"), - span(cls("sr-only"), if selected then "Vybráno" else "Nevybráno") - ), - td( - baseC, - cls("font-medium text-gray-900"), - f.name, - onClick.mapTo(()) --> toggleSelection - ), - td( - baseC, - cls("text-right font-medium"), - a( - href(f.url), - target("_blank"), - cls( - "flex items-center space-x-4 text-indigo-600 hover:text-indigo-900" - ), - Icons.outline.`external-link`(), - "Otevřít" - ) - ) - ) - - div( - cls("flex flex-col"), - div(cls("overflow-x-auto sm:-mx-6 lg:-mx-8")), - div( - cls("py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8"), - div( - cls("shadow overflow-hidden border-b border-gray-200 sm:rounded-lg"), - table( - cls("min-w-full divide-y divide-gray-200"), - thead( - cls("bg-gray-50"), - headerRow - ), - tbody( - children <-- files - .map(_.zipWithIndex) - .combineWithFn(selectedFiles)((f, sel) => - f.map((file, idx) => - val active = sel.contains(file) - tableRow(file, idx, active)( - selectedFiles.writer - .contramap(_ => if active then sel - file else sel + file) - ) - ) - ) - ) - ) - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala deleted file mode 100644 index 0fc8796..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala +++ /dev/null @@ -1,32 +0,0 @@ -package works.iterative.ui.components.tailwind - -import CustomAttrs.ariaHidden -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -// TODO: render icon or picture based on img signal -class Avatar($avatarImg: Signal[Option[String]]): - inline def avatarPlaceholder(size: Int): HtmlElement = - div( - cls := s"rounded-full text-indigo-200 bg-indigo-500 h-${size} w-${size} flex items-center justify-center", - Icons.outline.user(size - 2) - ) - - inline def avatarImage(size: Int): Signal[HtmlElement] = - $avatarImg.split(_ => ())((_, _, $url) => - img( - cls := s"w-$size h-$size rounded-full", - src <-- $url, - alt := "" - ) - ).map(_.getOrElse(avatarPlaceholder(size))) - - inline def avatar(size: Int): HtmlElement = - div( - cls := "relative", - child <-- avatarImage(size), - span( - cls := "absolute inset-0 shadow-inner rounded-full", - ariaHidden := true - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala deleted file mode 100644 index bcbed88..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala +++ /dev/null @@ -1,77 +0,0 @@ -package works.iterative.ui.components.tailwind - -enum ColorWeight(value: String): - inline def toCSS: String = value - // To be used for black and white until - // we support better mechanism via extension methods - case w__ extends ColorWeight("") - case w50 extends ColorWeight("50") - case w100 extends ColorWeight("100") - case w200 extends ColorWeight("200") - case w300 extends ColorWeight("300") - case w400 extends ColorWeight("400") - case w500 extends ColorWeight("500") - case w600 extends ColorWeight("600") - case w700 extends ColorWeight("700") - case w800 extends ColorWeight("800") - case w900 extends ColorWeight("900") - -enum Color(name: String): - import ColorWeight._ - - inline def toCSSNoColorWeight(prefix: String): String = - s"${prefix}-${name}" - inline def toCSSWithColorWeight( - prefix: String, - weight: ColorWeight - ): String = - s"${prefix}-${name}-${weight.toCSS}" - inline def toCSS(prefix: String)(weight: ColorWeight): String = - weight match { - case `w__` => toCSSNoColorWeight(prefix) - case _ => toCSSWithColorWeight(prefix, weight) - } - inline def bg: ColorWeight => String = toCSS("bg")(_) - inline def text: ColorWeight => String = toCSS("text")(_) - inline def decoration: ColorWeight => String = toCSS("decoration")(_) - inline def border: ColorWeight => String = toCSS("border")(_) - inline def outline: ColorWeight => String = toCSS("outline")(_) - inline def divide: ColorWeight => String = toCSS("divide")(_) - inline def ring: ColorWeight => String = toCSS("ring")(_) - inline def ringOffset: ColorWeight => String = toCSS("ring-offset")(_) - inline def shadow: ColorWeight => String = toCSS("shadow")(_) - inline def accent: ColorWeight => String = toCSS("accent")(_) - - // TODO: change the "stupid" methods to extension methods - // that will keep the invariants in comments lower - case current extends Color("current") - case inherit extends Color("inherit") - // Not present in for all methods - case transp extends Color("transparent") - // Seen in accent, not preset otherwise - case auto extends Color("auto") - // Black and white do not have weight - case black extends Color("black") - case white extends Color("white") - case slate extends Color("slate") - case gray extends Color("gray") - case zinc extends Color("zinc") - case neutral extends Color("neutral") - case stone extends Color("stone") - case red extends Color("red") - case orange extends Color("orange") - case amber extends Color("amber") - case yellow extends Color("yellow") - case lime extends Color("lime") - case green extends Color("green") - case emerald extends Color("emerald") - case teal extends Color("teal") - case cyan extends Color("cyan") - case sky extends Color("sky") - case blue extends Color("blue") - case indigo extends Color("indigo") - case violet extends Color("violet") - case purple extends Color("purple") - case fuchsia extends Color("fuchsia") - case pink extends Color("pink") - case rose extends Color("rose") diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala deleted file mode 100644 index 6fb453c..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -object CustomAttrs { - // Made a pull request to add aria-current to scala-dom-types, remove after - val ariaCurrent = customHtmlAttr("aria-current", StringAsIsCodec) - val ariaHidden = customHtmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - - val datetime = customHtmlAttr("datetime", StringAsIsCodec) - - object svg { - import com.raquo.laminar.api.L.svg.{*, given} - val ariaHidden = - customSvgAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - } -} diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala deleted file mode 100644 index 6cda48b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala +++ /dev/null @@ -1,28 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -object Display: - - enum Breakpoint: - case sm, md, lg, xl, `2xl` - - enum DisplayClass: - case block, `inline-block`, `inline`, flex, `inline-flex`, table, - `inline-table`, `table-caption` - - object ShowUpFrom: - inline def apply( - br: Breakpoint, - dc: DisplayClass = DisplayClass.block - ): HtmlElement = - div( - cls := "hidden", - cls := s"${br}:${dc}" - ) - - object HideUpTo: - inline def apply(br: Breakpoint): HtmlElement = - div( - cls := s"${br}:hidden" - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala deleted file mode 100644 index 4a8b065..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala +++ /dev/null @@ -1,270 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec -import com.raquo.domtypes.generic.defs.attrs.AriaAttrs -import com.raquo.laminar.api.L.svg.{*, given} -import com.raquo.laminar.builders.SvgBuilders -import com.raquo.laminar.keys.ReactiveSvgAttr - -// TODO: fix sizes, colors, hover and stuff, normalize and amend on call site -object Icons: - object aria: - inline def hidden = CustomAttrs.svg.ariaHidden - - object outline: - val defaultSize: Int = 6 - - inline def bell(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" - ) - ) - - inline def `check-circle`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" - ) - ) - - inline def `document-add`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M9 13h6m-3-3v6m5 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" - ) - ) - - inline def `external-link`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" - ) - ) - - inline def menu(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M4 6h16M4 12h16M4 18h16" - ) - ) - - inline def `status-offline`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M18.364 5.636a9 9 0 010 12.728m0 0l-2.829-2.829m2.829 2.829L21 21M15.536 8.464a5 5 0 010 7.072m0 0l-2.829-2.829m-4.243 2.829a4.978 4.978 0 01-1.414-2.83m-1.414 5.658a9 9 0 01-2.167-9.238m7.824 2.167a1 1 0 111.414 1.414m-1.414-1.414L3 3m8.293 8.293l1.414 1.414" - ) - ) - - inline def user(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" - ) - ) - - inline def x(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M6 18L18 6M6 6l12 12" - ) - ) - - end outline - - object solid: - val defaultSize: Int = 5 - - inline def users(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - d := "M9 6a3 3 0 11-6 0 3 3 0 016 0zM17 6a3 3 0 11-6 0 3 3 0 016 0zM12.93 17c.046-.327.07-.66.07-1a6.97 6.97 0 00-1.5-4.33A5 5 0 0119 16v1h-6.07zM6 11a5 5 0 015 5v1H1v-1a5 5 0 015-5z" - ) - ) - - inline def `location-marker`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M5.05 4.05a7 7 0 119.9 9.9L10 18.9l-4.95-4.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z", - clipRule := "evenodd" - ) - ) - - inline def calendar(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z", - clipRule := "evenodd" - ) - ) - - inline def `chevron-right`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z", - clipRule := "evenodd" - ) - ) - - inline def search(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z", - clipRule := "evenodd" - ) - ) - - inline def filter(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M3 3a1 1 0 011-1h12a1 1 0 011 1v3a1 1 0 01-.293.707L12 11.414V15a1 1 0 01-.293.707l-2 2A1 1 0 018 17v-5.586L3.293 6.707A1 1 0 013 6V3z", - clipRule := "evenodd" - ) - ) - - inline def `arrow-narrow-left`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M7.707 14.707a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 1.414L5.414 9H17a1 1 0 110 2H5.414l2.293 2.293a1 1 0 010 1.414z", - clipRule := "evenodd" - ) - ) - - inline def home(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - d := "M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z" - ) - ) - - inline def paperclip(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size} text-gray-400", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M8 4a3 3 0 00-3 3v4a5 5 0 0010 0V7a1 1 0 112 0v4a7 7 0 11-14 0V7a5 5 0 0110 0v4a3 3 0 11-6 0V7a1 1 0 012 0v4a1 1 0 102 0V7a3 3 0 00-3-3z", - clipRule := "evenodd" - ) - ) - - end solid -end Icons diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/LinkSupport.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/LinkSupport.scala deleted file mode 100644 index 88f0b9a..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/LinkSupport.scala +++ /dev/null @@ -1,12 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.jsdom.defs.events.TypedTargetMouseEvent - -object LinkSupport: - - extension [El <: org.scalajs.dom.EventTarget]( - ep: EventProcessor[TypedTargetMouseEvent[El], TypedTargetMouseEvent[El]] - ) - def noKeyMod = - ep.filter(ev => !(ev.ctrlKey || ev.metaKey || ev.shiftKey || ev.altKey)) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Loader.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Loader.scala deleted file mode 100644 index 5399933..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Loader.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -// TODO: proper loader -def Loading = - div( - cls := "bg-gray-50 overflow-hidden rounded-lg", - div( - cls := "px-4 py-5 sm:p-6", - "Loading..." - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Renderable.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Renderable.scala deleted file mode 100644 index a0fcc5b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Renderable.scala +++ /dev/null @@ -1,6 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -trait Renderable[A]: - extension (a: A) def toHtml: HtmlElement diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/ComboBox.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/ComboBox.scala deleted file mode 100644 index 8c5b8a1..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/ComboBox.scala +++ /dev/null @@ -1,92 +0,0 @@ -package works.iterative.ui.components.tailwind -package form - -import com.raquo.laminar.api.L.{*, given} -import io.laminext.syntax.core.* - -case class ComboBox( - id: String, - options: Signal[List[ComboBox.Option]], - valueUpdates: Observer[List[String]] -) - -object ComboBox: - - extension (m: ComboBox) - def toHtml: HtmlElement = - val isOpen = Var(false) - div( - cls := "relative mt-1", - input( - idAttr := m.id, - tpe := "text", - cls := "w-full rounded-md border border-gray-300 bg-white py-2 pl-3 pr-12 shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 sm:text-sm", - role := "combobox", - aria.controls := "options", - aria.expanded := false, - onClick.mapTo(true) --> isOpen.writer - ), - button( - tpe := "button", - cls := "absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-none", { - import svg.* - svg( - cls := "h-5 w-5 text-gray-400", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - CustomAttrs.svg.ariaHidden := true, - path( - fillRule := "evenodd", - d := "M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z", - clipRule := "evenodd" - ) - ) - } - ), - ul( - cls <-- isOpen.signal.switch("", "hidden"), - cls := "absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm", - idAttr := "options", - role := "listbox", - children <-- m.options.map(_.map(_.toHtml)) - ) - ) - - case class Option(value: String, active: Boolean) - - object Option: - extension (m: Option) - def toHtml: HtmlElement = - li( - cls := "relative cursor-default select-none py-2 pl-8 pr-4", - cls := (if m.active then "text-white bg-indigo-600" - else "text-gray-900"), - idAttr := "option-0", - role := "option", - tabIndex := -1, - span( - cls := "block truncate", - m.value - ), - if m.active then - span( - cls := "absolute inset-y-0 left-0 flex items-center pl-1.5", - cls := (if m.active then "text-white" else "text-indigo-600"), { - import svg.* - svg( - cls := "h-5 w-5", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - CustomAttrs.svg.ariaHidden := true, - path( - fillRule := "evenodd", - d := "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", - clipRule := "evenodd" - ) - ) - } - ) - else emptyNode - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/Form.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/Form.scala deleted file mode 100644 index 1b41dee..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/Form.scala +++ /dev/null @@ -1,17 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} -import org.scalajs.dom -import com.raquo.laminar.nodes.ReactiveHtmlElement - -object Form: - val Body = FormBody - val Section = FormSection - val Row = FormRow - - def apply(body: HtmlElement, buttons: HtmlElement): HtmlElement = - form( - cls := "space-y-8 divide-y divide-gray-200", - body, - buttons - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormBody.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormBody.scala deleted file mode 100644 index 89abc9c..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormBody.scala +++ /dev/null @@ -1,10 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} - -object FormBody: - def apply(sections: HtmlElement*): HtmlElement = - div( - cls := "space-y-8 divide-y divide-gray-200 sm:space-y-5", - sections - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormFields.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormFields.scala deleted file mode 100644 index a2e4cc6..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormFields.scala +++ /dev/null @@ -1,14 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.laminar.nodes.ReactiveHtmlElement -import org.scalajs.dom - -object FormFields: - def apply( - mods: Modifier[ReactiveHtmlElement[dom.HTMLElement]]* - ): HtmlElement = - div( - cls := "mt-6 sm:mt-5 space-y-6 sm:space-y-5", - mods - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormHeader.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormHeader.scala deleted file mode 100644 index 4083841..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormHeader.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} - -object FormHeader: - case class ViewModel(header: String, description: String) - def apply(m: ViewModel): HtmlElement = - div( - h3(cls := "text-lg leading-6 font-medium text-gray-900", m.header), - p(cls := "mt-1 max-w-2xl text-sm text-gray-500", m.description) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormRow.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormRow.scala deleted file mode 100644 index ffc5807..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormRow.scala +++ /dev/null @@ -1,22 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} - -case class FormRow(id: String, label: String, content: Modifier[Div]) - -object FormRow: - - extension (m: FormRow) - def toHtml: HtmlElement = - div( - cls := "sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5", - label( - forId := m.id, - cls := "block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2", - m.label - ), - div( - cls := "mt-1 sm:mt-0 sm:col-span-2", - m.content - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormSection.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormSection.scala deleted file mode 100644 index 50811ee..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormSection.scala +++ /dev/null @@ -1,15 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.laminar.nodes.ReactiveHtmlElement - -object FormSection: - def apply( - header: HtmlElement, - rows: HtmlElement* - ): HtmlElement = - div( - cls := "space-y-6 sm:space-y-5", - header, - rows - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/BaseList.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/BaseList.scala deleted file mode 100644 index 66506e9..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/BaseList.scala +++ /dev/null @@ -1,142 +0,0 @@ -package works.iterative.ui.components.tailwind -package list - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.waypoint.Router -import com.raquo.laminar.builders.HtmlTag -import org.scalajs.dom - -trait ListItem: - def key: String - def title: Modifier[HtmlElement] - def topRight: Modifier[HtmlElement] - def bottomLeft: Modifier[HtmlElement] - def bottomRight: Modifier[HtmlElement] - -trait ListRenderable[Item]: - extension (x: Item) def asItem: ListItem - -trait Navigable[Item]: - extension (x: Item) def navigate: Modifier[HtmlElement] - -object BaseList: - enum Color: - case Green, Yellow, Red - - case class IconText(text: HtmlElement, icon: SvgElement) - case class Tag(text: String, color: Color) - case class Row( - id: String, - title: String, - tag: Tag, - leftProps: List[IconText], - rightProp: IconText - ) - - trait AsRow[Data]: - extension (d: Data) def asRow: Row - - class RowListItem(d: Row) extends ListItem: - - def key: String = d.id - - def title: Modifier[HtmlElement] = d.title - - def topRight: Modifier[HtmlElement] = - inline def colorClass(color: Color): (String, Boolean) = - val c = color.toString.toLowerCase - s"bg-$c-100 text-$c-800" -> (d.tag.color == color) - - inline def colors = Map(Color.values.map(colorClass(_)): _*) - - p( - cls := "px-2 inline-flex text-xs leading-5 font-semibold rounded-full", - cls := colors, - d.tag.text - ) - - def bottomLeft: Modifier[HtmlElement] = - div( - cls := "sm:flex", - d.leftProps.zipWithIndex.map { case (i, idx) => - p( - cls := Map("mt-2 sm:mt-0 sm:ml-6" -> (idx == 0)), - cls := "flex items-center text-sm text-gray-500", - i.icon, - i.text - ) - } - ) - - def bottomRight: Modifier[HtmlElement] = - div( - cls := "mt-2 flex items-center text-sm text-gray-500 sm:mt-0", - d.rightProp.icon, - d.rightProp.text - ) - - object Row: - given asRowRenderable[T: AsRow]: ListRenderable[T] with - extension (d: T) def asItem = new RowListItem(d.asRow) - - end Row - -class BaseList[RowData: ListRenderable]: - - protected def containerElement: HtmlTag[dom.html.Element] = div - protected def containerMods(rowData: RowData): Modifier[HtmlElement] = - emptyMod - protected def farRight: Modifier[HtmlElement] = emptyMod - - def render($data: Signal[List[RowData]]): HtmlElement = - ul( - role := "list", - cls := "divide-y divide-gray-200", - children <-- $data.split(_.asItem.key)((_, d, _) => row(d)) - ) - - private def row(d: RowData): HtmlElement = - val data = d.asItem - li( - containerElement( - containerMods(d), - cls := "block hover:bg-gray-50", - div( - cls := "px-4 py-4 sm:px-6 items-center flex", - div( - cls := "min-w-0 flex-1 pr-4", - div( - cls := "flex items-center justify-between", - p( - cls := "text-sm font-medium text-indigo-600 truncate", - data.title - ), - div( - cls := "ml-2 flex-shrink-0 flex", - data.topRight - ) - ), - div( - cls := "mt-2 sm:flex sm:justify-between", - data.bottomLeft, - data.bottomRight - ) - ), - farRight - ) - ) - ) - -trait NavigableList[RowData: Navigable, Page](using router: Router[Page]) - extends BaseList[RowData]: - - override protected def containerElement: HtmlTag[dom.html.Element] = a - override protected def containerMods( - rowData: RowData - ): Modifier[HtmlElement] = - rowData.navigate - override protected def farRight: Modifier[HtmlElement] = - div( - cls := "flex-shrink-0", - Icons.solid.`chevron-right`().amend(svg.cls := "text-gray-400") - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/IconText.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/IconText.scala deleted file mode 100644 index 9f5a7b1..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/IconText.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.ui.components.tailwind -package list - -import com.raquo.laminar.api.L.{*, given} -import org.scalajs.dom -import com.raquo.laminar.builders.HtmlTag - -object IconText: - case class ViewModel(text: HtmlElement, icon: SvgElement) - def render($m: Signal[ViewModel]): HtmlElement = render($m, div) - def render( - $m: Signal[ViewModel], - container: HtmlTag[dom.html.Element] - ): HtmlElement = - container( - cls := "flex items-center text-sm text-gray-500", - child <-- $m.map(_.icon), - child <-- $m.map(_.text) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/ListRow.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/ListRow.scala deleted file mode 100644 index e5236a2..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/ListRow.scala +++ /dev/null @@ -1,47 +0,0 @@ -package works.iterative.ui.components.tailwind -package list - -import com.raquo.laminar.api.L.{*, given} -import org.scalajs.dom - -object ListRow: - case class ViewModel( - title: String, - topRight: Modifier[HtmlElement], - bottomLeft: Modifier[HtmlElement], - bottomRight: Modifier[HtmlElement], - farRight: Modifier[HtmlElement], - containerElement: HtmlElement = div() - ) - - def apply($m: Signal[ViewModel]): HtmlElement = - li( - child <-- $m.map(m => - m.containerElement.amend( - cls := "block hover:bg-gray-50", - div( - cls := "px-4 py-4 sm:px-6 items-center flex", - div( - cls := "min-w-0 flex-1 pr-4", - div( - cls := "flex items-center justify-between", - p( - cls := "text-sm font-medium text-indigo-600 truncate", - m.title - ), - div( - cls := "ml-2 flex-shrink-0 flex", - m.topRight - ) - ), - div( - cls := "mt-2 sm:flex sm:justify-between", - m.bottomLeft, - m.bottomRight - ) - ), - m.farRight - ) - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/PropList.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/PropList.scala deleted file mode 100644 index 946ffaf..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/PropList.scala +++ /dev/null @@ -1,16 +0,0 @@ -package works.iterative.ui.components.tailwind -package list - -import com.raquo.laminar.api.L.{*, given} - -object PropList: - type ViewModel = List[HtmlElement] - def render($m: Signal[ViewModel]): HtmlElement = - div( - cls := "sm:flex", - children <-- $m.map(_.zipWithIndex.map { case (i, idx) => - i.amend( - cls := Map("mt-2 sm:mt-0 sm:ml-6" -> (idx == 0)) - ) - }) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/RowNext.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/RowNext.scala deleted file mode 100644 index b53f3f2..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/RowNext.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.ui.components.tailwind -package list - -import com.raquo.laminar.api.L.{*, given} - -object RowNext: - def render: HtmlElement = - div( - cls := "flex-shrink-0", - Icons.solid.`chevron-right`().amend(svg.cls := "text-gray-400") - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala deleted file mode 100644 index e7e6627..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.akka - -// Base class for all command-processing related exceptions from handlers -sealed abstract class CommandHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandNotAvailable[C, S](cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd není dostupný ve stavu $state" - ) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandRejected[C, S](reason: String, cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd byl ve stavu $state odmítnut s odůvodněním $reason" - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala deleted file mode 100644 index 72a5bf9..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.akka - -sealed abstract class EventHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -case class UnhandledEvent[Event, State](event: Event, state: State) - extends EventHandlerException( - s"Událost $event nastala ve stavu $state bez možnosti zpracování" - ) diff --git a/iw/bom.sc b/iw/bom.sc deleted file mode 100644 index e9266b8..0000000 --- a/iw/bom.sc +++ /dev/null @@ -1,227 +0,0 @@ -import mill._, scalalib._ - -object IWMaterials { - - object Versions { - val akka = "2.6.16" - val akkaHttp = "10.2.4" - val cats = "2.7.0" - val elastic4s = "7.12.2" - val http4s = "0.23.10" - val http4sPac4J = "4.0.0" - val laminar = "0.14.2" - val laminext = laminar - val logbackClassic = "1.2.10" - val pac4j = "5.2.0" - val play = "2.8.8" - val playJson = "2.9.2" - val scalaTest = "3.2.9" - val slick = "3.3.3" - val sttpClient = "3.5.0" - val tapir = "0.20.1" - val urlDsl = "0.4.0" - val waypoint = "0.5.0" - val zio = "2.0.0-RC2" - val zioConfig = "3.0.0-RC2" - val zioInteropCats = "3.3.0-RC2" - val zioJson = "0.3.0-RC3" - val zioLogging = "2.0.0-RC5" - val zioPrelude = "1.0.0-RC10" - val zioZMX = "0.0.11" - } - - object Deps extends AkkaLibs with SlickLibs { - import IWMaterials.{Versions => V} - - val zioOrg = "dev.zio" - - def zioLib(name: String, version: String): Dep = - ivy"$zioOrg::zio-$name::$version" - - lazy val zio: Dep = ivy"$zioOrg::zio:${V.zio}" - - lazy val zioTest: Dep = zioLib("test", V.zio) - lazy val zioTestSbt: Dep = zioLib("test", V.zio) - - lazy val zioConfig: Dep = zioLib("config", V.zioConfig) - lazy val zioConfigTypesafe: Dep = - zioLib("config-typesafe", V.zioConfig) - lazy val zioConfigMagnolia: Dep = - zioLib("config-magnolia", V.zioConfig) - - lazy val zioJson: Dep = zioLib("json", V.zioJson) - lazy val zioLogging: Dep = zioLib("logging", V.zioLogging) - lazy val zioLoggingSlf4j: Dep = - zioLib("logging-slf4j", V.zioLogging) - lazy val zioPrelude: Dep = zioLib("prelude", V.zioPrelude) - lazy val zioStreams: Dep = zioLib("streams", V.zio) - lazy val zioZMX: Dep = zioLib("zmx", V.zioZMX) - lazy val zioInteropCats: Dep = - zioLib("interop-cats", V.zioInteropCats) - - /* What is the equivalent? ZIOModule with prepared test config? - def useZIO(testConf: Configuration*): Agg[Dep] = Agg( - zio, - zioTest, - zioTestSbt, - testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework") - ) - */ - - /* - def useZIOAll(testConf: Configuration*): Seq[Def.Setting[_]] = - useZIO(testConf: _*) ++ Seq( - zioStreams, - zioConfig, - zioConfigTypesafe, - zioConfigMagnolia, - zioJson, - zioZMX, - zioLogging, - zioPrelude - ) - */ - - private val tapirOrg = "com.softwaremill.sttp.tapir" - def tapirLib(name: String): Dep = - ivy"${tapirOrg}::tapir-$name::${V.tapir}" - - lazy val tapirCore: Dep = tapirLib("core") - lazy val tapirZIO: Dep = tapirLib("zio") - lazy val tapirZIOJson: Dep = tapirLib("json-zio") - lazy val tapirSttpClient: Dep = tapirLib("sttp-client") - lazy val tapirCats: Dep = tapirLib("cats") - lazy val tapirZIOHttp4sServer: Dep = tapirLib("zio-http4s-server") - - private val sttpClientOrg = "com.softwaremill.sttp.client3" - def sttpClientLib(name: String): Dep = - ivy"${sttpClientOrg}::${name}:${V.sttpClient}" - - lazy val sttpClientCore: Dep = sttpClientLib("core") - - lazy val http4sBlazeServer: Dep = - ivy"org.http4s::http4s-blaze-server:${V.http4s}" - - lazy val http4sPac4J: Dep = - ivy"org.pac4j::http4s-pac4j:${V.http4sPac4J}" - lazy val pac4jOIDC: Dep = - ivy"org.pac4j:pac4j-oidc:${V.pac4j}" - - lazy val scalaTest: Dep = - ivy"org.scalatest::scalatest:${V.scalaTest}" - lazy val scalaTestPlusScalacheck: Dep = - ivy"org.scalatestplus::scalacheck-1-15:3.2.9.0" - lazy val playScalaTest: Dep = - ivy"org.scalatestplus.play::scalatestplus-play:5.1.0" - - private val playOrg = "com.typesafe.play" - lazy val playMailer: Dep = ivy"${playOrg}::play-mailer:8.0.1" - lazy val playServer: Dep = ivy"${playOrg}::play-server:${V.play}" - lazy val playAkkaServer: Dep = - ivy"${playOrg}::play-akka-http-server:${V.play}" - lazy val play: Dep = ivy"${playOrg}::play:${V.play}" - lazy val playAhcWs: Dep = ivy"${playOrg}::play-ahc-ws:${V.play}" - lazy val playJson: Dep = ivy"${playOrg}::play-json:${V.playJson}" - - private val elastic4sOrg = "com.sksamuel.elastic4s" - lazy val useElastic4S: Agg[Dep] = Agg( - ivy"${elastic4sOrg}::elastic4s-core:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-client-akka:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-http-streams:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-json-play:${V.elastic4s}" - ) - - lazy val laminar: Dep = ivy"com.raquo::laminar::${V.laminar}" - - private def laminext(name: String): Dep = - ivy"io.laminext::$name::${V.laminar}" - - lazy val laminextCore: Dep = laminext("core") - lazy val laminextTailwind: Dep = laminext("tailwind") - lazy val laminextFetch: Dep = laminext("fetch") - lazy val laminextValidationCore: Dep = laminext("validation-core") - lazy val laminextUI: Dep = laminext("ui") - - lazy val waypoint: Dep = - ivy"com.raquo::waypoint::${V.waypoint}" - - lazy val urlDsl: Dep = - ivy"be.doeraene::url-dsl::${V.urlDsl}" - - lazy val scalaJavaTime: Dep = - ivy"io.github.cquiroz::scala-java-time::2.3.0" - - lazy val scalaJavaLocales: Dep = - ivy"io.github.cquiroz::scala-java-locales::1.2.1" - - lazy val logbackClassic: Dep = - ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" - } - - trait AkkaLibs { - - self: SlickLibs => - - object akka { - val V = Versions.akka - val tOrg = "com.typesafe.akka" - val lOrg = "com.lightbend.akka" - - def akkaMod(name: String): Dep = - ivy"$tOrg::akka-$name:$V" - - lazy val actor: Dep = akkaMod("actor") - lazy val actorTyped: Dep = akkaMod("actor-typed") - lazy val stream: Dep = akkaMod("stream") - lazy val persistence: Dep = akkaMod("persistence-typed") - lazy val persistenceQuery: Dep = akkaMod("persistence-query") - lazy val persistenceJdbc: Dep = - ivy"$lOrg::akka-persistence-jdbc:5.0.4" - val persistenceTestKit: Dep = ivy"$tOrg::akka-persistence-testkit:$V" - - object http { - val V = Versions.akkaHttp - - lazy val http: Dep = ivy"$tOrg::akka-http:$V" - lazy val sprayJson: Dep = ivy"$tOrg::akka-http-spray-json:$V" - - } - - object projection { - val V = "1.2.2" - - lazy val core: Dep = - ivy"$lOrg::akka-projection-core:$V" - lazy val eventsourced: Dep = - ivy"$lOrg::akka-projection-eventsourced:$V" - lazy val slick: Dep = - ivy"$lOrg::akka-projection-slick:$V" - lazy val jdbc: Dep = - ivy"$lOrg::akka-projection-jdbc:$V" - } - - object profiles { - // TODO: deal with cross-version for Scala3 (for3Use2_13) - lazy val eventsourcedJdbcProjection: Agg[Dep] = Agg( - persistenceQuery, - projection.core, - projection.eventsourced, - projection.slick, - persistenceJdbc - ) ++ slick.default - } - } - } - - trait SlickLibs { - object slick { - val V = IWMaterials.Versions.slick - val org = "com.typesafe.slick" - - lazy val slick: Dep = ivy"$org::slick:$V" - lazy val hikaricp: Dep = ivy"$org::slick-hikaricp:$V" - lazy val default: Agg[Dep] = Agg(slick, hikaricp) - } - } - -} diff --git a/iw/build.sc b/iw/build.sc deleted file mode 100644 index fbbc3b8..0000000 --- a/iw/build.sc +++ /dev/null @@ -1,144 +0,0 @@ -import mill._, scalalib._, scalajslib._ - -import $file.bom - -object support { - val Deps = bom.IWMaterials.Deps - val Versions = bom.IWMaterials.Versions - - trait CommonModule extends ScalaModule { - def scalaVersion = "3.1.1" - def scalacOptions = Seq( - "-encoding", - "utf8", - "-deprecation", - "-explain-types", - "-explain", - "-feature", - "-language:experimental.macros", - "-language:higherKinds", - "-language:implicitConversions", - "-unchecked", - "-Xfatal-warnings", - "-Ykind-projector" - ) - } - - trait CommonJSModule extends CommonModule with ScalaJSModule { - def scalaJSVersion = "1.8.0" - } - - trait CrossPlatformModule extends Module { outer => - def ivyDeps: T[Agg[Dep]] = Agg[Dep]() - def jsDeps: T[Agg[Dep]] = Agg[Dep]() - def jvmDeps: T[Agg[Dep]] = Agg[Dep]() - def moduleDeps: Seq[Module] = Seq[Module]() - - trait PlatformModule extends CommonModule { - def platform: String - override def millSourcePath = outer.millSourcePath - override def ivyDeps = outer.ivyDeps() ++ (platform match { - case "js" => jsDeps() - case _ => jvmDeps() - }) - override def moduleDeps = outer.moduleDeps.collect { - case m: CrossPlatformModule => - platform match { - case "js" => m.js - case _ => m.jvm - } - case m: JavaModule => m - } - } - - trait JsModule extends PlatformModule with CommonJSModule { - def platform = "js" - } - - trait JvmModule extends PlatformModule { - def platform = "jvm" - } - - val js: JsModule - val jvm: JvmModule - } - - trait PureCrossModule extends CrossPlatformModule { - override object js extends JsModule - override object jvm extends JvmModule - } - - trait PureCrossSbtModule extends CrossPlatformModule { - override object js extends JsModule with SbtModule - override object jvm extends JvmModule with SbtModule - } - - trait FullCrossSbtModule extends CrossPlatformModule { - trait FullSources extends JavaModule { self: PlatformModule => - override def sources = T.sources( - millSourcePath / platform / "src" / "main" / "scala", - millSourcePath / "shared" / "src" / "main" / "scala" - ) - - override def resources = T.sources( - millSourcePath / platform / "src" / "main" / "resources", - millSourcePath / "shared" / "src" / "main" / "resources" - ) - } - - override object js extends JsModule with FullSources - override object jvm extends JvmModule with FullSources - } - -} - -import support._ - -object mongo extends CommonModule { - def ivyDeps = Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - ivy"org.mongodb.scala::mongo-scala-driver:4.2.3".withDottyCompat( - scalaVersion() - ) - ) -} - -object tapir extends FullCrossSbtModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson, Deps.zioJson) - def jvmDeps = Agg(Deps.tapirZIO, Deps.tapirZIOHttp4sServer) - def jsDeps = Agg(Deps.tapirSttpClient) -} - -object akkaPersistence extends CommonModule { - def millSourcePath = build.millSourcePath / "akka-persistence" - def ivyDeps = (Agg( - Deps.akka.persistenceQuery, - Deps.akka.persistenceJdbc, - Deps.akka.projection.core, - Deps.akka.projection.eventsourced, - Deps.akka.projection.slick - ) ++ Deps.slick.default).map(_.withDottyCompat(scalaVersion())) ++ Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - Deps.akka.persistence.withDottyCompat(scalaVersion()), - ivy"com.typesafe.akka::akka-cluster-sharding-typed:${Deps.akka.V}" - .withDottyCompat(scalaVersion()) - ) -} - -object ui extends CommonJSModule { - def ivyDeps = Agg( - Deps.zio, - Deps.laminar, - Deps.zioJson, - Deps.waypoint, - Deps.urlDsl, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) -} diff --git a/iw/domain.sc b/iw/domain.sc deleted file mode 100644 index b09c56b..0000000 --- a/iw/domain.sc +++ /dev/null @@ -1,78 +0,0 @@ -import mill._, scalalib._ - -import $file.{build => ff}, ff.support._ - -trait DomainModule extends Module { - // General extra model deps - def modelModules: Seq[Module] = Seq.empty - // General extra codecs deps - def codecsModules: Seq[Module] = Seq.empty - // General extra endpoints deps - def endpointsModules: Seq[Module] = Seq.empty - - // Implementation deps for repo - def repoModules: Seq[JavaModule] = Seq.empty - - object shared extends Module { - object model extends PureCrossModule { - def moduleDeps = modelModules - def ivyDeps = Agg(Deps.zioPrelude) - } - object codecs extends PureCrossModule { - def moduleDeps = Seq(model) ++ codecsModules - def ivyDeps = Agg(Deps.zioJson) - } - } - - trait CommonProjects extends Module { - object model extends PureCrossModule { - def ivyDeps = Agg(Deps.zioPrelude) - def moduleDeps = Seq(shared.model) - } - object codecs extends PureCrossModule { - def moduleDeps = - Seq(model, shared.model, shared.codecs, ff.tapir) - } - object endpoints extends PureCrossModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson) - def moduleDeps = Seq(model, codecs) ++ endpointsModules - } - object client extends CommonJSModule { - def moduleDeps = Seq(endpoints.js) - } - object components extends CommonJSModule { - def ivyDeps = Agg( - Deps.laminar, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) - def moduleDeps = Seq(model.js, codecs.js, client, ff.ui) - } - } - - object query extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(repo, query.endpoints.jvm) - } - object repo extends CommonModule { - def ivyDeps = Agg(Deps.zio) - def moduleDeps = Seq(model.jvm, codecs.jvm) ++ repoModules - } - object projection extends CommonModule { - def moduleDeps = Seq(repo, ff.akkaPersistence) - } - } - - object command extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(entity, command.endpoints.jvm) - } - object entity extends CommonModule { - def moduleDeps = Seq(model.jvm, codecs.jvm, ff.akkaPersistence) - } - } -} diff --git a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala b/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala deleted file mode 100644 index 0fa3493..0000000 --- a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala +++ /dev/null @@ -1,53 +0,0 @@ -package works.iterative.mongo - -import zio.* -import zio.json.* -import zio.config.* -import org.mongodb.scala.* -import org.mongodb.scala.model.Filters.* -import org.bson.json.JsonObject -import org.mongodb.scala.model.ReplaceOptions -import org.mongodb.scala.bson.conversions.Bson - -case class MongoConfig(uri: String) - -object MongoConfig: - val configDesc = - import ConfigDescriptor.* - nested("MONGO")(string("URI").default("mongodb://localhost:27017")) - .to[MongoConfig] - val fromEnv = ZConfig.fromSystemEnv(configDesc) - -extension (m: MongoClient.type) - def layer: RLayer[MongoConfig, MongoClient] = - ZIO - .serviceWithZIO[MongoConfig](c => Task.attempt(MongoClient(c.uri))) - .toLayer - -class MongoJsonRepository[Elem, Key, Criteria]( - collection: MongoCollection[JsonObject], - toFilter: Criteria => Bson, - idFilter: Elem => (String, Key) -)(using JsonCodec[Elem]) { - def matching(criteria: Criteria): Task[Seq[Elem]] = - val filter = toFilter(criteria) - val query = collection.find(filter) - - for - result <- ZIO.fromFuture(_ => query.toFuture) - proof <- ZIO.collect(result)(j => - ZIO.fromOption(j.getJson.fromJson[Elem].toOption) - ) - yield proof - - def put(elem: Elem): Task[Unit] = - Task.async(cb => - collection - .replaceOne( - equal.tupled(idFilter(elem)), - JsonObject(elem.toJson), - ReplaceOptions().upsert(true) - ) - .subscribe(_ => cb(Task.unit), t => cb(Task.fail(t))) - ) -} diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala deleted file mode 100644 index 68d7196..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala +++ /dev/null @@ -1,12 +0,0 @@ -package works.iterative.tapir - -import sttp.model.Uri - -opaque type BaseUri = Option[Uri] - -object BaseUri: - - def apply(optU: Option[Uri]): BaseUri = optU - def apply(u: Uri): BaseUri = Some(u) - - extension (v: BaseUri) def toUri: Option[Uri] = v diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 653bc51..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,22 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.client.sttp.SttpClientInterpreter -import sttp.tapir.PublicEndpoint -import sttp.tapir.client.sttp.WebSocketToPipe -import scala.concurrent.Future -import sttp.client3.SttpBackend -import sttp.capabilities.WebSockets - -trait CustomTapirPlatformSpecific extends SttpClientInterpreter: - self: CustomTapir => - - type Backend = SttpBackend[Future, WebSockets] - - def makeClient[I, E, O]( - endpoint: PublicEndpoint[I, E, O, Any] - )(using - baseUri: BaseUri, - backend: Backend, - wsToPipe: WebSocketToPipe[Any] - ): I => Future[O] = - toClientThrowErrors(endpoint, baseUri.toUri, backend) diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 2545fbd..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,5 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.ztapir.ZTapir - -trait CustomTapirPlatformSpecific extends ZTapir diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala deleted file mode 100644 index 55e4ca1..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala +++ /dev/null @@ -1,7 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.server.http4s.ztapir.ZHttp4sServerInterpreter - -trait Http4sCustomTapir[Env] - extends CustomTapir - with ZHttp4sServerInterpreter[Env] diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala deleted file mode 100644 index dd52e9b..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala +++ /dev/null @@ -1,14 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.Tapir -import sttp.tapir.json.zio.TapirJsonZio -import sttp.tapir.TapirAliases - -trait CustomTapir - extends Tapir - with TapirJsonZio - with TapirAliases - with CustomTapirPlatformSpecific: - given Schema[ServerError] = Schema.derived - -object CustomTapir extends CustomTapir diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala deleted file mode 100644 index 715591d..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.tapir - -import zio.json.* - -sealed trait ServerError -case class InternalServerError(msg: String) extends ServerError -object InternalServerError: - def fromThrowable(t: Throwable): ServerError = InternalServerError( - t.getMessage - ) - -object ServerError: - given JsonCodec[ServerError] = DeriveJsonCodec.gen diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/File.scala deleted file mode 100644 index 09f6c07..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala +++ /dev/null @@ -1,3 +0,0 @@ -package works.iterative.services.files - -case class File(url: String, name: String) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala deleted file mode 100644 index 46633a5..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala +++ /dev/null @@ -1,25 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.Renderable - -given Renderable[File] with - extension (m: File) - def toHtml: HtmlElement = - li( - cls("pl-3 pr-4 py-3 flex items-center justify-between text-sm"), - div( - cls("w-0 flex-1 flex items-center"), - Icons.solid - .paperclip() - .amend(svg.cls := "flex-shrink-0 text-gray-400"), - span(cls("ml-2 flex-1 w-0 truncate"), m.name) - ), - a( - href(m.url), - cls("font-medium text-indigo-600 hover:text-indigo-500"), - "Otevřít" - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala deleted file mode 100644 index ac59db8..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import io.laminext.syntax.core.{*, given} - -def FileList(files: List[File]): HtmlElement = - ul( - role("list"), - cls("border border-gray-200 rounded-md divide-y divide-gray-200"), - files.map(_.toHtml) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala deleted file mode 100644 index 2e8f690..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala +++ /dev/null @@ -1,81 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import io.laminext.syntax.core.{*, given} - -object FilePicker: - sealed trait Event - sealed trait DoneEvent extends Event - case class SelectionUpdated(files: Set[File]) extends DoneEvent - case object SelectionCancelled extends DoneEvent - case object AvailableFilesRequested extends Event - - def apply( - currentFiles: Signal[List[File]], - availableFilesStream: EventStream[List[File]] - )(selectionUpdates: Observer[Event]): HtmlElement = - val (updatesStream, updatesObserver) = EventStream.withObserver[Event] - val selectorOpen = Var[Boolean](false) - - // This sequence tricks browser into displaying modal content centered - // Inspired by modal in headless ui playground - // https://github.com/tailwindlabs/headlessui/blob/fdd26297953080d5ec905dda0bf5ec9607897d86/packages/playground-react/pages/transitions/component-examples/modal.tsx#L78-L79 - inline def browserCenteringModalTrick: Modifier[HtmlElement] = - Seq[Modifier[HtmlElement]]( - span(cls("hidden sm:inline-block sm:h-screen sm:align-middle")), - "​" // Zero width space - ) - - inline def overlay: Modifier[HtmlElement] = - // Page overlay - /* TODO: transition - enter="ease-out duration-300" - enterFrom="opacity-0" - enterTo="opacity-100" - leave="ease-in duration-200" - leaveFrom="opacity-100" - leaveTo="opacity-0" - */ - div( - div( - onClick.mapTo(false) --> selectorOpen.writer, - cls("fixed inset-0 transition-opacity"), - div(cls("absolute inset-0 bg-gray-500 opacity-75")) - ) - ) - - inline def modalSelector: HtmlElement = - div( - cls("fixed inset-0 z-10 overflow-y-auto"), - div( - cls("text-center sm:block sm:p-0"), - overlay, - browserCenteringModalTrick, - child <-- currentFiles.map( - FileSelector(_, availableFilesStream)(updatesObserver) - ) - ) - ) - - div( - updatesStream --> selectionUpdates, - updatesStream.collect { case _: DoneEvent => - false - } --> selectorOpen.writer, - child.maybe <-- selectorOpen.signal.map(isOpen => - if isOpen then Some(modalSelector) else None - ), - div( - cls("flex flex-col space-y-5"), - child.maybe <-- currentFiles.map(files => - if files.isEmpty then None else Some(FileList(files)) - ), - button( - tpe := "button", - cls := "bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500", - "Zvolit soubory", - onClick.mapTo(true) --> selectorOpen.writer - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala deleted file mode 100644 index b43e07a..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala +++ /dev/null @@ -1,74 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.Loading -import io.laminext.syntax.core.{*, given} -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -object FileSelector: - import FilePicker._ - - def apply( - initialFiles: List[File], - availableFilesStream: EventStream[List[File]] - )(selectionUpdates: Observer[Event]): HtmlElement = - val selectedFiles = Var[Set[File]](initialFiles.to(Set)) - val availableFiles = availableFilesStream.startWithNone - // Request the files to display - selectionUpdates.onNext(AvailableFilesRequested) - div( - cls( - "inline-block transform overflow-hidden rounded-lg bg-white text-left align-bottom shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:align-middle" - ), - role("dialog"), - customHtmlAttr("aria.modal", BooleanAsTrueFalseStringCodec)(true), - aria.labelledBy("modal-headline"), - div( - cls("bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"), - div( - cls("sm:flex sm:items-start"), - div( - cls("mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"), - h3( - cls("text-lg font-medium leading-6 text-gray-900"), - idAttr("modal-headline"), - "Výběr souborů" - ) - ) - ), - child <-- availableFiles - .split(_ => ())((_, _, af) => FileTable(af, selectedFiles)) - .map(_.getOrElse(Loading)) - ), - div( - cls("bg-gray-50 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6"), - span( - cls("flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto"), - button( - typ("button"), - cls( - "focus:shadow-outline-green inline-flex w-full justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-base font-medium leading-6 text-white shadow-sm transition duration-150 ease-in-out hover:bg-indigo-500 focus:border-indigo-700 focus:outline-none sm:text-sm sm:leading-5" - ), - "Potvrdit", - composeEvents(onClick)( - _.sample(selectedFiles) - .map(SelectionUpdated(_)) - ) --> selectionUpdates - ) - ), - span( - cls("mt-3 flex w-full rounded-md shadow-sm sm:mt-0 sm:w-auto"), - button( - typ("button"), - cls( - "focus:shadow-outline-blue inline-flex w-full justify-center rounded-md border border-gray-300 bg-white px-4 py-2 text-base font-medium leading-6 text-gray-700 shadow-sm transition duration-150 ease-in-out hover:text-gray-500 focus:border-blue-300 focus:outline-none sm:text-sm sm:leading-5" - ), - "Zrušit", - onClick.mapTo(SelectionCancelled) --> selectionUpdates - ) - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala deleted file mode 100644 index aa11b2b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala +++ /dev/null @@ -1,91 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import works.iterative.ui.components.tailwind.Icons - -def FileTable( - files: Signal[List[File]], - selectedFiles: Var[Set[File]] -): HtmlElement = - val scope = customHtmlAttr("scope", StringAsIsCodec) - - def headerRow: HtmlElement = - val col = scope("col") - val baseM: Modifier[HtmlElement] = Seq(cls("px-6 py-3"), col) - val textH = cls( - "text-left text-xs font-medium text-gray-500 uppercase tracking-wider" - ) - tr( - th(baseM, span(cls("sr-only"), "Vybrat")), - th(baseM, textH, "Název"), - th(baseM, cls("relative"), span(cls("sr-only"), "Otevřít")) - ) - - def tableRow( - f: File, - idx: Int, - selected: Boolean - )(toggleSelection: Observer[Unit]): HtmlElement = - val baseC = cls("px-6 py-4 whitespace-nowrap text-sm") - tr( - cls(if idx % 2 == 0 then "bg-gray-50" else "bg-white"), - td( - cls("font-medium cursor-pointer"), - onClick.mapTo(()) --> toggleSelection, - cls(if selected then "text-green-900" else "text-gray-200"), - Icons.outline.`check-circle`().amend(svg.cls := "mx-auto"), - span(cls("sr-only"), if selected then "Vybráno" else "Nevybráno") - ), - td( - baseC, - cls("font-medium text-gray-900"), - f.name, - onClick.mapTo(()) --> toggleSelection - ), - td( - baseC, - cls("text-right font-medium"), - a( - href(f.url), - target("_blank"), - cls( - "flex items-center space-x-4 text-indigo-600 hover:text-indigo-900" - ), - Icons.outline.`external-link`(), - "Otevřít" - ) - ) - ) - - div( - cls("flex flex-col"), - div(cls("overflow-x-auto sm:-mx-6 lg:-mx-8")), - div( - cls("py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8"), - div( - cls("shadow overflow-hidden border-b border-gray-200 sm:rounded-lg"), - table( - cls("min-w-full divide-y divide-gray-200"), - thead( - cls("bg-gray-50"), - headerRow - ), - tbody( - children <-- files - .map(_.zipWithIndex) - .combineWithFn(selectedFiles)((f, sel) => - f.map((file, idx) => - val active = sel.contains(file) - tableRow(file, idx, active)( - selectedFiles.writer - .contramap(_ => if active then sel - file else sel + file) - ) - ) - ) - ) - ) - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala deleted file mode 100644 index 0fc8796..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala +++ /dev/null @@ -1,32 +0,0 @@ -package works.iterative.ui.components.tailwind - -import CustomAttrs.ariaHidden -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -// TODO: render icon or picture based on img signal -class Avatar($avatarImg: Signal[Option[String]]): - inline def avatarPlaceholder(size: Int): HtmlElement = - div( - cls := s"rounded-full text-indigo-200 bg-indigo-500 h-${size} w-${size} flex items-center justify-center", - Icons.outline.user(size - 2) - ) - - inline def avatarImage(size: Int): Signal[HtmlElement] = - $avatarImg.split(_ => ())((_, _, $url) => - img( - cls := s"w-$size h-$size rounded-full", - src <-- $url, - alt := "" - ) - ).map(_.getOrElse(avatarPlaceholder(size))) - - inline def avatar(size: Int): HtmlElement = - div( - cls := "relative", - child <-- avatarImage(size), - span( - cls := "absolute inset-0 shadow-inner rounded-full", - ariaHidden := true - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala deleted file mode 100644 index bcbed88..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala +++ /dev/null @@ -1,77 +0,0 @@ -package works.iterative.ui.components.tailwind - -enum ColorWeight(value: String): - inline def toCSS: String = value - // To be used for black and white until - // we support better mechanism via extension methods - case w__ extends ColorWeight("") - case w50 extends ColorWeight("50") - case w100 extends ColorWeight("100") - case w200 extends ColorWeight("200") - case w300 extends ColorWeight("300") - case w400 extends ColorWeight("400") - case w500 extends ColorWeight("500") - case w600 extends ColorWeight("600") - case w700 extends ColorWeight("700") - case w800 extends ColorWeight("800") - case w900 extends ColorWeight("900") - -enum Color(name: String): - import ColorWeight._ - - inline def toCSSNoColorWeight(prefix: String): String = - s"${prefix}-${name}" - inline def toCSSWithColorWeight( - prefix: String, - weight: ColorWeight - ): String = - s"${prefix}-${name}-${weight.toCSS}" - inline def toCSS(prefix: String)(weight: ColorWeight): String = - weight match { - case `w__` => toCSSNoColorWeight(prefix) - case _ => toCSSWithColorWeight(prefix, weight) - } - inline def bg: ColorWeight => String = toCSS("bg")(_) - inline def text: ColorWeight => String = toCSS("text")(_) - inline def decoration: ColorWeight => String = toCSS("decoration")(_) - inline def border: ColorWeight => String = toCSS("border")(_) - inline def outline: ColorWeight => String = toCSS("outline")(_) - inline def divide: ColorWeight => String = toCSS("divide")(_) - inline def ring: ColorWeight => String = toCSS("ring")(_) - inline def ringOffset: ColorWeight => String = toCSS("ring-offset")(_) - inline def shadow: ColorWeight => String = toCSS("shadow")(_) - inline def accent: ColorWeight => String = toCSS("accent")(_) - - // TODO: change the "stupid" methods to extension methods - // that will keep the invariants in comments lower - case current extends Color("current") - case inherit extends Color("inherit") - // Not present in for all methods - case transp extends Color("transparent") - // Seen in accent, not preset otherwise - case auto extends Color("auto") - // Black and white do not have weight - case black extends Color("black") - case white extends Color("white") - case slate extends Color("slate") - case gray extends Color("gray") - case zinc extends Color("zinc") - case neutral extends Color("neutral") - case stone extends Color("stone") - case red extends Color("red") - case orange extends Color("orange") - case amber extends Color("amber") - case yellow extends Color("yellow") - case lime extends Color("lime") - case green extends Color("green") - case emerald extends Color("emerald") - case teal extends Color("teal") - case cyan extends Color("cyan") - case sky extends Color("sky") - case blue extends Color("blue") - case indigo extends Color("indigo") - case violet extends Color("violet") - case purple extends Color("purple") - case fuchsia extends Color("fuchsia") - case pink extends Color("pink") - case rose extends Color("rose") diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala deleted file mode 100644 index 6fb453c..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -object CustomAttrs { - // Made a pull request to add aria-current to scala-dom-types, remove after - val ariaCurrent = customHtmlAttr("aria-current", StringAsIsCodec) - val ariaHidden = customHtmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - - val datetime = customHtmlAttr("datetime", StringAsIsCodec) - - object svg { - import com.raquo.laminar.api.L.svg.{*, given} - val ariaHidden = - customSvgAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - } -} diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala deleted file mode 100644 index 6cda48b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala +++ /dev/null @@ -1,28 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -object Display: - - enum Breakpoint: - case sm, md, lg, xl, `2xl` - - enum DisplayClass: - case block, `inline-block`, `inline`, flex, `inline-flex`, table, - `inline-table`, `table-caption` - - object ShowUpFrom: - inline def apply( - br: Breakpoint, - dc: DisplayClass = DisplayClass.block - ): HtmlElement = - div( - cls := "hidden", - cls := s"${br}:${dc}" - ) - - object HideUpTo: - inline def apply(br: Breakpoint): HtmlElement = - div( - cls := s"${br}:hidden" - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala deleted file mode 100644 index 4a8b065..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala +++ /dev/null @@ -1,270 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec -import com.raquo.domtypes.generic.defs.attrs.AriaAttrs -import com.raquo.laminar.api.L.svg.{*, given} -import com.raquo.laminar.builders.SvgBuilders -import com.raquo.laminar.keys.ReactiveSvgAttr - -// TODO: fix sizes, colors, hover and stuff, normalize and amend on call site -object Icons: - object aria: - inline def hidden = CustomAttrs.svg.ariaHidden - - object outline: - val defaultSize: Int = 6 - - inline def bell(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" - ) - ) - - inline def `check-circle`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" - ) - ) - - inline def `document-add`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M9 13h6m-3-3v6m5 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" - ) - ) - - inline def `external-link`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" - ) - ) - - inline def menu(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M4 6h16M4 12h16M4 18h16" - ) - ) - - inline def `status-offline`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M18.364 5.636a9 9 0 010 12.728m0 0l-2.829-2.829m2.829 2.829L21 21M15.536 8.464a5 5 0 010 7.072m0 0l-2.829-2.829m-4.243 2.829a4.978 4.978 0 01-1.414-2.83m-1.414 5.658a9 9 0 01-2.167-9.238m7.824 2.167a1 1 0 111.414 1.414m-1.414-1.414L3 3m8.293 8.293l1.414 1.414" - ) - ) - - inline def user(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" - ) - ) - - inline def x(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M6 18L18 6M6 6l12 12" - ) - ) - - end outline - - object solid: - val defaultSize: Int = 5 - - inline def users(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - d := "M9 6a3 3 0 11-6 0 3 3 0 016 0zM17 6a3 3 0 11-6 0 3 3 0 016 0zM12.93 17c.046-.327.07-.66.07-1a6.97 6.97 0 00-1.5-4.33A5 5 0 0119 16v1h-6.07zM6 11a5 5 0 015 5v1H1v-1a5 5 0 015-5z" - ) - ) - - inline def `location-marker`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M5.05 4.05a7 7 0 119.9 9.9L10 18.9l-4.95-4.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z", - clipRule := "evenodd" - ) - ) - - inline def calendar(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z", - clipRule := "evenodd" - ) - ) - - inline def `chevron-right`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z", - clipRule := "evenodd" - ) - ) - - inline def search(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z", - clipRule := "evenodd" - ) - ) - - inline def filter(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M3 3a1 1 0 011-1h12a1 1 0 011 1v3a1 1 0 01-.293.707L12 11.414V15a1 1 0 01-.293.707l-2 2A1 1 0 018 17v-5.586L3.293 6.707A1 1 0 013 6V3z", - clipRule := "evenodd" - ) - ) - - inline def `arrow-narrow-left`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M7.707 14.707a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 1.414L5.414 9H17a1 1 0 110 2H5.414l2.293 2.293a1 1 0 010 1.414z", - clipRule := "evenodd" - ) - ) - - inline def home(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - d := "M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z" - ) - ) - - inline def paperclip(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size} text-gray-400", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M8 4a3 3 0 00-3 3v4a5 5 0 0010 0V7a1 1 0 112 0v4a7 7 0 11-14 0V7a5 5 0 0110 0v4a3 3 0 11-6 0V7a1 1 0 012 0v4a1 1 0 102 0V7a3 3 0 00-3-3z", - clipRule := "evenodd" - ) - ) - - end solid -end Icons diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/LinkSupport.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/LinkSupport.scala deleted file mode 100644 index 88f0b9a..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/LinkSupport.scala +++ /dev/null @@ -1,12 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.jsdom.defs.events.TypedTargetMouseEvent - -object LinkSupport: - - extension [El <: org.scalajs.dom.EventTarget]( - ep: EventProcessor[TypedTargetMouseEvent[El], TypedTargetMouseEvent[El]] - ) - def noKeyMod = - ep.filter(ev => !(ev.ctrlKey || ev.metaKey || ev.shiftKey || ev.altKey)) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Loader.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Loader.scala deleted file mode 100644 index 5399933..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Loader.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -// TODO: proper loader -def Loading = - div( - cls := "bg-gray-50 overflow-hidden rounded-lg", - div( - cls := "px-4 py-5 sm:p-6", - "Loading..." - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Renderable.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Renderable.scala deleted file mode 100644 index a0fcc5b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Renderable.scala +++ /dev/null @@ -1,6 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -trait Renderable[A]: - extension (a: A) def toHtml: HtmlElement diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/ComboBox.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/ComboBox.scala deleted file mode 100644 index 8c5b8a1..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/ComboBox.scala +++ /dev/null @@ -1,92 +0,0 @@ -package works.iterative.ui.components.tailwind -package form - -import com.raquo.laminar.api.L.{*, given} -import io.laminext.syntax.core.* - -case class ComboBox( - id: String, - options: Signal[List[ComboBox.Option]], - valueUpdates: Observer[List[String]] -) - -object ComboBox: - - extension (m: ComboBox) - def toHtml: HtmlElement = - val isOpen = Var(false) - div( - cls := "relative mt-1", - input( - idAttr := m.id, - tpe := "text", - cls := "w-full rounded-md border border-gray-300 bg-white py-2 pl-3 pr-12 shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 sm:text-sm", - role := "combobox", - aria.controls := "options", - aria.expanded := false, - onClick.mapTo(true) --> isOpen.writer - ), - button( - tpe := "button", - cls := "absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-none", { - import svg.* - svg( - cls := "h-5 w-5 text-gray-400", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - CustomAttrs.svg.ariaHidden := true, - path( - fillRule := "evenodd", - d := "M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z", - clipRule := "evenodd" - ) - ) - } - ), - ul( - cls <-- isOpen.signal.switch("", "hidden"), - cls := "absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm", - idAttr := "options", - role := "listbox", - children <-- m.options.map(_.map(_.toHtml)) - ) - ) - - case class Option(value: String, active: Boolean) - - object Option: - extension (m: Option) - def toHtml: HtmlElement = - li( - cls := "relative cursor-default select-none py-2 pl-8 pr-4", - cls := (if m.active then "text-white bg-indigo-600" - else "text-gray-900"), - idAttr := "option-0", - role := "option", - tabIndex := -1, - span( - cls := "block truncate", - m.value - ), - if m.active then - span( - cls := "absolute inset-y-0 left-0 flex items-center pl-1.5", - cls := (if m.active then "text-white" else "text-indigo-600"), { - import svg.* - svg( - cls := "h-5 w-5", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - CustomAttrs.svg.ariaHidden := true, - path( - fillRule := "evenodd", - d := "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", - clipRule := "evenodd" - ) - ) - } - ) - else emptyNode - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/Form.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/Form.scala deleted file mode 100644 index 1b41dee..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/Form.scala +++ /dev/null @@ -1,17 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} -import org.scalajs.dom -import com.raquo.laminar.nodes.ReactiveHtmlElement - -object Form: - val Body = FormBody - val Section = FormSection - val Row = FormRow - - def apply(body: HtmlElement, buttons: HtmlElement): HtmlElement = - form( - cls := "space-y-8 divide-y divide-gray-200", - body, - buttons - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormBody.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormBody.scala deleted file mode 100644 index 89abc9c..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormBody.scala +++ /dev/null @@ -1,10 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} - -object FormBody: - def apply(sections: HtmlElement*): HtmlElement = - div( - cls := "space-y-8 divide-y divide-gray-200 sm:space-y-5", - sections - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormFields.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormFields.scala deleted file mode 100644 index a2e4cc6..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormFields.scala +++ /dev/null @@ -1,14 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.laminar.nodes.ReactiveHtmlElement -import org.scalajs.dom - -object FormFields: - def apply( - mods: Modifier[ReactiveHtmlElement[dom.HTMLElement]]* - ): HtmlElement = - div( - cls := "mt-6 sm:mt-5 space-y-6 sm:space-y-5", - mods - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormHeader.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormHeader.scala deleted file mode 100644 index 4083841..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormHeader.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} - -object FormHeader: - case class ViewModel(header: String, description: String) - def apply(m: ViewModel): HtmlElement = - div( - h3(cls := "text-lg leading-6 font-medium text-gray-900", m.header), - p(cls := "mt-1 max-w-2xl text-sm text-gray-500", m.description) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormRow.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormRow.scala deleted file mode 100644 index ffc5807..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormRow.scala +++ /dev/null @@ -1,22 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} - -case class FormRow(id: String, label: String, content: Modifier[Div]) - -object FormRow: - - extension (m: FormRow) - def toHtml: HtmlElement = - div( - cls := "sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5", - label( - forId := m.id, - cls := "block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2", - m.label - ), - div( - cls := "mt-1 sm:mt-0 sm:col-span-2", - m.content - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormSection.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormSection.scala deleted file mode 100644 index 50811ee..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormSection.scala +++ /dev/null @@ -1,15 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.laminar.nodes.ReactiveHtmlElement - -object FormSection: - def apply( - header: HtmlElement, - rows: HtmlElement* - ): HtmlElement = - div( - cls := "space-y-6 sm:space-y-5", - header, - rows - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/BaseList.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/BaseList.scala deleted file mode 100644 index 66506e9..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/BaseList.scala +++ /dev/null @@ -1,142 +0,0 @@ -package works.iterative.ui.components.tailwind -package list - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.waypoint.Router -import com.raquo.laminar.builders.HtmlTag -import org.scalajs.dom - -trait ListItem: - def key: String - def title: Modifier[HtmlElement] - def topRight: Modifier[HtmlElement] - def bottomLeft: Modifier[HtmlElement] - def bottomRight: Modifier[HtmlElement] - -trait ListRenderable[Item]: - extension (x: Item) def asItem: ListItem - -trait Navigable[Item]: - extension (x: Item) def navigate: Modifier[HtmlElement] - -object BaseList: - enum Color: - case Green, Yellow, Red - - case class IconText(text: HtmlElement, icon: SvgElement) - case class Tag(text: String, color: Color) - case class Row( - id: String, - title: String, - tag: Tag, - leftProps: List[IconText], - rightProp: IconText - ) - - trait AsRow[Data]: - extension (d: Data) def asRow: Row - - class RowListItem(d: Row) extends ListItem: - - def key: String = d.id - - def title: Modifier[HtmlElement] = d.title - - def topRight: Modifier[HtmlElement] = - inline def colorClass(color: Color): (String, Boolean) = - val c = color.toString.toLowerCase - s"bg-$c-100 text-$c-800" -> (d.tag.color == color) - - inline def colors = Map(Color.values.map(colorClass(_)): _*) - - p( - cls := "px-2 inline-flex text-xs leading-5 font-semibold rounded-full", - cls := colors, - d.tag.text - ) - - def bottomLeft: Modifier[HtmlElement] = - div( - cls := "sm:flex", - d.leftProps.zipWithIndex.map { case (i, idx) => - p( - cls := Map("mt-2 sm:mt-0 sm:ml-6" -> (idx == 0)), - cls := "flex items-center text-sm text-gray-500", - i.icon, - i.text - ) - } - ) - - def bottomRight: Modifier[HtmlElement] = - div( - cls := "mt-2 flex items-center text-sm text-gray-500 sm:mt-0", - d.rightProp.icon, - d.rightProp.text - ) - - object Row: - given asRowRenderable[T: AsRow]: ListRenderable[T] with - extension (d: T) def asItem = new RowListItem(d.asRow) - - end Row - -class BaseList[RowData: ListRenderable]: - - protected def containerElement: HtmlTag[dom.html.Element] = div - protected def containerMods(rowData: RowData): Modifier[HtmlElement] = - emptyMod - protected def farRight: Modifier[HtmlElement] = emptyMod - - def render($data: Signal[List[RowData]]): HtmlElement = - ul( - role := "list", - cls := "divide-y divide-gray-200", - children <-- $data.split(_.asItem.key)((_, d, _) => row(d)) - ) - - private def row(d: RowData): HtmlElement = - val data = d.asItem - li( - containerElement( - containerMods(d), - cls := "block hover:bg-gray-50", - div( - cls := "px-4 py-4 sm:px-6 items-center flex", - div( - cls := "min-w-0 flex-1 pr-4", - div( - cls := "flex items-center justify-between", - p( - cls := "text-sm font-medium text-indigo-600 truncate", - data.title - ), - div( - cls := "ml-2 flex-shrink-0 flex", - data.topRight - ) - ), - div( - cls := "mt-2 sm:flex sm:justify-between", - data.bottomLeft, - data.bottomRight - ) - ), - farRight - ) - ) - ) - -trait NavigableList[RowData: Navigable, Page](using router: Router[Page]) - extends BaseList[RowData]: - - override protected def containerElement: HtmlTag[dom.html.Element] = a - override protected def containerMods( - rowData: RowData - ): Modifier[HtmlElement] = - rowData.navigate - override protected def farRight: Modifier[HtmlElement] = - div( - cls := "flex-shrink-0", - Icons.solid.`chevron-right`().amend(svg.cls := "text-gray-400") - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/IconText.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/IconText.scala deleted file mode 100644 index 9f5a7b1..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/IconText.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.ui.components.tailwind -package list - -import com.raquo.laminar.api.L.{*, given} -import org.scalajs.dom -import com.raquo.laminar.builders.HtmlTag - -object IconText: - case class ViewModel(text: HtmlElement, icon: SvgElement) - def render($m: Signal[ViewModel]): HtmlElement = render($m, div) - def render( - $m: Signal[ViewModel], - container: HtmlTag[dom.html.Element] - ): HtmlElement = - container( - cls := "flex items-center text-sm text-gray-500", - child <-- $m.map(_.icon), - child <-- $m.map(_.text) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/ListRow.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/ListRow.scala deleted file mode 100644 index e5236a2..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/ListRow.scala +++ /dev/null @@ -1,47 +0,0 @@ -package works.iterative.ui.components.tailwind -package list - -import com.raquo.laminar.api.L.{*, given} -import org.scalajs.dom - -object ListRow: - case class ViewModel( - title: String, - topRight: Modifier[HtmlElement], - bottomLeft: Modifier[HtmlElement], - bottomRight: Modifier[HtmlElement], - farRight: Modifier[HtmlElement], - containerElement: HtmlElement = div() - ) - - def apply($m: Signal[ViewModel]): HtmlElement = - li( - child <-- $m.map(m => - m.containerElement.amend( - cls := "block hover:bg-gray-50", - div( - cls := "px-4 py-4 sm:px-6 items-center flex", - div( - cls := "min-w-0 flex-1 pr-4", - div( - cls := "flex items-center justify-between", - p( - cls := "text-sm font-medium text-indigo-600 truncate", - m.title - ), - div( - cls := "ml-2 flex-shrink-0 flex", - m.topRight - ) - ), - div( - cls := "mt-2 sm:flex sm:justify-between", - m.bottomLeft, - m.bottomRight - ) - ), - m.farRight - ) - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/PropList.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/PropList.scala deleted file mode 100644 index 946ffaf..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/PropList.scala +++ /dev/null @@ -1,16 +0,0 @@ -package works.iterative.ui.components.tailwind -package list - -import com.raquo.laminar.api.L.{*, given} - -object PropList: - type ViewModel = List[HtmlElement] - def render($m: Signal[ViewModel]): HtmlElement = - div( - cls := "sm:flex", - children <-- $m.map(_.zipWithIndex.map { case (i, idx) => - i.amend( - cls := Map("mt-2 sm:mt-0 sm:ml-6" -> (idx == 0)) - ) - }) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/RowNext.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/RowNext.scala deleted file mode 100644 index b53f3f2..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/RowNext.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.ui.components.tailwind -package list - -import com.raquo.laminar.api.L.{*, given} - -object RowNext: - def render: HtmlElement = - div( - cls := "flex-shrink-0", - Icons.solid.`chevron-right`().amend(svg.cls := "text-gray-400") - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/RowTag.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/RowTag.scala deleted file mode 100644 index 129abe4..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/RowTag.scala +++ /dev/null @@ -1,17 +0,0 @@ -package works.iterative.ui.components.tailwind -package list - -import com.raquo.laminar.api.L.{*, given} - -object RowTag: - case class ViewModel(text: String, color: Color) - def render($m: Signal[ViewModel]): HtmlElement = - inline def colorClass(color: Color): Seq[String] = - import ColorWeight._ - List(color.bg(w100), color.text(w800)) - - p( - cls := "px-2 inline-flex text-xs leading-5 font-semibold rounded-full", - cls <-- $m.map(t => colorClass(t.color)), - child.text <-- $m.map(_.text) - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala deleted file mode 100644 index e7e6627..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/CommandHandlerException.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.akka - -// Base class for all command-processing related exceptions from handlers -sealed abstract class CommandHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandNotAvailable[C, S](cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd není dostupný ve stavu $state" - ) - -// TODO: use a typeclass like "Show" to create the error message -case class CommandRejected[C, S](reason: String, cmd: C, state: S) - extends CommandHandlerException( - s"Příkaz $cmd byl ve stavu $state odmítnut s odůvodněním $reason" - ) diff --git a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala b/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala deleted file mode 100644 index 72a5bf9..0000000 --- a/iw/akka-persistence/src/main/scala/fiftyforms/akka/EventHandlerException.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.akka - -sealed abstract class EventHandlerException( - msg: String, - cause: Option[Throwable] = None -) extends Exception(msg, cause.orNull) - -case class UnhandledEvent[Event, State](event: Event, state: State) - extends EventHandlerException( - s"Událost $event nastala ve stavu $state bez možnosti zpracování" - ) diff --git a/iw/bom.sc b/iw/bom.sc deleted file mode 100644 index e9266b8..0000000 --- a/iw/bom.sc +++ /dev/null @@ -1,227 +0,0 @@ -import mill._, scalalib._ - -object IWMaterials { - - object Versions { - val akka = "2.6.16" - val akkaHttp = "10.2.4" - val cats = "2.7.0" - val elastic4s = "7.12.2" - val http4s = "0.23.10" - val http4sPac4J = "4.0.0" - val laminar = "0.14.2" - val laminext = laminar - val logbackClassic = "1.2.10" - val pac4j = "5.2.0" - val play = "2.8.8" - val playJson = "2.9.2" - val scalaTest = "3.2.9" - val slick = "3.3.3" - val sttpClient = "3.5.0" - val tapir = "0.20.1" - val urlDsl = "0.4.0" - val waypoint = "0.5.0" - val zio = "2.0.0-RC2" - val zioConfig = "3.0.0-RC2" - val zioInteropCats = "3.3.0-RC2" - val zioJson = "0.3.0-RC3" - val zioLogging = "2.0.0-RC5" - val zioPrelude = "1.0.0-RC10" - val zioZMX = "0.0.11" - } - - object Deps extends AkkaLibs with SlickLibs { - import IWMaterials.{Versions => V} - - val zioOrg = "dev.zio" - - def zioLib(name: String, version: String): Dep = - ivy"$zioOrg::zio-$name::$version" - - lazy val zio: Dep = ivy"$zioOrg::zio:${V.zio}" - - lazy val zioTest: Dep = zioLib("test", V.zio) - lazy val zioTestSbt: Dep = zioLib("test", V.zio) - - lazy val zioConfig: Dep = zioLib("config", V.zioConfig) - lazy val zioConfigTypesafe: Dep = - zioLib("config-typesafe", V.zioConfig) - lazy val zioConfigMagnolia: Dep = - zioLib("config-magnolia", V.zioConfig) - - lazy val zioJson: Dep = zioLib("json", V.zioJson) - lazy val zioLogging: Dep = zioLib("logging", V.zioLogging) - lazy val zioLoggingSlf4j: Dep = - zioLib("logging-slf4j", V.zioLogging) - lazy val zioPrelude: Dep = zioLib("prelude", V.zioPrelude) - lazy val zioStreams: Dep = zioLib("streams", V.zio) - lazy val zioZMX: Dep = zioLib("zmx", V.zioZMX) - lazy val zioInteropCats: Dep = - zioLib("interop-cats", V.zioInteropCats) - - /* What is the equivalent? ZIOModule with prepared test config? - def useZIO(testConf: Configuration*): Agg[Dep] = Agg( - zio, - zioTest, - zioTestSbt, - testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework") - ) - */ - - /* - def useZIOAll(testConf: Configuration*): Seq[Def.Setting[_]] = - useZIO(testConf: _*) ++ Seq( - zioStreams, - zioConfig, - zioConfigTypesafe, - zioConfigMagnolia, - zioJson, - zioZMX, - zioLogging, - zioPrelude - ) - */ - - private val tapirOrg = "com.softwaremill.sttp.tapir" - def tapirLib(name: String): Dep = - ivy"${tapirOrg}::tapir-$name::${V.tapir}" - - lazy val tapirCore: Dep = tapirLib("core") - lazy val tapirZIO: Dep = tapirLib("zio") - lazy val tapirZIOJson: Dep = tapirLib("json-zio") - lazy val tapirSttpClient: Dep = tapirLib("sttp-client") - lazy val tapirCats: Dep = tapirLib("cats") - lazy val tapirZIOHttp4sServer: Dep = tapirLib("zio-http4s-server") - - private val sttpClientOrg = "com.softwaremill.sttp.client3" - def sttpClientLib(name: String): Dep = - ivy"${sttpClientOrg}::${name}:${V.sttpClient}" - - lazy val sttpClientCore: Dep = sttpClientLib("core") - - lazy val http4sBlazeServer: Dep = - ivy"org.http4s::http4s-blaze-server:${V.http4s}" - - lazy val http4sPac4J: Dep = - ivy"org.pac4j::http4s-pac4j:${V.http4sPac4J}" - lazy val pac4jOIDC: Dep = - ivy"org.pac4j:pac4j-oidc:${V.pac4j}" - - lazy val scalaTest: Dep = - ivy"org.scalatest::scalatest:${V.scalaTest}" - lazy val scalaTestPlusScalacheck: Dep = - ivy"org.scalatestplus::scalacheck-1-15:3.2.9.0" - lazy val playScalaTest: Dep = - ivy"org.scalatestplus.play::scalatestplus-play:5.1.0" - - private val playOrg = "com.typesafe.play" - lazy val playMailer: Dep = ivy"${playOrg}::play-mailer:8.0.1" - lazy val playServer: Dep = ivy"${playOrg}::play-server:${V.play}" - lazy val playAkkaServer: Dep = - ivy"${playOrg}::play-akka-http-server:${V.play}" - lazy val play: Dep = ivy"${playOrg}::play:${V.play}" - lazy val playAhcWs: Dep = ivy"${playOrg}::play-ahc-ws:${V.play}" - lazy val playJson: Dep = ivy"${playOrg}::play-json:${V.playJson}" - - private val elastic4sOrg = "com.sksamuel.elastic4s" - lazy val useElastic4S: Agg[Dep] = Agg( - ivy"${elastic4sOrg}::elastic4s-core:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-client-akka:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-http-streams:${V.elastic4s}", - ivy"${elastic4sOrg}::elastic4s-json-play:${V.elastic4s}" - ) - - lazy val laminar: Dep = ivy"com.raquo::laminar::${V.laminar}" - - private def laminext(name: String): Dep = - ivy"io.laminext::$name::${V.laminar}" - - lazy val laminextCore: Dep = laminext("core") - lazy val laminextTailwind: Dep = laminext("tailwind") - lazy val laminextFetch: Dep = laminext("fetch") - lazy val laminextValidationCore: Dep = laminext("validation-core") - lazy val laminextUI: Dep = laminext("ui") - - lazy val waypoint: Dep = - ivy"com.raquo::waypoint::${V.waypoint}" - - lazy val urlDsl: Dep = - ivy"be.doeraene::url-dsl::${V.urlDsl}" - - lazy val scalaJavaTime: Dep = - ivy"io.github.cquiroz::scala-java-time::2.3.0" - - lazy val scalaJavaLocales: Dep = - ivy"io.github.cquiroz::scala-java-locales::1.2.1" - - lazy val logbackClassic: Dep = - ivy"ch.qos.logback:logback-classic:${V.logbackClassic}" - } - - trait AkkaLibs { - - self: SlickLibs => - - object akka { - val V = Versions.akka - val tOrg = "com.typesafe.akka" - val lOrg = "com.lightbend.akka" - - def akkaMod(name: String): Dep = - ivy"$tOrg::akka-$name:$V" - - lazy val actor: Dep = akkaMod("actor") - lazy val actorTyped: Dep = akkaMod("actor-typed") - lazy val stream: Dep = akkaMod("stream") - lazy val persistence: Dep = akkaMod("persistence-typed") - lazy val persistenceQuery: Dep = akkaMod("persistence-query") - lazy val persistenceJdbc: Dep = - ivy"$lOrg::akka-persistence-jdbc:5.0.4" - val persistenceTestKit: Dep = ivy"$tOrg::akka-persistence-testkit:$V" - - object http { - val V = Versions.akkaHttp - - lazy val http: Dep = ivy"$tOrg::akka-http:$V" - lazy val sprayJson: Dep = ivy"$tOrg::akka-http-spray-json:$V" - - } - - object projection { - val V = "1.2.2" - - lazy val core: Dep = - ivy"$lOrg::akka-projection-core:$V" - lazy val eventsourced: Dep = - ivy"$lOrg::akka-projection-eventsourced:$V" - lazy val slick: Dep = - ivy"$lOrg::akka-projection-slick:$V" - lazy val jdbc: Dep = - ivy"$lOrg::akka-projection-jdbc:$V" - } - - object profiles { - // TODO: deal with cross-version for Scala3 (for3Use2_13) - lazy val eventsourcedJdbcProjection: Agg[Dep] = Agg( - persistenceQuery, - projection.core, - projection.eventsourced, - projection.slick, - persistenceJdbc - ) ++ slick.default - } - } - } - - trait SlickLibs { - object slick { - val V = IWMaterials.Versions.slick - val org = "com.typesafe.slick" - - lazy val slick: Dep = ivy"$org::slick:$V" - lazy val hikaricp: Dep = ivy"$org::slick-hikaricp:$V" - lazy val default: Agg[Dep] = Agg(slick, hikaricp) - } - } - -} diff --git a/iw/build.sc b/iw/build.sc deleted file mode 100644 index fbbc3b8..0000000 --- a/iw/build.sc +++ /dev/null @@ -1,144 +0,0 @@ -import mill._, scalalib._, scalajslib._ - -import $file.bom - -object support { - val Deps = bom.IWMaterials.Deps - val Versions = bom.IWMaterials.Versions - - trait CommonModule extends ScalaModule { - def scalaVersion = "3.1.1" - def scalacOptions = Seq( - "-encoding", - "utf8", - "-deprecation", - "-explain-types", - "-explain", - "-feature", - "-language:experimental.macros", - "-language:higherKinds", - "-language:implicitConversions", - "-unchecked", - "-Xfatal-warnings", - "-Ykind-projector" - ) - } - - trait CommonJSModule extends CommonModule with ScalaJSModule { - def scalaJSVersion = "1.8.0" - } - - trait CrossPlatformModule extends Module { outer => - def ivyDeps: T[Agg[Dep]] = Agg[Dep]() - def jsDeps: T[Agg[Dep]] = Agg[Dep]() - def jvmDeps: T[Agg[Dep]] = Agg[Dep]() - def moduleDeps: Seq[Module] = Seq[Module]() - - trait PlatformModule extends CommonModule { - def platform: String - override def millSourcePath = outer.millSourcePath - override def ivyDeps = outer.ivyDeps() ++ (platform match { - case "js" => jsDeps() - case _ => jvmDeps() - }) - override def moduleDeps = outer.moduleDeps.collect { - case m: CrossPlatformModule => - platform match { - case "js" => m.js - case _ => m.jvm - } - case m: JavaModule => m - } - } - - trait JsModule extends PlatformModule with CommonJSModule { - def platform = "js" - } - - trait JvmModule extends PlatformModule { - def platform = "jvm" - } - - val js: JsModule - val jvm: JvmModule - } - - trait PureCrossModule extends CrossPlatformModule { - override object js extends JsModule - override object jvm extends JvmModule - } - - trait PureCrossSbtModule extends CrossPlatformModule { - override object js extends JsModule with SbtModule - override object jvm extends JvmModule with SbtModule - } - - trait FullCrossSbtModule extends CrossPlatformModule { - trait FullSources extends JavaModule { self: PlatformModule => - override def sources = T.sources( - millSourcePath / platform / "src" / "main" / "scala", - millSourcePath / "shared" / "src" / "main" / "scala" - ) - - override def resources = T.sources( - millSourcePath / platform / "src" / "main" / "resources", - millSourcePath / "shared" / "src" / "main" / "resources" - ) - } - - override object js extends JsModule with FullSources - override object jvm extends JvmModule with FullSources - } - -} - -import support._ - -object mongo extends CommonModule { - def ivyDeps = Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - ivy"org.mongodb.scala::mongo-scala-driver:4.2.3".withDottyCompat( - scalaVersion() - ) - ) -} - -object tapir extends FullCrossSbtModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson, Deps.zioJson) - def jvmDeps = Agg(Deps.tapirZIO, Deps.tapirZIOHttp4sServer) - def jsDeps = Agg(Deps.tapirSttpClient) -} - -object akkaPersistence extends CommonModule { - def millSourcePath = build.millSourcePath / "akka-persistence" - def ivyDeps = (Agg( - Deps.akka.persistenceQuery, - Deps.akka.persistenceJdbc, - Deps.akka.projection.core, - Deps.akka.projection.eventsourced, - Deps.akka.projection.slick - ) ++ Deps.slick.default).map(_.withDottyCompat(scalaVersion())) ++ Agg( - Deps.zio, - Deps.zioJson, - Deps.zioConfig, - Deps.akka.persistence.withDottyCompat(scalaVersion()), - ivy"com.typesafe.akka::akka-cluster-sharding-typed:${Deps.akka.V}" - .withDottyCompat(scalaVersion()) - ) -} - -object ui extends CommonJSModule { - def ivyDeps = Agg( - Deps.zio, - Deps.laminar, - Deps.zioJson, - Deps.waypoint, - Deps.urlDsl, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) -} diff --git a/iw/domain.sc b/iw/domain.sc deleted file mode 100644 index b09c56b..0000000 --- a/iw/domain.sc +++ /dev/null @@ -1,78 +0,0 @@ -import mill._, scalalib._ - -import $file.{build => ff}, ff.support._ - -trait DomainModule extends Module { - // General extra model deps - def modelModules: Seq[Module] = Seq.empty - // General extra codecs deps - def codecsModules: Seq[Module] = Seq.empty - // General extra endpoints deps - def endpointsModules: Seq[Module] = Seq.empty - - // Implementation deps for repo - def repoModules: Seq[JavaModule] = Seq.empty - - object shared extends Module { - object model extends PureCrossModule { - def moduleDeps = modelModules - def ivyDeps = Agg(Deps.zioPrelude) - } - object codecs extends PureCrossModule { - def moduleDeps = Seq(model) ++ codecsModules - def ivyDeps = Agg(Deps.zioJson) - } - } - - trait CommonProjects extends Module { - object model extends PureCrossModule { - def ivyDeps = Agg(Deps.zioPrelude) - def moduleDeps = Seq(shared.model) - } - object codecs extends PureCrossModule { - def moduleDeps = - Seq(model, shared.model, shared.codecs, ff.tapir) - } - object endpoints extends PureCrossModule { - def ivyDeps = Agg(Deps.tapirCore, Deps.tapirZIOJson) - def moduleDeps = Seq(model, codecs) ++ endpointsModules - } - object client extends CommonJSModule { - def moduleDeps = Seq(endpoints.js) - } - object components extends CommonJSModule { - def ivyDeps = Agg( - Deps.laminar, - Deps.laminextCore, - Deps.laminextUI, - Deps.laminextTailwind, - Deps.laminextValidationCore - ) - def moduleDeps = Seq(model.js, codecs.js, client, ff.ui) - } - } - - object query extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(repo, query.endpoints.jvm) - } - object repo extends CommonModule { - def ivyDeps = Agg(Deps.zio) - def moduleDeps = Seq(model.jvm, codecs.jvm) ++ repoModules - } - object projection extends CommonModule { - def moduleDeps = Seq(repo, ff.akkaPersistence) - } - } - - object command extends CommonProjects { - object api extends CommonModule { - def ivyDeps = Agg(Deps.zio, Deps.tapirZIOHttp4sServer) - def moduleDeps = Seq(entity, command.endpoints.jvm) - } - object entity extends CommonModule { - def moduleDeps = Seq(model.jvm, codecs.jvm, ff.akkaPersistence) - } - } -} diff --git a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala b/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala deleted file mode 100644 index 0fa3493..0000000 --- a/iw/mongo/src/main/scala/fiftyforms/mongo/MongoJsonRepository.scala +++ /dev/null @@ -1,53 +0,0 @@ -package works.iterative.mongo - -import zio.* -import zio.json.* -import zio.config.* -import org.mongodb.scala.* -import org.mongodb.scala.model.Filters.* -import org.bson.json.JsonObject -import org.mongodb.scala.model.ReplaceOptions -import org.mongodb.scala.bson.conversions.Bson - -case class MongoConfig(uri: String) - -object MongoConfig: - val configDesc = - import ConfigDescriptor.* - nested("MONGO")(string("URI").default("mongodb://localhost:27017")) - .to[MongoConfig] - val fromEnv = ZConfig.fromSystemEnv(configDesc) - -extension (m: MongoClient.type) - def layer: RLayer[MongoConfig, MongoClient] = - ZIO - .serviceWithZIO[MongoConfig](c => Task.attempt(MongoClient(c.uri))) - .toLayer - -class MongoJsonRepository[Elem, Key, Criteria]( - collection: MongoCollection[JsonObject], - toFilter: Criteria => Bson, - idFilter: Elem => (String, Key) -)(using JsonCodec[Elem]) { - def matching(criteria: Criteria): Task[Seq[Elem]] = - val filter = toFilter(criteria) - val query = collection.find(filter) - - for - result <- ZIO.fromFuture(_ => query.toFuture) - proof <- ZIO.collect(result)(j => - ZIO.fromOption(j.getJson.fromJson[Elem].toOption) - ) - yield proof - - def put(elem: Elem): Task[Unit] = - Task.async(cb => - collection - .replaceOne( - equal.tupled(idFilter(elem)), - JsonObject(elem.toJson), - ReplaceOptions().upsert(true) - ) - .subscribe(_ => cb(Task.unit), t => cb(Task.fail(t))) - ) -} diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala deleted file mode 100644 index 68d7196..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/BaseUri.scala +++ /dev/null @@ -1,12 +0,0 @@ -package works.iterative.tapir - -import sttp.model.Uri - -opaque type BaseUri = Option[Uri] - -object BaseUri: - - def apply(optU: Option[Uri]): BaseUri = optU - def apply(u: Uri): BaseUri = Some(u) - - extension (v: BaseUri) def toUri: Option[Uri] = v diff --git a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 653bc51..0000000 --- a/iw/tapir/js/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,22 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.client.sttp.SttpClientInterpreter -import sttp.tapir.PublicEndpoint -import sttp.tapir.client.sttp.WebSocketToPipe -import scala.concurrent.Future -import sttp.client3.SttpBackend -import sttp.capabilities.WebSockets - -trait CustomTapirPlatformSpecific extends SttpClientInterpreter: - self: CustomTapir => - - type Backend = SttpBackend[Future, WebSockets] - - def makeClient[I, E, O]( - endpoint: PublicEndpoint[I, E, O, Any] - )(using - baseUri: BaseUri, - backend: Backend, - wsToPipe: WebSocketToPipe[Any] - ): I => Future[O] = - toClientThrowErrors(endpoint, baseUri.toUri, backend) diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala deleted file mode 100644 index 2545fbd..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/CustomTapirPlatformSpecific.scala +++ /dev/null @@ -1,5 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.ztapir.ZTapir - -trait CustomTapirPlatformSpecific extends ZTapir diff --git a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala b/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala deleted file mode 100644 index 55e4ca1..0000000 --- a/iw/tapir/jvm/src/main/scala/fiftyforms/tapir/Http4sCustomTapir.scala +++ /dev/null @@ -1,7 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.server.http4s.ztapir.ZHttp4sServerInterpreter - -trait Http4sCustomTapir[Env] - extends CustomTapir - with ZHttp4sServerInterpreter[Env] diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala deleted file mode 100644 index dd52e9b..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/CustomTapir.scala +++ /dev/null @@ -1,14 +0,0 @@ -package works.iterative.tapir - -import sttp.tapir.Tapir -import sttp.tapir.json.zio.TapirJsonZio -import sttp.tapir.TapirAliases - -trait CustomTapir - extends Tapir - with TapirJsonZio - with TapirAliases - with CustomTapirPlatformSpecific: - given Schema[ServerError] = Schema.derived - -object CustomTapir extends CustomTapir diff --git a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala b/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala deleted file mode 100644 index 715591d..0000000 --- a/iw/tapir/shared/src/main/scala/fiftyforms/tapir/ServerError.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.tapir - -import zio.json.* - -sealed trait ServerError -case class InternalServerError(msg: String) extends ServerError -object InternalServerError: - def fromThrowable(t: Throwable): ServerError = InternalServerError( - t.getMessage - ) - -object ServerError: - given JsonCodec[ServerError] = DeriveJsonCodec.gen diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/File.scala deleted file mode 100644 index 09f6c07..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/File.scala +++ /dev/null @@ -1,3 +0,0 @@ -package works.iterative.services.files - -case class File(url: String, name: String) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala deleted file mode 100644 index 46633a5..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/File.scala +++ /dev/null @@ -1,25 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.Renderable - -given Renderable[File] with - extension (m: File) - def toHtml: HtmlElement = - li( - cls("pl-3 pr-4 py-3 flex items-center justify-between text-sm"), - div( - cls("w-0 flex-1 flex items-center"), - Icons.solid - .paperclip() - .amend(svg.cls := "flex-shrink-0 text-gray-400"), - span(cls("ml-2 flex-1 w-0 truncate"), m.name) - ), - a( - href(m.url), - cls("font-medium text-indigo-600 hover:text-indigo-500"), - "Otevřít" - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala deleted file mode 100644 index ac59db8..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileList.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.Icons -import io.laminext.syntax.core.{*, given} - -def FileList(files: List[File]): HtmlElement = - ul( - role("list"), - cls("border border-gray-200 rounded-md divide-y divide-gray-200"), - files.map(_.toHtml) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala deleted file mode 100644 index 2e8f690..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FilePicker.scala +++ /dev/null @@ -1,81 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import io.laminext.syntax.core.{*, given} - -object FilePicker: - sealed trait Event - sealed trait DoneEvent extends Event - case class SelectionUpdated(files: Set[File]) extends DoneEvent - case object SelectionCancelled extends DoneEvent - case object AvailableFilesRequested extends Event - - def apply( - currentFiles: Signal[List[File]], - availableFilesStream: EventStream[List[File]] - )(selectionUpdates: Observer[Event]): HtmlElement = - val (updatesStream, updatesObserver) = EventStream.withObserver[Event] - val selectorOpen = Var[Boolean](false) - - // This sequence tricks browser into displaying modal content centered - // Inspired by modal in headless ui playground - // https://github.com/tailwindlabs/headlessui/blob/fdd26297953080d5ec905dda0bf5ec9607897d86/packages/playground-react/pages/transitions/component-examples/modal.tsx#L78-L79 - inline def browserCenteringModalTrick: Modifier[HtmlElement] = - Seq[Modifier[HtmlElement]]( - span(cls("hidden sm:inline-block sm:h-screen sm:align-middle")), - "​" // Zero width space - ) - - inline def overlay: Modifier[HtmlElement] = - // Page overlay - /* TODO: transition - enter="ease-out duration-300" - enterFrom="opacity-0" - enterTo="opacity-100" - leave="ease-in duration-200" - leaveFrom="opacity-100" - leaveTo="opacity-0" - */ - div( - div( - onClick.mapTo(false) --> selectorOpen.writer, - cls("fixed inset-0 transition-opacity"), - div(cls("absolute inset-0 bg-gray-500 opacity-75")) - ) - ) - - inline def modalSelector: HtmlElement = - div( - cls("fixed inset-0 z-10 overflow-y-auto"), - div( - cls("text-center sm:block sm:p-0"), - overlay, - browserCenteringModalTrick, - child <-- currentFiles.map( - FileSelector(_, availableFilesStream)(updatesObserver) - ) - ) - ) - - div( - updatesStream --> selectionUpdates, - updatesStream.collect { case _: DoneEvent => - false - } --> selectorOpen.writer, - child.maybe <-- selectorOpen.signal.map(isOpen => - if isOpen then Some(modalSelector) else None - ), - div( - cls("flex flex-col space-y-5"), - child.maybe <-- currentFiles.map(files => - if files.isEmpty then None else Some(FileList(files)) - ), - button( - tpe := "button", - cls := "bg-white py-2 px-3 border border-gray-300 rounded-md shadow-sm text-sm leading-4 font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500", - "Zvolit soubory", - onClick.mapTo(true) --> selectorOpen.writer - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala deleted file mode 100644 index b43e07a..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileSelector.scala +++ /dev/null @@ -1,74 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.Loading -import io.laminext.syntax.core.{*, given} -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -object FileSelector: - import FilePicker._ - - def apply( - initialFiles: List[File], - availableFilesStream: EventStream[List[File]] - )(selectionUpdates: Observer[Event]): HtmlElement = - val selectedFiles = Var[Set[File]](initialFiles.to(Set)) - val availableFiles = availableFilesStream.startWithNone - // Request the files to display - selectionUpdates.onNext(AvailableFilesRequested) - div( - cls( - "inline-block transform overflow-hidden rounded-lg bg-white text-left align-bottom shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:align-middle" - ), - role("dialog"), - customHtmlAttr("aria.modal", BooleanAsTrueFalseStringCodec)(true), - aria.labelledBy("modal-headline"), - div( - cls("bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"), - div( - cls("sm:flex sm:items-start"), - div( - cls("mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"), - h3( - cls("text-lg font-medium leading-6 text-gray-900"), - idAttr("modal-headline"), - "Výběr souborů" - ) - ) - ), - child <-- availableFiles - .split(_ => ())((_, _, af) => FileTable(af, selectedFiles)) - .map(_.getOrElse(Loading)) - ), - div( - cls("bg-gray-50 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6"), - span( - cls("flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto"), - button( - typ("button"), - cls( - "focus:shadow-outline-green inline-flex w-full justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-base font-medium leading-6 text-white shadow-sm transition duration-150 ease-in-out hover:bg-indigo-500 focus:border-indigo-700 focus:outline-none sm:text-sm sm:leading-5" - ), - "Potvrdit", - composeEvents(onClick)( - _.sample(selectedFiles) - .map(SelectionUpdated(_)) - ) --> selectionUpdates - ) - ), - span( - cls("mt-3 flex w-full rounded-md shadow-sm sm:mt-0 sm:w-auto"), - button( - typ("button"), - cls( - "focus:shadow-outline-blue inline-flex w-full justify-center rounded-md border border-gray-300 bg-white px-4 py-2 text-base font-medium leading-6 text-gray-700 shadow-sm transition duration-150 ease-in-out hover:text-gray-500 focus:border-blue-300 focus:outline-none sm:text-sm sm:leading-5" - ), - "Zrušit", - onClick.mapTo(SelectionCancelled) --> selectionUpdates - ) - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala b/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala deleted file mode 100644 index aa11b2b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/services/files/components/tailwind/FileTable.scala +++ /dev/null @@ -1,91 +0,0 @@ -package works.iterative.services.files -package components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import works.iterative.ui.components.tailwind.Icons - -def FileTable( - files: Signal[List[File]], - selectedFiles: Var[Set[File]] -): HtmlElement = - val scope = customHtmlAttr("scope", StringAsIsCodec) - - def headerRow: HtmlElement = - val col = scope("col") - val baseM: Modifier[HtmlElement] = Seq(cls("px-6 py-3"), col) - val textH = cls( - "text-left text-xs font-medium text-gray-500 uppercase tracking-wider" - ) - tr( - th(baseM, span(cls("sr-only"), "Vybrat")), - th(baseM, textH, "Název"), - th(baseM, cls("relative"), span(cls("sr-only"), "Otevřít")) - ) - - def tableRow( - f: File, - idx: Int, - selected: Boolean - )(toggleSelection: Observer[Unit]): HtmlElement = - val baseC = cls("px-6 py-4 whitespace-nowrap text-sm") - tr( - cls(if idx % 2 == 0 then "bg-gray-50" else "bg-white"), - td( - cls("font-medium cursor-pointer"), - onClick.mapTo(()) --> toggleSelection, - cls(if selected then "text-green-900" else "text-gray-200"), - Icons.outline.`check-circle`().amend(svg.cls := "mx-auto"), - span(cls("sr-only"), if selected then "Vybráno" else "Nevybráno") - ), - td( - baseC, - cls("font-medium text-gray-900"), - f.name, - onClick.mapTo(()) --> toggleSelection - ), - td( - baseC, - cls("text-right font-medium"), - a( - href(f.url), - target("_blank"), - cls( - "flex items-center space-x-4 text-indigo-600 hover:text-indigo-900" - ), - Icons.outline.`external-link`(), - "Otevřít" - ) - ) - ) - - div( - cls("flex flex-col"), - div(cls("overflow-x-auto sm:-mx-6 lg:-mx-8")), - div( - cls("py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8"), - div( - cls("shadow overflow-hidden border-b border-gray-200 sm:rounded-lg"), - table( - cls("min-w-full divide-y divide-gray-200"), - thead( - cls("bg-gray-50"), - headerRow - ), - tbody( - children <-- files - .map(_.zipWithIndex) - .combineWithFn(selectedFiles)((f, sel) => - f.map((file, idx) => - val active = sel.contains(file) - tableRow(file, idx, active)( - selectedFiles.writer - .contramap(_ => if active then sel - file else sel + file) - ) - ) - ) - ) - ) - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala deleted file mode 100644 index 0fc8796..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Avatar.scala +++ /dev/null @@ -1,32 +0,0 @@ -package works.iterative.ui.components.tailwind - -import CustomAttrs.ariaHidden -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -// TODO: render icon or picture based on img signal -class Avatar($avatarImg: Signal[Option[String]]): - inline def avatarPlaceholder(size: Int): HtmlElement = - div( - cls := s"rounded-full text-indigo-200 bg-indigo-500 h-${size} w-${size} flex items-center justify-center", - Icons.outline.user(size - 2) - ) - - inline def avatarImage(size: Int): Signal[HtmlElement] = - $avatarImg.split(_ => ())((_, _, $url) => - img( - cls := s"w-$size h-$size rounded-full", - src <-- $url, - alt := "" - ) - ).map(_.getOrElse(avatarPlaceholder(size))) - - inline def avatar(size: Int): HtmlElement = - div( - cls := "relative", - child <-- avatarImage(size), - span( - cls := "absolute inset-0 shadow-inner rounded-full", - ariaHidden := true - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala deleted file mode 100644 index bcbed88..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Color.scala +++ /dev/null @@ -1,77 +0,0 @@ -package works.iterative.ui.components.tailwind - -enum ColorWeight(value: String): - inline def toCSS: String = value - // To be used for black and white until - // we support better mechanism via extension methods - case w__ extends ColorWeight("") - case w50 extends ColorWeight("50") - case w100 extends ColorWeight("100") - case w200 extends ColorWeight("200") - case w300 extends ColorWeight("300") - case w400 extends ColorWeight("400") - case w500 extends ColorWeight("500") - case w600 extends ColorWeight("600") - case w700 extends ColorWeight("700") - case w800 extends ColorWeight("800") - case w900 extends ColorWeight("900") - -enum Color(name: String): - import ColorWeight._ - - inline def toCSSNoColorWeight(prefix: String): String = - s"${prefix}-${name}" - inline def toCSSWithColorWeight( - prefix: String, - weight: ColorWeight - ): String = - s"${prefix}-${name}-${weight.toCSS}" - inline def toCSS(prefix: String)(weight: ColorWeight): String = - weight match { - case `w__` => toCSSNoColorWeight(prefix) - case _ => toCSSWithColorWeight(prefix, weight) - } - inline def bg: ColorWeight => String = toCSS("bg")(_) - inline def text: ColorWeight => String = toCSS("text")(_) - inline def decoration: ColorWeight => String = toCSS("decoration")(_) - inline def border: ColorWeight => String = toCSS("border")(_) - inline def outline: ColorWeight => String = toCSS("outline")(_) - inline def divide: ColorWeight => String = toCSS("divide")(_) - inline def ring: ColorWeight => String = toCSS("ring")(_) - inline def ringOffset: ColorWeight => String = toCSS("ring-offset")(_) - inline def shadow: ColorWeight => String = toCSS("shadow")(_) - inline def accent: ColorWeight => String = toCSS("accent")(_) - - // TODO: change the "stupid" methods to extension methods - // that will keep the invariants in comments lower - case current extends Color("current") - case inherit extends Color("inherit") - // Not present in for all methods - case transp extends Color("transparent") - // Seen in accent, not preset otherwise - case auto extends Color("auto") - // Black and white do not have weight - case black extends Color("black") - case white extends Color("white") - case slate extends Color("slate") - case gray extends Color("gray") - case zinc extends Color("zinc") - case neutral extends Color("neutral") - case stone extends Color("stone") - case red extends Color("red") - case orange extends Color("orange") - case amber extends Color("amber") - case yellow extends Color("yellow") - case lime extends Color("lime") - case green extends Color("green") - case emerald extends Color("emerald") - case teal extends Color("teal") - case cyan extends Color("cyan") - case sky extends Color("sky") - case blue extends Color("blue") - case indigo extends Color("indigo") - case violet extends Color("violet") - case purple extends Color("purple") - case fuchsia extends Color("fuchsia") - case pink extends Color("pink") - case rose extends Color("rose") diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala deleted file mode 100644 index 6fb453c..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/CustomAttrs.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.generic.codecs.StringAsIsCodec -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec - -object CustomAttrs { - // Made a pull request to add aria-current to scala-dom-types, remove after - val ariaCurrent = customHtmlAttr("aria-current", StringAsIsCodec) - val ariaHidden = customHtmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - - val datetime = customHtmlAttr("datetime", StringAsIsCodec) - - object svg { - import com.raquo.laminar.api.L.svg.{*, given} - val ariaHidden = - customSvgAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - } -} diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala deleted file mode 100644 index 6cda48b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Display.scala +++ /dev/null @@ -1,28 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -object Display: - - enum Breakpoint: - case sm, md, lg, xl, `2xl` - - enum DisplayClass: - case block, `inline-block`, `inline`, flex, `inline-flex`, table, - `inline-table`, `table-caption` - - object ShowUpFrom: - inline def apply( - br: Breakpoint, - dc: DisplayClass = DisplayClass.block - ): HtmlElement = - div( - cls := "hidden", - cls := s"${br}:${dc}" - ) - - object HideUpTo: - inline def apply(br: Breakpoint): HtmlElement = - div( - cls := s"${br}:hidden" - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala deleted file mode 100644 index 4a8b065..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Icons.scala +++ /dev/null @@ -1,270 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.domtypes.generic.codecs.BooleanAsTrueFalseStringCodec -import com.raquo.domtypes.generic.defs.attrs.AriaAttrs -import com.raquo.laminar.api.L.svg.{*, given} -import com.raquo.laminar.builders.SvgBuilders -import com.raquo.laminar.keys.ReactiveSvgAttr - -// TODO: fix sizes, colors, hover and stuff, normalize and amend on call site -object Icons: - object aria: - inline def hidden = CustomAttrs.svg.ariaHidden - - object outline: - val defaultSize: Int = 6 - - inline def bell(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" - ) - ) - - inline def `check-circle`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" - ) - ) - - inline def `document-add`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M9 13h6m-3-3v6m5 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" - ) - ) - - inline def `external-link`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" - ) - ) - - inline def menu(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M4 6h16M4 12h16M4 18h16" - ) - ) - - inline def `status-offline`(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M18.364 5.636a9 9 0 010 12.728m0 0l-2.829-2.829m2.829 2.829L21 21M15.536 8.464a5 5 0 010 7.072m0 0l-2.829-2.829m-4.243 2.829a4.978 4.978 0 01-1.414-2.83m-1.414 5.658a9 9 0 01-2.167-9.238m7.824 2.167a1 1 0 111.414 1.414m-1.414-1.414L3 3m8.293 8.293l1.414 1.414" - ) - ) - - inline def user(size: Int = defaultSize) = - svg( - cls := s"w-${size} h-${size}", - fill := "none", - stroke := "currentColor", - viewBox := "0 0 24 24", - xmlns := "http://www.w3.org/2000/svg", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" - ) - ) - - inline def x(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - fill := "none", - viewBox := "0 0 24 24", - stroke := "currentColor", - aria.hidden := true, - path( - strokeLineCap := "round", - strokeLineJoin := "round", - strokeWidth := "2", - d := "M6 18L18 6M6 6l12 12" - ) - ) - - end outline - - object solid: - val defaultSize: Int = 5 - - inline def users(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - d := "M9 6a3 3 0 11-6 0 3 3 0 016 0zM17 6a3 3 0 11-6 0 3 3 0 016 0zM12.93 17c.046-.327.07-.66.07-1a6.97 6.97 0 00-1.5-4.33A5 5 0 0119 16v1h-6.07zM6 11a5 5 0 015 5v1H1v-1a5 5 0 015-5z" - ) - ) - - inline def `location-marker`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M5.05 4.05a7 7 0 119.9 9.9L10 18.9l-4.95-4.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z", - clipRule := "evenodd" - ) - ) - - inline def calendar(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z", - clipRule := "evenodd" - ) - ) - - inline def `chevron-right`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z", - clipRule := "evenodd" - ) - ) - - inline def search(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z", - clipRule := "evenodd" - ) - ) - - inline def filter(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M3 3a1 1 0 011-1h12a1 1 0 011 1v3a1 1 0 01-.293.707L12 11.414V15a1 1 0 01-.293.707l-2 2A1 1 0 018 17v-5.586L3.293 6.707A1 1 0 013 6V3z", - clipRule := "evenodd" - ) - ) - - inline def `arrow-narrow-left`(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M7.707 14.707a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 1.414L5.414 9H17a1 1 0 110 2H5.414l2.293 2.293a1 1 0 010 1.414z", - clipRule := "evenodd" - ) - ) - - inline def home(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size}", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - d := "M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z" - ) - ) - - inline def paperclip(size: Int = defaultSize) = - svg( - cls := s"h-${size} w-${size} text-gray-400", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - aria.hidden := true, - path( - fillRule := "evenodd", - d := "M8 4a3 3 0 00-3 3v4a5 5 0 0010 0V7a1 1 0 112 0v4a7 7 0 11-14 0V7a5 5 0 0110 0v4a3 3 0 11-6 0V7a1 1 0 012 0v4a1 1 0 102 0V7a3 3 0 00-3-3z", - clipRule := "evenodd" - ) - ) - - end solid -end Icons diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/LinkSupport.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/LinkSupport.scala deleted file mode 100644 index 88f0b9a..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/LinkSupport.scala +++ /dev/null @@ -1,12 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.domtypes.jsdom.defs.events.TypedTargetMouseEvent - -object LinkSupport: - - extension [El <: org.scalajs.dom.EventTarget]( - ep: EventProcessor[TypedTargetMouseEvent[El], TypedTargetMouseEvent[El]] - ) - def noKeyMod = - ep.filter(ev => !(ev.ctrlKey || ev.metaKey || ev.shiftKey || ev.altKey)) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Loader.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Loader.scala deleted file mode 100644 index 5399933..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Loader.scala +++ /dev/null @@ -1,13 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -// TODO: proper loader -def Loading = - div( - cls := "bg-gray-50 overflow-hidden rounded-lg", - div( - cls := "px-4 py-5 sm:p-6", - "Loading..." - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Renderable.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Renderable.scala deleted file mode 100644 index a0fcc5b..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/Renderable.scala +++ /dev/null @@ -1,6 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -trait Renderable[A]: - extension (a: A) def toHtml: HtmlElement diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/ComboBox.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/ComboBox.scala deleted file mode 100644 index 8c5b8a1..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/ComboBox.scala +++ /dev/null @@ -1,92 +0,0 @@ -package works.iterative.ui.components.tailwind -package form - -import com.raquo.laminar.api.L.{*, given} -import io.laminext.syntax.core.* - -case class ComboBox( - id: String, - options: Signal[List[ComboBox.Option]], - valueUpdates: Observer[List[String]] -) - -object ComboBox: - - extension (m: ComboBox) - def toHtml: HtmlElement = - val isOpen = Var(false) - div( - cls := "relative mt-1", - input( - idAttr := m.id, - tpe := "text", - cls := "w-full rounded-md border border-gray-300 bg-white py-2 pl-3 pr-12 shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 sm:text-sm", - role := "combobox", - aria.controls := "options", - aria.expanded := false, - onClick.mapTo(true) --> isOpen.writer - ), - button( - tpe := "button", - cls := "absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-none", { - import svg.* - svg( - cls := "h-5 w-5 text-gray-400", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - CustomAttrs.svg.ariaHidden := true, - path( - fillRule := "evenodd", - d := "M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z", - clipRule := "evenodd" - ) - ) - } - ), - ul( - cls <-- isOpen.signal.switch("", "hidden"), - cls := "absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm", - idAttr := "options", - role := "listbox", - children <-- m.options.map(_.map(_.toHtml)) - ) - ) - - case class Option(value: String, active: Boolean) - - object Option: - extension (m: Option) - def toHtml: HtmlElement = - li( - cls := "relative cursor-default select-none py-2 pl-8 pr-4", - cls := (if m.active then "text-white bg-indigo-600" - else "text-gray-900"), - idAttr := "option-0", - role := "option", - tabIndex := -1, - span( - cls := "block truncate", - m.value - ), - if m.active then - span( - cls := "absolute inset-y-0 left-0 flex items-center pl-1.5", - cls := (if m.active then "text-white" else "text-indigo-600"), { - import svg.* - svg( - cls := "h-5 w-5", - xmlns := "http://www.w3.org/2000/svg", - viewBox := "0 0 20 20", - fill := "currentColor", - CustomAttrs.svg.ariaHidden := true, - path( - fillRule := "evenodd", - d := "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", - clipRule := "evenodd" - ) - ) - } - ) - else emptyNode - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/Form.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/Form.scala deleted file mode 100644 index 1b41dee..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/Form.scala +++ /dev/null @@ -1,17 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} -import org.scalajs.dom -import com.raquo.laminar.nodes.ReactiveHtmlElement - -object Form: - val Body = FormBody - val Section = FormSection - val Row = FormRow - - def apply(body: HtmlElement, buttons: HtmlElement): HtmlElement = - form( - cls := "space-y-8 divide-y divide-gray-200", - body, - buttons - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormBody.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormBody.scala deleted file mode 100644 index 89abc9c..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormBody.scala +++ /dev/null @@ -1,10 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} - -object FormBody: - def apply(sections: HtmlElement*): HtmlElement = - div( - cls := "space-y-8 divide-y divide-gray-200 sm:space-y-5", - sections - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormFields.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormFields.scala deleted file mode 100644 index a2e4cc6..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormFields.scala +++ /dev/null @@ -1,14 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.laminar.nodes.ReactiveHtmlElement -import org.scalajs.dom - -object FormFields: - def apply( - mods: Modifier[ReactiveHtmlElement[dom.HTMLElement]]* - ): HtmlElement = - div( - cls := "mt-6 sm:mt-5 space-y-6 sm:space-y-5", - mods - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormHeader.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormHeader.scala deleted file mode 100644 index 4083841..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormHeader.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} - -object FormHeader: - case class ViewModel(header: String, description: String) - def apply(m: ViewModel): HtmlElement = - div( - h3(cls := "text-lg leading-6 font-medium text-gray-900", m.header), - p(cls := "mt-1 max-w-2xl text-sm text-gray-500", m.description) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormRow.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormRow.scala deleted file mode 100644 index ffc5807..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormRow.scala +++ /dev/null @@ -1,22 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} - -case class FormRow(id: String, label: String, content: Modifier[Div]) - -object FormRow: - - extension (m: FormRow) - def toHtml: HtmlElement = - div( - cls := "sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5", - label( - forId := m.id, - cls := "block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2", - m.label - ), - div( - cls := "mt-1 sm:mt-0 sm:col-span-2", - m.content - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormSection.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormSection.scala deleted file mode 100644 index 50811ee..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/form/FormSection.scala +++ /dev/null @@ -1,15 +0,0 @@ -package works.iterative.ui.components.tailwind.form - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.laminar.nodes.ReactiveHtmlElement - -object FormSection: - def apply( - header: HtmlElement, - rows: HtmlElement* - ): HtmlElement = - div( - cls := "space-y-6 sm:space-y-5", - header, - rows - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/BaseList.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/BaseList.scala deleted file mode 100644 index 66506e9..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/BaseList.scala +++ /dev/null @@ -1,142 +0,0 @@ -package works.iterative.ui.components.tailwind -package list - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.waypoint.Router -import com.raquo.laminar.builders.HtmlTag -import org.scalajs.dom - -trait ListItem: - def key: String - def title: Modifier[HtmlElement] - def topRight: Modifier[HtmlElement] - def bottomLeft: Modifier[HtmlElement] - def bottomRight: Modifier[HtmlElement] - -trait ListRenderable[Item]: - extension (x: Item) def asItem: ListItem - -trait Navigable[Item]: - extension (x: Item) def navigate: Modifier[HtmlElement] - -object BaseList: - enum Color: - case Green, Yellow, Red - - case class IconText(text: HtmlElement, icon: SvgElement) - case class Tag(text: String, color: Color) - case class Row( - id: String, - title: String, - tag: Tag, - leftProps: List[IconText], - rightProp: IconText - ) - - trait AsRow[Data]: - extension (d: Data) def asRow: Row - - class RowListItem(d: Row) extends ListItem: - - def key: String = d.id - - def title: Modifier[HtmlElement] = d.title - - def topRight: Modifier[HtmlElement] = - inline def colorClass(color: Color): (String, Boolean) = - val c = color.toString.toLowerCase - s"bg-$c-100 text-$c-800" -> (d.tag.color == color) - - inline def colors = Map(Color.values.map(colorClass(_)): _*) - - p( - cls := "px-2 inline-flex text-xs leading-5 font-semibold rounded-full", - cls := colors, - d.tag.text - ) - - def bottomLeft: Modifier[HtmlElement] = - div( - cls := "sm:flex", - d.leftProps.zipWithIndex.map { case (i, idx) => - p( - cls := Map("mt-2 sm:mt-0 sm:ml-6" -> (idx == 0)), - cls := "flex items-center text-sm text-gray-500", - i.icon, - i.text - ) - } - ) - - def bottomRight: Modifier[HtmlElement] = - div( - cls := "mt-2 flex items-center text-sm text-gray-500 sm:mt-0", - d.rightProp.icon, - d.rightProp.text - ) - - object Row: - given asRowRenderable[T: AsRow]: ListRenderable[T] with - extension (d: T) def asItem = new RowListItem(d.asRow) - - end Row - -class BaseList[RowData: ListRenderable]: - - protected def containerElement: HtmlTag[dom.html.Element] = div - protected def containerMods(rowData: RowData): Modifier[HtmlElement] = - emptyMod - protected def farRight: Modifier[HtmlElement] = emptyMod - - def render($data: Signal[List[RowData]]): HtmlElement = - ul( - role := "list", - cls := "divide-y divide-gray-200", - children <-- $data.split(_.asItem.key)((_, d, _) => row(d)) - ) - - private def row(d: RowData): HtmlElement = - val data = d.asItem - li( - containerElement( - containerMods(d), - cls := "block hover:bg-gray-50", - div( - cls := "px-4 py-4 sm:px-6 items-center flex", - div( - cls := "min-w-0 flex-1 pr-4", - div( - cls := "flex items-center justify-between", - p( - cls := "text-sm font-medium text-indigo-600 truncate", - data.title - ), - div( - cls := "ml-2 flex-shrink-0 flex", - data.topRight - ) - ), - div( - cls := "mt-2 sm:flex sm:justify-between", - data.bottomLeft, - data.bottomRight - ) - ), - farRight - ) - ) - ) - -trait NavigableList[RowData: Navigable, Page](using router: Router[Page]) - extends BaseList[RowData]: - - override protected def containerElement: HtmlTag[dom.html.Element] = a - override protected def containerMods( - rowData: RowData - ): Modifier[HtmlElement] = - rowData.navigate - override protected def farRight: Modifier[HtmlElement] = - div( - cls := "flex-shrink-0", - Icons.solid.`chevron-right`().amend(svg.cls := "text-gray-400") - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/IconText.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/IconText.scala deleted file mode 100644 index 9f5a7b1..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/IconText.scala +++ /dev/null @@ -1,19 +0,0 @@ -package works.iterative.ui.components.tailwind -package list - -import com.raquo.laminar.api.L.{*, given} -import org.scalajs.dom -import com.raquo.laminar.builders.HtmlTag - -object IconText: - case class ViewModel(text: HtmlElement, icon: SvgElement) - def render($m: Signal[ViewModel]): HtmlElement = render($m, div) - def render( - $m: Signal[ViewModel], - container: HtmlTag[dom.html.Element] - ): HtmlElement = - container( - cls := "flex items-center text-sm text-gray-500", - child <-- $m.map(_.icon), - child <-- $m.map(_.text) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/ListRow.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/ListRow.scala deleted file mode 100644 index e5236a2..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/ListRow.scala +++ /dev/null @@ -1,47 +0,0 @@ -package works.iterative.ui.components.tailwind -package list - -import com.raquo.laminar.api.L.{*, given} -import org.scalajs.dom - -object ListRow: - case class ViewModel( - title: String, - topRight: Modifier[HtmlElement], - bottomLeft: Modifier[HtmlElement], - bottomRight: Modifier[HtmlElement], - farRight: Modifier[HtmlElement], - containerElement: HtmlElement = div() - ) - - def apply($m: Signal[ViewModel]): HtmlElement = - li( - child <-- $m.map(m => - m.containerElement.amend( - cls := "block hover:bg-gray-50", - div( - cls := "px-4 py-4 sm:px-6 items-center flex", - div( - cls := "min-w-0 flex-1 pr-4", - div( - cls := "flex items-center justify-between", - p( - cls := "text-sm font-medium text-indigo-600 truncate", - m.title - ), - div( - cls := "ml-2 flex-shrink-0 flex", - m.topRight - ) - ), - div( - cls := "mt-2 sm:flex sm:justify-between", - m.bottomLeft, - m.bottomRight - ) - ), - m.farRight - ) - ) - ) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/PropList.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/PropList.scala deleted file mode 100644 index 946ffaf..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/PropList.scala +++ /dev/null @@ -1,16 +0,0 @@ -package works.iterative.ui.components.tailwind -package list - -import com.raquo.laminar.api.L.{*, given} - -object PropList: - type ViewModel = List[HtmlElement] - def render($m: Signal[ViewModel]): HtmlElement = - div( - cls := "sm:flex", - children <-- $m.map(_.zipWithIndex.map { case (i, idx) => - i.amend( - cls := Map("mt-2 sm:mt-0 sm:ml-6" -> (idx == 0)) - ) - }) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/RowNext.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/RowNext.scala deleted file mode 100644 index b53f3f2..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/RowNext.scala +++ /dev/null @@ -1,11 +0,0 @@ -package works.iterative.ui.components.tailwind -package list - -import com.raquo.laminar.api.L.{*, given} - -object RowNext: - def render: HtmlElement = - div( - cls := "flex-shrink-0", - Icons.solid.`chevron-right`().amend(svg.cls := "text-gray-400") - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/RowTag.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/RowTag.scala deleted file mode 100644 index 129abe4..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/RowTag.scala +++ /dev/null @@ -1,17 +0,0 @@ -package works.iterative.ui.components.tailwind -package list - -import com.raquo.laminar.api.L.{*, given} - -object RowTag: - case class ViewModel(text: String, color: Color) - def render($m: Signal[ViewModel]): HtmlElement = - inline def colorClass(color: Color): Seq[String] = - import ColorWeight._ - List(color.bg(w100), color.text(w800)) - - p( - cls := "px-2 inline-flex text-xs leading-5 font-semibold rounded-full", - cls <-- $m.map(t => colorClass(t.color)), - child.text <-- $m.map(_.text) - ) diff --git a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/StackedList.scala b/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/StackedList.scala deleted file mode 100644 index 6059362..0000000 --- a/iw/ui/src/main/scala/fiftyforms/ui/components/tailwind/list/StackedList.scala +++ /dev/null @@ -1,16 +0,0 @@ -package works.iterative.ui.components.tailwind -package list - -import com.raquo.laminar.api.L.{*, given} - -class StackedList[Item]: - type ViewModel = List[Item] - def apply( - $m: Signal[ViewModel], - keyF: Item => String - )(f: Signal[Item] => Signal[ListRow.ViewModel]): HtmlElement = - ul( - role := "list", - cls := "divide-y divide-gray-200", - children <-- $m.split(keyF)((_, _, $d) => ListRow(f($d))) - )