many changes
This commit is contained in:
parent
d455175044
commit
08b320b051
@ -6,6 +6,9 @@
|
||||
<title>Outwatch App</title>
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/blk-design-system@1.0.2/assets/css/nucleo-icons.css" integrity="sha256-03+9B37/His+rzjhgA6Y1+ByU9DGN2ZPWjjA5CJJF2w=" crossorigin="anonymous">
|
||||
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js" integrity="sha512-bLT0Qm9VnAYZDflyKcBaQ2gg0hSYNQrJ8RilYldYQ1FxQYoCLtUjuuRuZo+fjqhx/qtq/1itJ0C2ejDxltZVFg==" crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.datatables.net/1.10.22/js/jquery.dataTables.min.js"></script> -->
|
||||
<!-- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@sweetalert2/theme-dark@3/dark.css"> -->
|
||||
|
||||
</head>
|
||||
|
||||
|
26
build.sbt
26
build.sbt
@ -35,18 +35,28 @@ libraryDependencies ++= Seq(
|
||||
"com.beachape" %%% "enumeratum-circe" % "1.6.1",
|
||||
"com.github.valskalla" %%% "odin-core" % "0.7.0+95-ab4381ae+20201227-1831-SNAPSHOT",
|
||||
"io.github.cquiroz" %%% "scala-java-time" % "2.1.0",
|
||||
"io.github.cquiroz" %%% "scala-java-time-tzdb" % "2.1.0"
|
||||
"io.github.cquiroz" %%% "scala-java-time-tzdb" % "2.1.0",
|
||||
"com.lihaoyi" %%% "scalatags" % "0.9.2"
|
||||
// "com.clovellytech" %%% "outwatch-router" % "0.0.9+7-5be0b1a2+20201227-2019-SNAPSHOT"
|
||||
)
|
||||
|
||||
Compile / npmDependencies ++= Seq(
|
||||
"jquery" -> "3.3.1",
|
||||
"jquery" -> "3.5.1",
|
||||
"@types/jquery" -> "3.5.5",
|
||||
"blk-design-system" -> "1.0.2",
|
||||
"bootstrap" -> "4.5.3",
|
||||
"@types/chart.js" -> "2.9.11",
|
||||
"chart.js" -> "2.9.3",
|
||||
"snabbdom" -> "git://github.com/outwatch/snabbdom.git#semver:0.7.5",
|
||||
"fuse.js" -> "6.4.3"
|
||||
"fuse.js" -> "6.4.3",
|
||||
"datatables.net-bs4" -> "1.10.23",
|
||||
// "datatables.net-dt" -> "1.10.23",
|
||||
"@types/datatables.net" -> "1.10.19",
|
||||
"sweetalert2" -> "10.12.6",
|
||||
"@sweetalert2/themes" -> "4.0.1",
|
||||
"reconnecting-websocket" -> "4.4.0",
|
||||
"paralleljs" -> "1.1.0",
|
||||
"@types/paralleljs" -> "0.0.20"
|
||||
)
|
||||
|
||||
Compile / npmDevDependencies ++= Seq(
|
||||
@ -57,8 +67,14 @@ Compile / npmDevDependencies ++= Seq(
|
||||
"url-loader" -> "1.1.2"
|
||||
)
|
||||
|
||||
stIgnore ++= List("jquery", "blk-design-system", "bootstrap")
|
||||
stIgnore ++= List("snabbdom")
|
||||
stIgnore ++= List(
|
||||
"datatables.net-bs4",
|
||||
"datatables.net-dt",
|
||||
"blk-design-system",
|
||||
"bootstrap",
|
||||
"snabbdom",
|
||||
"@sweetalert2/themes"
|
||||
)
|
||||
stStdlib := List("es6")
|
||||
stUseScalaJsDom := false
|
||||
|
||||
|
@ -25,7 +25,7 @@ object MonixWS {
|
||||
class MonixWS[F[_], S](val url: String)(implicit F: Sync[F], S: Show[S]) {
|
||||
val ws = new org.scalajs.dom.WebSocket(url)
|
||||
|
||||
lazy val source: Observable[MessageEvent] =
|
||||
val source: Observable[MessageEvent] =
|
||||
Observable.create[MessageEvent](OverflowStrategy.Unbounded) { sub =>
|
||||
ws.onmessage = (e: MessageEvent) => sub.onNext(e)
|
||||
ws.onerror =
|
||||
@ -33,7 +33,7 @@ class MonixWS[F[_], S](val url: String)(implicit F: Sync[F], S: Show[S]) {
|
||||
Cancelable(() => ws.close())
|
||||
}
|
||||
|
||||
lazy val sink: F[Observer[S]] = {
|
||||
val sink: F[Observer[S]] = {
|
||||
F.delay {
|
||||
new Observer[S] {
|
||||
override def onNext(elem: S): Future[Ack] = {
|
||||
|
@ -20,7 +20,7 @@ object WebSocketF {
|
||||
class WebSocketF[F[_]](val url: String)(implicit F: Sync[F]) {
|
||||
val ws = new org.scalajs.dom.WebSocket(url)
|
||||
|
||||
lazy val source: Observable[MessageEvent] =
|
||||
val source: Observable[MessageEvent] =
|
||||
Observable.create[MessageEvent] { observer =>
|
||||
ws.onmessage = (e: MessageEvent) => observer.onNext(e)
|
||||
ws.onerror =
|
||||
@ -28,7 +28,7 @@ class WebSocketF[F[_]](val url: String)(implicit F: Sync[F]) {
|
||||
Cancelable(() => ws.close())
|
||||
}
|
||||
|
||||
lazy val sink: F[Observer[String]] = {
|
||||
val sink: F[Observer[String]] = {
|
||||
F.delay {
|
||||
new Observer[String] {
|
||||
override def onNext(elem: String): Unit = ws.send(elem)
|
||||
|
@ -8,28 +8,47 @@ import io.odin.consoleLogger
|
||||
import monix.bio.Task
|
||||
import monix.eval.Coeval
|
||||
import monix.reactive.Observable
|
||||
import monix.{eval => me}
|
||||
import org.scalajs.dom.raw.Element
|
||||
import outwatch._
|
||||
import outwatch.dsl._
|
||||
import outwatch.router._
|
||||
import outwatchapp.components.ChartjsDemo
|
||||
import outwatchapp.components.CounterDemo
|
||||
import outwatchapp.components.DatatablesDemo
|
||||
import outwatchapp.components.FusejsDemo
|
||||
import outwatchapp.components.RequestDemo
|
||||
import outwatchapp.components.todo.ChartjsDemo
|
||||
import outwatchapp.components.todo.FusejsDemo
|
||||
import outwatchapp.pages.HomePage
|
||||
import outwatchapp.ui.components.todo.TodoListStore
|
||||
import outwatchapp.util.IOUtils
|
||||
import outwatchapp.util.reactive.WebSocket
|
||||
import outwatchapp.util.reactive.WebWorker
|
||||
import outwatchapp.util.reactive.WebsocketData
|
||||
import outwatchapp.util.reactive.WorkerData
|
||||
import outwatchapp.util.reactive.ReconnectingWebSocket
|
||||
import cats.syntax.eq._
|
||||
import monix.execution.Ack
|
||||
import monix.reactive.Observer
|
||||
|
||||
class MainApp(el: Element)(implicit
|
||||
backend: AppTypes.Backend,
|
||||
store: RouterStore[Page]
|
||||
) {
|
||||
|
||||
import MainApp._
|
||||
|
||||
def run: Task[Unit] = for {
|
||||
resolver <- resolver
|
||||
_ <- OutWatch.renderInto[Task](el, Router(resolver))
|
||||
} yield ()
|
||||
|
||||
def producer(data: Long, ws: Observer[WebsocketData]): me.Task[Unit] =
|
||||
me.Task.deferFuture(ws.onNext(WebsocketData(data))).flatMap {
|
||||
case Ack.Continue => producer(data + 1, ws)
|
||||
// .delayExecution(3.seconds)
|
||||
case Ack.Stop => me.Task.unit
|
||||
}
|
||||
|
||||
def resolver: Task[PartialFunction[Page, VDomModifier]] =
|
||||
Task.deferAction(implicit s =>
|
||||
for {
|
||||
@ -38,35 +57,131 @@ class MainApp(el: Element)(implicit
|
||||
todoStore <- TodoListStore(consoleLogger[Task]())
|
||||
requestDemo <- RequestDemo(todoStore)
|
||||
demoWorker <- WebWorker[WorkerData]("/worker.js")
|
||||
dtDemo <- DatatablesDemo()
|
||||
_ <- IOUtils
|
||||
.toIO(
|
||||
Observable
|
||||
.interval(1.second)
|
||||
.take(2)
|
||||
.doOnNextF(i => Coeval(println(s"Producer emitted $i")))
|
||||
.doOnNextF(i =>
|
||||
me.Task.deferFuture(demoWorker.onNext(WorkerData(i))).void
|
||||
)
|
||||
.completedL >>
|
||||
me.Task.deferFuture(demoWorker.onNext(WorkerData(999)))
|
||||
// .delayExecution(6.seconds)
|
||||
)
|
||||
.startAndForget
|
||||
_ <-
|
||||
ReconnectingWebSocket[WebsocketData]("ws://localhost:6789").flatMap {
|
||||
case (source, sink) =>
|
||||
IOUtils
|
||||
.toIO(
|
||||
me.Task.parZip2(
|
||||
// Observable
|
||||
// .interval(3.seconds)
|
||||
// .doOnNext(_ => me.Task(println("Sending websocket data")))
|
||||
// .doOnNext(i =>
|
||||
// me.Task.deferFuture(
|
||||
// ws.onNext(WebsocketData(i))
|
||||
// ) >> me.Task.unit
|
||||
// )
|
||||
// .completedL,
|
||||
producer(0, sink.value),
|
||||
source.value
|
||||
.doOnNext(data => me.Task(println(s"Received : $data")))
|
||||
.completedL
|
||||
)
|
||||
)
|
||||
}
|
||||
// .onErrorRestartIf {
|
||||
// case e: Exception =>
|
||||
// e.getMessage() === "Websocket closed"
|
||||
// case _ => false
|
||||
// }
|
||||
// .onErrorRestartLoop(Backoff(3, 1.second)) { (err, state, retry) =>
|
||||
// val Backoff(maxRetries, delay) = state
|
||||
// if (maxRetries > 0)
|
||||
// retry(Backoff(maxRetries - 1, delay * 2)).delayExecution(delay)
|
||||
// else
|
||||
// // No retries left, rethrow the error
|
||||
// Task.raiseError(err)
|
||||
// }
|
||||
.startAndForget
|
||||
// .start
|
||||
// .startAndForget
|
||||
// _ <- SweetAlertDemo.dumbNotif
|
||||
// _ <- Task(println(s"Notif result: $notifRes"))
|
||||
} yield {
|
||||
case Page.Home => wire[HomePage].render
|
||||
case Page.SomePage =>
|
||||
div(
|
||||
div(cls := "title", "SomePage"),
|
||||
// RequestDemo(todoStore),
|
||||
Observable
|
||||
.interval(1.second)
|
||||
.doOnNextF(i => Coeval(println(s"Producer emitted $i")))
|
||||
.doOnNextF(i =>
|
||||
Coeval(demoWorker.onNext(WorkerData(i))) >> Coeval.unit
|
||||
)
|
||||
.map(_ => div()),
|
||||
// Observable
|
||||
// .interval(1.second)
|
||||
// .doOnNextF(i => Coeval(println(s"Producer emitted $i")))
|
||||
// .doOnNextF(i =>
|
||||
// Coeval(demoWorker.onNext(WorkerData(i))) >> Coeval.unit
|
||||
// )
|
||||
// .take(2)
|
||||
// .map(_ => div()),
|
||||
demoWorker.map(_.toString).map(v => p(cls := "text-white", v)),
|
||||
requestDemo,
|
||||
div(cls := "slider")
|
||||
)
|
||||
case Page.UserHome(id) =>
|
||||
div(
|
||||
cls := "text-white",
|
||||
// cls := "text-white",
|
||||
div(cls := "title", "UserHome"),
|
||||
s"User id: $id",
|
||||
div(FusejsDemo.y.map(_.item.toString).mkString(" "))
|
||||
div(FusejsDemo.y.map(_.item.toString).mkString(" ")),
|
||||
// ExampleDataTable.value(
|
||||
// onDomMount.asHtml.foreachSync(el =>
|
||||
// Coeval(DatatablesDemo.init(el)) >> Coeval.unit
|
||||
// )
|
||||
// )
|
||||
dtDemo
|
||||
)
|
||||
case Page.NotFound =>
|
||||
div(
|
||||
div(cls := "title", "NotFound"),
|
||||
p(cls := "text-white", "notfound")
|
||||
Task(
|
||||
div(
|
||||
cls := "page-header error-page header-filter",
|
||||
div(
|
||||
cls := "page-header-image"
|
||||
// style := js.Dictionary(
|
||||
// "background-image" -> "url('../assets/img/braden-collum.jpg')"
|
||||
// )
|
||||
),
|
||||
div(
|
||||
cls := "container",
|
||||
cls := "text-center",
|
||||
div(
|
||||
cls := "row",
|
||||
div(
|
||||
cls := "col-md-12",
|
||||
h1(
|
||||
fontSize := "12em",
|
||||
color := "#fff",
|
||||
letterSpacing := "14px",
|
||||
fontWeight := 700,
|
||||
cls := "title",
|
||||
"404"
|
||||
),
|
||||
h2(cls := "description", "Page not found :("),
|
||||
h4(
|
||||
cls := "description",
|
||||
"Ooooups! Looks like you got lost."
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
object MainApp {
|
||||
final case class Backoff(maxRetries: Long, delay: FiniteDuration)
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
package outwatchapp.components.todo
|
||||
package outwatchapp.components
|
||||
import scala.scalajs.js.|
|
||||
import scala.util.Random
|
||||
|
||||
@ -6,9 +6,10 @@ import colibri.ext.monix._
|
||||
import com.softwaremill.tagging._
|
||||
import monix.bio.Task
|
||||
import monix.eval.Coeval
|
||||
import monix.execution.Cancelable
|
||||
import outwatch.HtmlVNode
|
||||
import outwatch.ManagedSubscriptions.managedElement
|
||||
import outwatch.dsl._
|
||||
import outwatch.reactive.handlers.monix._
|
||||
import typings.chartJs.mod._
|
||||
import typings.chartJs.mod.{^ => Chart}
|
||||
import typings.moment.mod.Moment
|
||||
@ -20,29 +21,19 @@ import scalajs.js
|
||||
sealed trait ChartjsDemo
|
||||
object ChartjsDemo {
|
||||
val random = new Random()
|
||||
def apply(): Task[HtmlVNode @@ ChartjsDemo] = Task.deferAction(implicit s =>
|
||||
for {
|
||||
canvasH <- Handler.createF[Task, HTMLCanvasElement]
|
||||
} yield div(
|
||||
def apply(): Task[HtmlVNode @@ ChartjsDemo] = Coeval(
|
||||
div(
|
||||
canvas(
|
||||
onDomMount.asHtml.map(a =>
|
||||
a.asInstanceOf[HTMLCanvasElement]
|
||||
) --> canvasH
|
||||
),
|
||||
div(
|
||||
canvasH
|
||||
.doOnNextF(el =>
|
||||
Coeval(
|
||||
new Chart(
|
||||
el,
|
||||
chartConfig(ChartType.bar, randomData(100, random.nextInt()))
|
||||
)
|
||||
) >> Coeval.unit
|
||||
managedElement { el =>
|
||||
val chart = new Chart(
|
||||
el.asInstanceOf[HTMLCanvasElement],
|
||||
chartConfig(ChartType.bar, randomData(100, random.nextInt()))
|
||||
)
|
||||
.map(_ => div())
|
||||
Cancelable(() => chart.destroy())
|
||||
}
|
||||
)
|
||||
).taggedWith[ChartjsDemo]
|
||||
)
|
||||
).to[Task]
|
||||
|
||||
def randomData(
|
||||
max: Int,
|
141
src/main/scala/outwatchapp/components/DatatablesDemo.scala
Normal file
141
src/main/scala/outwatchapp/components/DatatablesDemo.scala
Normal file
@ -0,0 +1,141 @@
|
||||
package outwatchapp.components
|
||||
|
||||
import scala.annotation.unused
|
||||
import scala.scalajs.js.annotation.JSExportAll
|
||||
import scala.scalajs.js.annotation.JSImport
|
||||
|
||||
import colibri.ext.monix._
|
||||
import monix.bio.Task
|
||||
import monix.eval.Coeval
|
||||
import monix.execution.Cancelable
|
||||
import org.scalajs.dom.raw.HTMLElement
|
||||
import outwatch.ManagedSubscriptions.managedElement
|
||||
import typings.datatablesNet.DataTables.ColumnSettings
|
||||
import typings.datatablesNet.DataTables.FunctionColumnRender
|
||||
import typings.datatablesNet.DataTables.Settings
|
||||
import typings.datatablesNet.datatablesNetRequire
|
||||
import typings.jquery.JQuery_
|
||||
import typings.jquery.{mod => $}
|
||||
|
||||
import scalajs.js
|
||||
|
||||
@js.native
|
||||
@JSImport(
|
||||
"datatables.net-bs4/css/dataTables.bootstrap4.min.css",
|
||||
JSImport.Namespace
|
||||
)
|
||||
object DataTablesBs4Css extends js.Object
|
||||
@js.native
|
||||
@JSImport(
|
||||
"datatables.net-bs4/js/dataTables.bootstrap4.min.js",
|
||||
JSImport.Namespace
|
||||
)
|
||||
object DataTablesBs4Js extends js.Object
|
||||
|
||||
object DatatablesDemo {
|
||||
implicit def isJQueryDT[T](x: JQuery_[T]): typings.datatablesNet.JQuery =
|
||||
x.asInstanceOf[typings.datatablesNet.JQuery]
|
||||
|
||||
datatablesNetRequire
|
||||
DataTablesBs4Css
|
||||
DataTablesBs4Js
|
||||
|
||||
@unused
|
||||
private def _test(tableElement: HTMLElement) = $(tableElement)
|
||||
.DataTable(
|
||||
Settings().setColumnsVarargs(
|
||||
ColumnSettings().setData("name").setRender(nameRenderFn)
|
||||
)
|
||||
)
|
||||
.destroy()
|
||||
|
||||
val nameRenderFn: FunctionColumnRender = (_data, tpe, row, d) => {
|
||||
if (tpe.toString == "display") println("Matched")
|
||||
else println("Did not match")
|
||||
// this can be done with scalatags instead
|
||||
s"""
|
||||
<div>${_data.toString.toUpperCase}</div>
|
||||
"""
|
||||
// with scalatags
|
||||
// import scalatags.Text.all._
|
||||
// div(_data.toString.toUpperCase).render
|
||||
}
|
||||
|
||||
@JSExportAll
|
||||
case class TestData(
|
||||
name: String,
|
||||
position: String,
|
||||
location: String,
|
||||
id: String,
|
||||
date: String,
|
||||
salary: String
|
||||
)
|
||||
|
||||
val dataset = js
|
||||
.Array(
|
||||
TestData(
|
||||
"Tiger Nixon",
|
||||
"System Architect",
|
||||
"Edinburgh",
|
||||
"5421",
|
||||
"2011/04/25",
|
||||
"$320,800"
|
||||
),
|
||||
TestData(
|
||||
"Garrett Winters",
|
||||
"Accountant",
|
||||
"Tokyo",
|
||||
"8422",
|
||||
"2011/07/25",
|
||||
"$170,750"
|
||||
),
|
||||
TestData(
|
||||
"Ashton Cox",
|
||||
"Junior Technical Author",
|
||||
"San Francisco",
|
||||
"1562",
|
||||
"2009/01/12",
|
||||
"$86,000"
|
||||
),
|
||||
TestData(
|
||||
"Cedric Kelly",
|
||||
"Senior Javascript Developer",
|
||||
"Edinburgh",
|
||||
"6224",
|
||||
"2012/03/29",
|
||||
"$433,060"
|
||||
)
|
||||
)
|
||||
|
||||
// $(document).ready(_ => $(tableElement).DataTable())
|
||||
|
||||
def apply() = {
|
||||
import outwatch.dsl._
|
||||
Coeval(
|
||||
div(
|
||||
table(idAttr := "myTable")(managedElement { el =>
|
||||
val dt = $(el).DataTable(
|
||||
Settings()
|
||||
.setColumnsVarargs(
|
||||
ColumnSettings()
|
||||
.setTitle("Name")
|
||||
.setData("name")
|
||||
.setRender(nameRenderFn),
|
||||
ColumnSettings().setData("position"),
|
||||
ColumnSettings().setData("location"),
|
||||
ColumnSettings().setData("id"),
|
||||
ColumnSettings().setData("date"),
|
||||
ColumnSettings().setData("salary")
|
||||
)
|
||||
.setDom("Blfrtip")
|
||||
.setData(dataset)
|
||||
)
|
||||
Cancelable { () =>
|
||||
println("Destroying")
|
||||
dt.destroy()
|
||||
}
|
||||
})
|
||||
)
|
||||
).to[Task]
|
||||
}
|
||||
}
|
492
src/main/scala/outwatchapp/components/ExampleDataTable.scala
Normal file
492
src/main/scala/outwatchapp/components/ExampleDataTable.scala
Normal file
@ -0,0 +1,492 @@
|
||||
package outwatchapp.components
|
||||
|
||||
import outwatch.dsl._
|
||||
|
||||
object Table {
|
||||
val value = table(
|
||||
idAttr := "myTable",
|
||||
cls := "display",
|
||||
cls := "table",
|
||||
// style := "width: 100%",
|
||||
width := "100%",
|
||||
// style("width") := "100%",
|
||||
thead(
|
||||
tr(
|
||||
th("Name"),
|
||||
th("Position"),
|
||||
th("Office"),
|
||||
th("Age"),
|
||||
th("Start date"),
|
||||
th("Salary")
|
||||
)
|
||||
),
|
||||
tbody(
|
||||
tr(
|
||||
td("Tiger Nixon"),
|
||||
td("System Architect"),
|
||||
td("Edinburgh"),
|
||||
td("61"),
|
||||
td("2011/04/25"),
|
||||
td("$320,800")
|
||||
),
|
||||
tr(
|
||||
td("Garrett Winters"),
|
||||
td("Accountant"),
|
||||
td("Tokyo"),
|
||||
td("63"),
|
||||
td("2011/07/25"),
|
||||
td("$170,750")
|
||||
),
|
||||
tr(
|
||||
td("Ashton Cox"),
|
||||
td("Junior Technical Author"),
|
||||
td("San Francisco"),
|
||||
td("66"),
|
||||
td("2009/01/12"),
|
||||
td("$86,000")
|
||||
),
|
||||
tr(
|
||||
td("Cedric Kelly"),
|
||||
td("Senior Javascript Developer"),
|
||||
td("Edinburgh"),
|
||||
td("22"),
|
||||
td("2012/03/29"),
|
||||
td("$433,060")
|
||||
),
|
||||
tr(
|
||||
td("Airi Satou"),
|
||||
td("Accountant"),
|
||||
td("Tokyo"),
|
||||
td("33"),
|
||||
td("2008/11/28"),
|
||||
td("$162,700")
|
||||
),
|
||||
tr(
|
||||
td("Brielle Williamson"),
|
||||
td("Integration Specialist"),
|
||||
td("New York"),
|
||||
td("61"),
|
||||
td("2012/12/02"),
|
||||
td("$372,000")
|
||||
),
|
||||
tr(
|
||||
td("Herrod Chandler"),
|
||||
td("Sales Assistant"),
|
||||
td("San Francisco"),
|
||||
td("59"),
|
||||
td("2012/08/06"),
|
||||
td("$137,500")
|
||||
),
|
||||
tr(
|
||||
td("Rhona Davidson"),
|
||||
td("Integration Specialist"),
|
||||
td("Tokyo"),
|
||||
td("55"),
|
||||
td("2010/10/14"),
|
||||
td("$327,900")
|
||||
),
|
||||
tr(
|
||||
td("Colleen Hurst"),
|
||||
td("Javascript Developer"),
|
||||
td("San Francisco"),
|
||||
td("39"),
|
||||
td("2009/09/15"),
|
||||
td("$205,500")
|
||||
),
|
||||
tr(
|
||||
td("Sonya Frost"),
|
||||
td("Software Engineer"),
|
||||
td("Edinburgh"),
|
||||
td("23"),
|
||||
td("2008/12/13"),
|
||||
td("$103,600")
|
||||
),
|
||||
tr(
|
||||
td("Jena Gaines"),
|
||||
td("Office Manager"),
|
||||
td("London"),
|
||||
td("30"),
|
||||
td("2008/12/19"),
|
||||
td("$90,560")
|
||||
),
|
||||
tr(
|
||||
td("Quinn Flynn"),
|
||||
td("Support Lead"),
|
||||
td("Edinburgh"),
|
||||
td("22"),
|
||||
td("2013/03/03"),
|
||||
td("$342,000")
|
||||
),
|
||||
tr(
|
||||
td("Charde Marshall"),
|
||||
td("Regional Director"),
|
||||
td("San Francisco"),
|
||||
td("36"),
|
||||
td("2008/10/16"),
|
||||
td("$470,600")
|
||||
),
|
||||
tr(
|
||||
td("Haley Kennedy"),
|
||||
td("Senior Marketing Designer"),
|
||||
td("London"),
|
||||
td("43"),
|
||||
td("2012/12/18"),
|
||||
td("$313,500")
|
||||
),
|
||||
tr(
|
||||
td("Tatyana Fitzpatrick"),
|
||||
td("Regional Director"),
|
||||
td("London"),
|
||||
td("19"),
|
||||
td("2010/03/17"),
|
||||
td("$385,750")
|
||||
),
|
||||
tr(
|
||||
td("Michael Silva"),
|
||||
td("Marketing Designer"),
|
||||
td("London"),
|
||||
td("66"),
|
||||
td("2012/11/27"),
|
||||
td("$198,500")
|
||||
),
|
||||
tr(
|
||||
td("Paul Byrd"),
|
||||
td("Chief Financial Officer (CFO)"),
|
||||
td("New York"),
|
||||
td("64"),
|
||||
td("2010/06/09"),
|
||||
td("$725,000")
|
||||
),
|
||||
tr(
|
||||
td("Gloria Little"),
|
||||
td("Systems Administrator"),
|
||||
td("New York"),
|
||||
td("59"),
|
||||
td("2009/04/10"),
|
||||
td("$237,500")
|
||||
),
|
||||
tr(
|
||||
td("Bradley Greer"),
|
||||
td("Software Engineer"),
|
||||
td("London"),
|
||||
td("41"),
|
||||
td("2012/10/13"),
|
||||
td("$132,000")
|
||||
),
|
||||
tr(
|
||||
td("Dai Rios"),
|
||||
td("Personnel Lead"),
|
||||
td("Edinburgh"),
|
||||
td("35"),
|
||||
td("2012/09/26"),
|
||||
td("$217,500")
|
||||
),
|
||||
tr(
|
||||
td("Jenette Caldwell"),
|
||||
td("Development Lead"),
|
||||
td("New York"),
|
||||
td("30"),
|
||||
td("2011/09/03"),
|
||||
td("$345,000")
|
||||
),
|
||||
tr(
|
||||
td("Yuri Berry"),
|
||||
td("Chief Marketing Officer (CMO)"),
|
||||
td("New York"),
|
||||
td("40"),
|
||||
td("2009/06/25"),
|
||||
td("$675,000")
|
||||
),
|
||||
tr(
|
||||
td("Caesar Vance"),
|
||||
td("Pre-Sales Support"),
|
||||
td("New York"),
|
||||
td("21"),
|
||||
td("2011/12/12"),
|
||||
td("$106,450")
|
||||
),
|
||||
tr(
|
||||
td("Doris Wilder"),
|
||||
td("Sales Assistant"),
|
||||
td("Sydney"),
|
||||
td("23"),
|
||||
td("2010/09/20"),
|
||||
td("$85,600")
|
||||
),
|
||||
tr(
|
||||
td("Angelica Ramos"),
|
||||
td("Chief Executive Officer (CEO)"),
|
||||
td("London"),
|
||||
td("47"),
|
||||
td("2009/10/09"),
|
||||
td("$1,200,000")
|
||||
),
|
||||
tr(
|
||||
td("Gavin Joyce"),
|
||||
td("Developer"),
|
||||
td("Edinburgh"),
|
||||
td("42"),
|
||||
td("2010/12/22"),
|
||||
td("$92,575")
|
||||
),
|
||||
tr(
|
||||
td("Jennifer Chang"),
|
||||
td("Regional Director"),
|
||||
td("Singapore"),
|
||||
td("28"),
|
||||
td("2010/11/14"),
|
||||
td("$357,650")
|
||||
),
|
||||
tr(
|
||||
td("Brenden Wagner"),
|
||||
td("Software Engineer"),
|
||||
td("San Francisco"),
|
||||
td("28"),
|
||||
td("2011/06/07"),
|
||||
td("$206,850")
|
||||
),
|
||||
tr(
|
||||
td("Fiona Green"),
|
||||
td("Chief Operating Officer (COO)"),
|
||||
td("San Francisco"),
|
||||
td("48"),
|
||||
td("2010/03/11"),
|
||||
td("$850,000")
|
||||
),
|
||||
tr(
|
||||
td("Shou Itou"),
|
||||
td("Regional Marketing"),
|
||||
td("Tokyo"),
|
||||
td("20"),
|
||||
td("2011/08/14"),
|
||||
td("$163,000")
|
||||
),
|
||||
tr(
|
||||
td("Michelle House"),
|
||||
td("Integration Specialist"),
|
||||
td("Sydney"),
|
||||
td("37"),
|
||||
td("2011/06/02"),
|
||||
td("$95,400")
|
||||
),
|
||||
tr(
|
||||
td("Suki Burks"),
|
||||
td("Developer"),
|
||||
td("London"),
|
||||
td("53"),
|
||||
td("2009/10/22"),
|
||||
td("$114,500")
|
||||
),
|
||||
tr(
|
||||
td("Prescott Bartlett"),
|
||||
td("Technical Author"),
|
||||
td("London"),
|
||||
td("27"),
|
||||
td("2011/05/07"),
|
||||
td("$145,000")
|
||||
),
|
||||
tr(
|
||||
td("Gavin Cortez"),
|
||||
td("Team Leader"),
|
||||
td("San Francisco"),
|
||||
td("22"),
|
||||
td("2008/10/26"),
|
||||
td("$235,500")
|
||||
),
|
||||
tr(
|
||||
td("Martena Mccray"),
|
||||
td("Post-Sales support"),
|
||||
td("Edinburgh"),
|
||||
td("46"),
|
||||
td("2011/03/09"),
|
||||
td("$324,050")
|
||||
),
|
||||
tr(
|
||||
td("Unity Butler"),
|
||||
td("Marketing Designer"),
|
||||
td("San Francisco"),
|
||||
td("47"),
|
||||
td("2009/12/09"),
|
||||
td("$85,675")
|
||||
),
|
||||
tr(
|
||||
td("Howard Hatfield"),
|
||||
td("Office Manager"),
|
||||
td("San Francisco"),
|
||||
td("51"),
|
||||
td("2008/12/16"),
|
||||
td("$164,500")
|
||||
),
|
||||
tr(
|
||||
td("Hope Fuentes"),
|
||||
td("Secretary"),
|
||||
td("San Francisco"),
|
||||
td("41"),
|
||||
td("2010/02/12"),
|
||||
td("$109,850")
|
||||
),
|
||||
tr(
|
||||
td("Vivian Harrell"),
|
||||
td("Financial Controller"),
|
||||
td("San Francisco"),
|
||||
td("62"),
|
||||
td("2009/02/14"),
|
||||
td("$452,500")
|
||||
),
|
||||
tr(
|
||||
td("Timothy Mooney"),
|
||||
td("Office Manager"),
|
||||
td("London"),
|
||||
td("37"),
|
||||
td("2008/12/11"),
|
||||
td("$136,200")
|
||||
),
|
||||
tr(
|
||||
td("Jackson Bradshaw"),
|
||||
td("Director"),
|
||||
td("New York"),
|
||||
td("65"),
|
||||
td("2008/09/26"),
|
||||
td("$645,750")
|
||||
),
|
||||
tr(
|
||||
td("Olivia Liang"),
|
||||
td("Support Engineer"),
|
||||
td("Singapore"),
|
||||
td("64"),
|
||||
td("2011/02/03"),
|
||||
td("$234,500")
|
||||
),
|
||||
tr(
|
||||
td("Bruno Nash"),
|
||||
td("Software Engineer"),
|
||||
td("London"),
|
||||
td("38"),
|
||||
td("2011/05/03"),
|
||||
td("$163,500")
|
||||
),
|
||||
tr(
|
||||
td("Sakura Yamamoto"),
|
||||
td("Support Engineer"),
|
||||
td("Tokyo"),
|
||||
td("37"),
|
||||
td("2009/08/19"),
|
||||
td("$139,575")
|
||||
),
|
||||
tr(
|
||||
td("Thor Walton"),
|
||||
td("Developer"),
|
||||
td("New York"),
|
||||
td("61"),
|
||||
td("2013/08/11"),
|
||||
td("$98,540")
|
||||
),
|
||||
tr(
|
||||
td("Finn Camacho"),
|
||||
td("Support Engineer"),
|
||||
td("San Francisco"),
|
||||
td("47"),
|
||||
td("2009/07/07"),
|
||||
td("$87,500")
|
||||
),
|
||||
tr(
|
||||
td("Serge Baldwin"),
|
||||
td("Data Coordinator"),
|
||||
td("Singapore"),
|
||||
td("64"),
|
||||
td("2012/04/09"),
|
||||
td("$138,575")
|
||||
),
|
||||
tr(
|
||||
td("Zenaida Frank"),
|
||||
td("Software Engineer"),
|
||||
td("New York"),
|
||||
td("63"),
|
||||
td("2010/01/04"),
|
||||
td("$125,250")
|
||||
),
|
||||
tr(
|
||||
td("Zorita Serrano"),
|
||||
td("Software Engineer"),
|
||||
td("San Francisco"),
|
||||
td("56"),
|
||||
td("2012/06/01"),
|
||||
td("$115,000")
|
||||
),
|
||||
tr(
|
||||
td("Jennifer Acosta"),
|
||||
td("Junior Javascript Developer"),
|
||||
td("Edinburgh"),
|
||||
td("43"),
|
||||
td("2013/02/01"),
|
||||
td("$75,650")
|
||||
),
|
||||
tr(
|
||||
td("Cara Stevens"),
|
||||
td("Sales Assistant"),
|
||||
td("New York"),
|
||||
td("46"),
|
||||
td("2011/12/06"),
|
||||
td("$145,600")
|
||||
),
|
||||
tr(
|
||||
td("Hermione Butler"),
|
||||
td("Regional Director"),
|
||||
td("London"),
|
||||
td("47"),
|
||||
td("2011/03/21"),
|
||||
td("$356,250")
|
||||
),
|
||||
tr(
|
||||
td("Lael Greer"),
|
||||
td("Systems Administrator"),
|
||||
td("London"),
|
||||
td("21"),
|
||||
td("2009/02/27"),
|
||||
td("$103,500")
|
||||
),
|
||||
tr(
|
||||
td("Jonas Alexander"),
|
||||
td("Developer"),
|
||||
td("San Francisco"),
|
||||
td("30"),
|
||||
td("2010/07/14"),
|
||||
td("$86,500")
|
||||
),
|
||||
tr(
|
||||
td("Shad Decker"),
|
||||
td("Regional Director"),
|
||||
td("Edinburgh"),
|
||||
td("51"),
|
||||
td("2008/11/13"),
|
||||
td("$183,000")
|
||||
),
|
||||
tr(
|
||||
td("Michael Bruce"),
|
||||
td("Javascript Developer"),
|
||||
td("Singapore"),
|
||||
td("29"),
|
||||
td("2011/06/27"),
|
||||
td("$183,000")
|
||||
),
|
||||
tr(
|
||||
td("Donna Snider"),
|
||||
td("Customer Support"),
|
||||
td("New York"),
|
||||
td("27"),
|
||||
td("2011/01/25"),
|
||||
td("$112,000")
|
||||
)
|
||||
),
|
||||
tfoot(
|
||||
tr(
|
||||
th("Name"),
|
||||
th("Position"),
|
||||
th("Office"),
|
||||
th("Age"),
|
||||
th("Start date"),
|
||||
th("Salary")
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package outwatchapp.components.todo
|
||||
package outwatchapp.components
|
||||
|
||||
import outwatchapp.ui.components.todo.Todo
|
||||
import typings.fuseJs.mod.Fuse.IFuseOptions
|
@ -5,14 +5,14 @@ import com.softwaremill.tagging._
|
||||
import monix.bio._
|
||||
import monix.eval.Coeval
|
||||
import monix.{eval => me}
|
||||
import outwatchapp.ui.components.todo.Todo
|
||||
import outwatchapp.ui.components.todo.TodoListStore
|
||||
import outwatchapp.util.reactive.store.Store
|
||||
import outwatch._
|
||||
import outwatch.dsl._
|
||||
import outwatch.reactive.handlers.monix._
|
||||
import outwatchapp.AppTypes
|
||||
import outwatchapp.implicits._
|
||||
import outwatchapp.ui.components.todo.Todo
|
||||
import outwatchapp.ui.components.todo.TodoListStore
|
||||
import outwatchapp.util.reactive.store.Store
|
||||
import sttp.client._
|
||||
|
||||
sealed trait RequestDemo
|
||||
|
104
src/main/scala/outwatchapp/components/SweetAlertDemo.scala
Normal file
104
src/main/scala/outwatchapp/components/SweetAlertDemo.scala
Normal file
@ -0,0 +1,104 @@
|
||||
package outwatchapp.components
|
||||
import scala.scalajs.js.annotation.JSImport
|
||||
|
||||
import monix.bio.Task
|
||||
import outwatchapp.implicits._
|
||||
import typings.sweetalert2.mod.SweetAlertIcon
|
||||
import typings.sweetalert2.mod.SweetAlertOptions
|
||||
import typings.sweetalert2.mod.SweetAlertPosition
|
||||
|
||||
import scalajs.js
|
||||
|
||||
object SweetAlertDemo {
|
||||
@JSImport(
|
||||
"@sweetalert2/themes/borderless/borderless.min.css",
|
||||
JSImport.Namespace
|
||||
)
|
||||
@js.native
|
||||
object SweetAlertBorderlessTheme extends js.Object
|
||||
@JSImport("@sweetalert2/themes/dark/dark.min.css", JSImport.Namespace)
|
||||
@js.native
|
||||
object SweetAlertDarkTheme extends js.Object
|
||||
@JSImport("sweetalert2/dist/sweetalert2.js", JSImport.Namespace)
|
||||
@js.native
|
||||
object SweetAlertJs extends js.Object
|
||||
|
||||
SweetAlertDarkTheme
|
||||
val Swal = SweetAlertJs.asInstanceOf[typings.sweetalert2.mod.default.type]
|
||||
|
||||
val loginPrompt = {
|
||||
import scalatags.JsDom.all._
|
||||
|
||||
val nameInput = input(
|
||||
tpe := "text",
|
||||
`id` := "loginName",
|
||||
cls := "swal-input",
|
||||
placeholder := "Username"
|
||||
).render
|
||||
val passInput = input(
|
||||
tpe := "password",
|
||||
`id` := "loginPass",
|
||||
cls := "swal-input",
|
||||
placeholder := "Password"
|
||||
).render
|
||||
val html = div(nameInput, passInput).render.asST
|
||||
|
||||
def nameAndPass(a: js.Any) = (nameInput.value, passInput.value)
|
||||
|
||||
Swal.fireL(
|
||||
SweetAlertOptions()
|
||||
.setTitle("Login Form")
|
||||
.setHtml(html)
|
||||
.setFocusConfirm(false)
|
||||
.setConfirmButtonText("Sign In")
|
||||
.setPreConfirm(nameAndPass _)
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
val successPrompt =
|
||||
Swal.fireL(
|
||||
SweetAlertOptions()
|
||||
.setPosition(SweetAlertPosition.`top-end`)
|
||||
.setIcon(SweetAlertIcon.success)
|
||||
.setTitle("Login Success")
|
||||
.setShowConfirmButton(false)
|
||||
.setTimer(1500)
|
||||
) >> Task.unit
|
||||
|
||||
val failurePrompt =
|
||||
Swal.fireL(
|
||||
SweetAlertOptions()
|
||||
.setPosition(SweetAlertPosition.`top-end`)
|
||||
.setIcon(SweetAlertIcon.error)
|
||||
.setTitle("Login Failure")
|
||||
.setShowConfirmButton(false)
|
||||
.setTimer(1500)
|
||||
) >> Task.unit
|
||||
|
||||
val dumbNotif =
|
||||
Swal
|
||||
.fireL(
|
||||
SweetAlertOptions()
|
||||
.setTitle("Hello there")
|
||||
.setText("Welcome to outwatch app")
|
||||
// .setInput(SweetAlertInput.text)
|
||||
// .setShowCancelButton(true)
|
||||
.setIcon(SweetAlertIcon.info)
|
||||
// .setToast(true)
|
||||
// .setPreConfirm { res: String =>
|
||||
// val username = Swal.getPopup().asOption
|
||||
// username match {
|
||||
// case Some(value) =>
|
||||
// println(s"Got value: $value")
|
||||
// 1
|
||||
// case None =>
|
||||
// println("Got none")
|
||||
// 2
|
||||
// }
|
||||
// res
|
||||
// }
|
||||
) >> Task.unit
|
||||
// .map(_.value.toOption)
|
||||
|
||||
}
|
@ -1,11 +1,19 @@
|
||||
package outwatchapp
|
||||
|
||||
import scala.util.Try
|
||||
|
||||
import colibri.LiftSink
|
||||
import colibri.LiftSource
|
||||
import colibri.ext.monix._
|
||||
import monix.bio.Task
|
||||
import monix.execution.Scheduler
|
||||
import monix.reactive.Observable
|
||||
import monix.reactive.Observer
|
||||
import org.scalajs.dom.raw.HTMLElement
|
||||
import typings.sweetalert2.mod.SweetAlertOptions
|
||||
import typings.sweetalert2.mod.{default => Swal}
|
||||
|
||||
import scalajs.js.|
|
||||
|
||||
package object implicits {
|
||||
// implicit def sinkForMyMonixProSub[A, M] =
|
||||
@ -20,10 +28,26 @@ package object implicits {
|
||||
// )
|
||||
// }
|
||||
|
||||
implicit class Ops[A](val sink: Observer[A]) {
|
||||
implicit class Ops[A](val sink: Observer[A]) extends AnyVal {
|
||||
@inline def liftSink[G[_]: LiftSink]: G[A] = LiftSink[G].lift(sink)
|
||||
}
|
||||
implicit class Ops2[A](val source: Observable[A])(implicit s: Scheduler) {
|
||||
@inline def liftSource[G[_]: LiftSource]: G[A] = LiftSource[G].lift(source)
|
||||
implicit class Ops2[A](val source: Observable[A]) extends AnyVal {
|
||||
@inline def liftSource[G[_]: LiftSource](implicit s: Scheduler): G[A] =
|
||||
LiftSource[G].lift(source)
|
||||
}
|
||||
|
||||
implicit class HTMLElementOps[T <: HTMLElement](private val el: T)
|
||||
extends AnyVal {
|
||||
def asST = el.asInstanceOf[typings.std.HTMLElement]
|
||||
}
|
||||
implicit class SweetAlertOps(private val sw: Swal.type) extends AnyVal {
|
||||
def fireL[T](options: SweetAlertOptions[T, _]) =
|
||||
Task.deferFuture(sw.fire(options).toFuture)
|
||||
}
|
||||
implicit class NullUnionOps[A, B](private val union: A | B) extends AnyVal {
|
||||
@scala.inline
|
||||
def asOption(implicit ev: B =:= scala.Null) = Try(
|
||||
union.asInstanceOf[A]
|
||||
).toOption
|
||||
}
|
||||
}
|
||||
|
@ -1,36 +1,59 @@
|
||||
package outwatchapp.pages
|
||||
import cats.syntax.eq._
|
||||
import com.softwaremill.tagging._
|
||||
import monix.bio.Task
|
||||
import outwatch._
|
||||
import outwatch.dsl._
|
||||
import outwatchapp.components.ChartjsDemo
|
||||
import outwatchapp.components.CounterDemo
|
||||
import outwatchapp.components.todo.ChartjsDemo
|
||||
import outwatchapp.components.SweetAlertDemo
|
||||
|
||||
class HomePage(
|
||||
counterDemo: VNode @@ CounterDemo,
|
||||
chartDemo: VNode @@ ChartjsDemo
|
||||
) {
|
||||
def render = div(
|
||||
div(cls := "title", "Home"),
|
||||
div(
|
||||
cls := "card",
|
||||
val loginDemo = for {
|
||||
res <- SweetAlertDemo.loginPrompt.map(_.value.toOption)
|
||||
_ <- Task(println(s"Got $res"))
|
||||
_ <-
|
||||
if (res === Some("foo" -> "bar")) SweetAlertDemo.successPrompt
|
||||
else SweetAlertDemo.failurePrompt
|
||||
} yield ()
|
||||
|
||||
def render = Task.deferAction(implicit s =>
|
||||
Task(
|
||||
div(
|
||||
cls := "card-body",
|
||||
counterDemo,
|
||||
chartDemo,
|
||||
p(
|
||||
cls := "text-white",
|
||||
div(cls := "title", "Home"),
|
||||
div(
|
||||
cls := "card",
|
||||
div(
|
||||
"hm",
|
||||
htmlTag("blockQuote")(
|
||||
cls := "blockquote",
|
||||
p(
|
||||
cls := "mb-0",
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a ante."
|
||||
),
|
||||
footer(
|
||||
cls := "blockquote-footer",
|
||||
"Someone famous in ",
|
||||
cite(title := "Source Title", "Source Title")
|
||||
cls := "card-body",
|
||||
counterDemo,
|
||||
chartDemo,
|
||||
div(
|
||||
cls := "text-center",
|
||||
button(
|
||||
cls := "btn btn-primary",
|
||||
onClick.preventDefault.doAsync(loginDemo),
|
||||
"Login"
|
||||
)
|
||||
),
|
||||
p(
|
||||
cls := "text-white",
|
||||
div(
|
||||
"hm",
|
||||
htmlTag("blockQuote")(
|
||||
cls := "blockquote",
|
||||
p(
|
||||
cls := "mb-0",
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a ante."
|
||||
),
|
||||
footer(
|
||||
cls := "blockquote-footer",
|
||||
"Someone famous in ",
|
||||
cite(title := "Source Title", "Source Title")
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
34
src/main/scala/outwatchapp/util/ParallelDemo.scala
Normal file
34
src/main/scala/outwatchapp/util/ParallelDemo.scala
Normal file
@ -0,0 +1,34 @@
|
||||
package outwatchapp.util
|
||||
import scala.concurrent.Future
|
||||
|
||||
import typings.paralleljs.ParallelOptions
|
||||
import typings.paralleljs.mod.{^ => Parallel}
|
||||
|
||||
import scalajs.js
|
||||
import scalajs.js.|
|
||||
|
||||
object ParallelDemo {
|
||||
val p =
|
||||
new Parallel(js.Array(1, 2, 3, 4, 5), ParallelOptions().setMaxWorkers(2))
|
||||
// val pr = new js.Promise()
|
||||
|
||||
// p.map((_: Int) * 2)
|
||||
|
||||
def toFuture[A](p: Parallel[A]): Future[A] = {
|
||||
val p2 = scala.concurrent.Promise[A]()
|
||||
p.`then`(
|
||||
{ (v: A) =>
|
||||
p2.success(v)
|
||||
(): Unit | js.Thenable[Unit]
|
||||
},
|
||||
{ (e: typings.std.Error) =>
|
||||
p2.failure(e match {
|
||||
case th: Throwable => th
|
||||
case _ => js.JavaScriptException(e)
|
||||
})
|
||||
(): Unit | js.Thenable[Unit]
|
||||
}
|
||||
)
|
||||
p2.future
|
||||
}
|
||||
}
|
14
src/main/scala/outwatchapp/util/SubtleCryptoTest.scala
Normal file
14
src/main/scala/outwatchapp/util/SubtleCryptoTest.scala
Normal file
@ -0,0 +1,14 @@
|
||||
package outwatchapp.util
|
||||
|
||||
// import typings.std.{SubtleCrypto => TS}
|
||||
// import org.scalajs.dom.crypto.SubtleCrypto
|
||||
import typings.std.global.Uint8Array
|
||||
import typings.std.global.{SubtleCrypto => TS}
|
||||
|
||||
object SubtleCryptoTest {
|
||||
val sc = new TS()
|
||||
// Algorithm
|
||||
// sc.deriveKey
|
||||
// val n = WindowBase64
|
||||
new Uint8Array(10)
|
||||
}
|
44
src/main/scala/outwatchapp/util/WorkerTest.scala
Normal file
44
src/main/scala/outwatchapp/util/WorkerTest.scala
Normal file
@ -0,0 +1,44 @@
|
||||
// package outwatchapp.util
|
||||
|
||||
// import org.scalajs.dom
|
||||
|
||||
// import scala.scalajs.js
|
||||
// import scala.scalajs.js.annotation.JSExport
|
||||
// import org.scalajs.dom.webworkers.DedicatedWorkerGlobalScope
|
||||
// import scala.scalajs.js.annotation.JSGlobalScope
|
||||
// import scala.scalajs.js.annotation.JSExportTopLevel
|
||||
|
||||
// // @js.native
|
||||
// // object WorkerGlobal extends js.GlobalScope {
|
||||
// // def addEventListener(`type`: String, f: js.Function): Unit = js.native
|
||||
// // def postMessage(data: js.Any): Unit = js.native
|
||||
// // }
|
||||
|
||||
// @js.native
|
||||
// @JSGlobalScope
|
||||
// object WorkerGlobal extends DedicatedWorkerGlobalScope
|
||||
|
||||
// @JSExportTopLevel("WorkerMain")
|
||||
// object WorkerMain {
|
||||
// @JSExport
|
||||
// def main(): Unit = {
|
||||
// // WorkerGlobal.addEventListener("message", onMessage _)
|
||||
// WorkerGlobal.onmessage = onMessage _
|
||||
// WorkerGlobal.postMessage(s"Started")
|
||||
// }
|
||||
|
||||
// val timeMessage = """Time.*""".r
|
||||
// var count = 0
|
||||
|
||||
// def onMessage(msg: dom.MessageEvent) = {
|
||||
// val s = msg.data.asInstanceOf[String]
|
||||
// s match {
|
||||
// case timeMessage() =>
|
||||
// count += 1
|
||||
// if (count % 600 == 0)
|
||||
// WorkerGlobal.postMessage("60fps")
|
||||
// case _ =>
|
||||
// WorkerGlobal.postMessage(s"Received: $s")
|
||||
// }
|
||||
// }
|
||||
// }
|
@ -0,0 +1,77 @@
|
||||
package outwatchapp.util.reactive
|
||||
|
||||
import io.circe.Decoder
|
||||
import io.circe.Encoder
|
||||
import io.circe.Printer
|
||||
import io.circe.parser._
|
||||
import io.circe.syntax._
|
||||
import monix.execution.Ack
|
||||
import monix.execution.Cancelable
|
||||
import monix.execution.cancelables.SingleAssignCancelable
|
||||
import monix.reactive.Observable
|
||||
import monix.reactive.Observer
|
||||
import monix.reactive.OverflowStrategy
|
||||
import org.scalajs.dom.raw.MessageEvent
|
||||
import org.scalajs.dom.webworkers.DedicatedWorkerGlobalScope
|
||||
import scala.concurrent.Future
|
||||
import outwatchapp.util.reactive.Exceptions.WrongTypeException
|
||||
// @js.native
|
||||
// @JSGlobalScope
|
||||
// object WorkerGlobal extends DedicatedWorkerGlobalScope
|
||||
|
||||
// class DedicatedWorkerImpl[T: Encoder: Decoder](wg: DedicatedWorkerGlobalScope) {
|
||||
// def run = Task.deferAction(implicit s =>
|
||||
// for {
|
||||
// _ <- Task.unit
|
||||
// sub <- Task(ConcurrentSubject.publish[T])
|
||||
// } yield ()
|
||||
// )
|
||||
// }
|
||||
|
||||
object DedicatedWorker {
|
||||
def source[T: Decoder](wg: DedicatedWorkerGlobalScope) =
|
||||
Observable.create[T](OverflowStrategy.DropOld(50)) { sub =>
|
||||
val c = SingleAssignCancelable()
|
||||
def onmessageFn(event: MessageEvent): Unit = {
|
||||
event.data match {
|
||||
case s: String =>
|
||||
decode[T](s)
|
||||
.map { res =>
|
||||
if (sub.onNext(res) == Ack.Stop) c.cancel()
|
||||
res
|
||||
}
|
||||
.left
|
||||
.foreach(err =>
|
||||
sub.onError(
|
||||
new Exception(s"Failed to decode $s. Error was $err")
|
||||
)
|
||||
)
|
||||
case other =>
|
||||
sub.onError(WrongTypeException(s"Received wrong type: $other"))
|
||||
}
|
||||
}
|
||||
wg.onmessage = onmessageFn _
|
||||
|
||||
c := Cancelable(() => wg.onmessage = _ => ())
|
||||
}
|
||||
|
||||
def sink[T: Encoder](wg: DedicatedWorkerGlobalScope) =
|
||||
new Observer[T] {
|
||||
val printer = Printer.noSpaces
|
||||
|
||||
override def onNext(elem: T): Future[Ack] = {
|
||||
// wg.onoffline
|
||||
wg.postMessage(printer.print(elem.asJson))
|
||||
Ack.Continue
|
||||
}
|
||||
|
||||
override def onError(ex: Throwable): Unit = ex.printStackTrace()
|
||||
|
||||
override def onComplete(): Unit = println("Worker observer completed")
|
||||
|
||||
}
|
||||
|
||||
def apply[T: Encoder: Decoder](
|
||||
wg: DedicatedWorkerGlobalScope
|
||||
): DedicatedWorker[T] = MonixProSubject.from(sink(wg), source(wg))
|
||||
}
|
16
src/main/scala/outwatchapp/util/reactive/Exceptions.scala
Normal file
16
src/main/scala/outwatchapp/util/reactive/Exceptions.scala
Normal file
@ -0,0 +1,16 @@
|
||||
package outwatchapp.util.reactive
|
||||
|
||||
object Exceptions {
|
||||
//cause: Option[Throwable]) extends Exception(message, cause.orNull)
|
||||
sealed abstract class ReactiveException(val message: String)
|
||||
extends Throwable(message)
|
||||
final case class DecodeException(override val message: String)
|
||||
extends ReactiveException(message)
|
||||
final case class WrongTypeException(override val message: String)
|
||||
extends ReactiveException(message)
|
||||
final case class UseAfterClose(override val message: String)
|
||||
extends ReactiveException(message)
|
||||
final case class TerminatedException(override val message: String)
|
||||
extends ReactiveException(message)
|
||||
|
||||
}
|
@ -0,0 +1,165 @@
|
||||
package outwatchapp.util.reactive
|
||||
|
||||
import scala.concurrent.Future
|
||||
|
||||
import io.circe.Decoder
|
||||
import io.circe.Encoder
|
||||
import io.circe.Printer
|
||||
import io.circe.parser._
|
||||
import io.circe.syntax._
|
||||
import monix.bio.Task
|
||||
import monix.execution.Ack
|
||||
import monix.execution.Cancelable
|
||||
import monix.execution.CancelablePromise
|
||||
import monix.execution.cancelables.SingleAssignCancelable
|
||||
import monix.reactive.Observable
|
||||
import monix.reactive.Observer
|
||||
import monix.reactive.OverflowStrategy
|
||||
import typings.reconnectingWebsocket.eventsMod.CloseEvent
|
||||
import typings.reconnectingWebsocket.eventsMod.ErrorEvent
|
||||
import typings.reconnectingWebsocket.eventsMod.Event
|
||||
import typings.reconnectingWebsocket.mod.{default => RW}
|
||||
|
||||
import scalajs.js
|
||||
import typings.std.MessageEvent
|
||||
import typings.reconnectingWebsocket.mod.Options
|
||||
import outwatchapp.util.reactive.Exceptions.DecodeException
|
||||
import outwatchapp.util.reactive.Exceptions.WrongTypeException
|
||||
import monix.reactive.observers.Subscriber
|
||||
import monix.reactive.observers.BufferedSubscriber
|
||||
import outwatchapp.util.reactive.Exceptions.UseAfterClose
|
||||
import monix.bio.IO
|
||||
|
||||
class ReconnectingWebSocketImpl[T: Encoder: Decoder](
|
||||
ws: RW,
|
||||
overflowStrategy: OverflowStrategy.Synchronous[T]
|
||||
) {
|
||||
val printer = Printer.noSpaces
|
||||
|
||||
/** @throws ReactiveException
|
||||
*/
|
||||
val source: Task[Observable[T]] =
|
||||
Task.deferAction(implicit s =>
|
||||
for {
|
||||
obs <- Task(
|
||||
Observable
|
||||
.create[T](overflowStrategy) { sub =>
|
||||
val c = SingleAssignCancelable()
|
||||
val onmessage: js.Function1[MessageEvent[_], Unit] =
|
||||
_.data match {
|
||||
case s: String =>
|
||||
decode[T](s)
|
||||
.map { res =>
|
||||
if (sub.onNext(res) == Ack.Stop) c.cancel()
|
||||
res
|
||||
}
|
||||
.left
|
||||
.foreach(err =>
|
||||
sub.onError(
|
||||
DecodeException(
|
||||
s"Failed to decode $s. Error was $err"
|
||||
)
|
||||
)
|
||||
)
|
||||
case other =>
|
||||
sub.onError(
|
||||
WrongTypeException(s"Received wrong type: $other")
|
||||
)
|
||||
}
|
||||
val emptyMsgFn: js.Function1[MessageEvent[_], Unit] =
|
||||
_ => println("message fn not initialized")
|
||||
ws.onmessage = onmessage
|
||||
|
||||
val onclose: js.Function1[CloseEvent, Unit] = a => {
|
||||
println(
|
||||
s"Websocket closing - ${a.code} ${a.wasClean} ${a.reason}"
|
||||
)
|
||||
sub.onComplete()
|
||||
}
|
||||
ws.onclose = onclose
|
||||
|
||||
val onerror: js.Function1[ErrorEvent, Unit] = (e: Event) =>
|
||||
sub.onError(new Exception(s"Error in WebSocket: $e"))
|
||||
|
||||
ws.onerror = onerror
|
||||
|
||||
c := Cancelable { () => ws.onmessage = emptyMsgFn }
|
||||
}
|
||||
.publish
|
||||
.refCount
|
||||
)
|
||||
_ <- Task(obs.subscribe(Observer.empty))
|
||||
} yield obs
|
||||
)
|
||||
|
||||
val sink: Task[Observer[T]] =
|
||||
Task.deferAction(implicit s =>
|
||||
Task(
|
||||
BufferedSubscriber(
|
||||
new Subscriber[T] {
|
||||
import monix.execution.FutureUtils
|
||||
import scala.concurrent.duration._
|
||||
override def scheduler = s
|
||||
override def onNext(elem: T): Future[Ack] = {
|
||||
val msg = printer.print(elem.asJson)
|
||||
if (ws.readyState == 2 || ws.readyState == 3)
|
||||
Ack.Stop
|
||||
else {
|
||||
FutureUtils
|
||||
.delayedResult(2.second)(ws.send(msg))
|
||||
.flatMap(_ => Ack.Continue)
|
||||
}
|
||||
}
|
||||
override def onError(ex: Throwable): Unit = s.reportFailure(ex)
|
||||
override def onComplete(): Unit = println("CLOSING WEBSOCKET 2 ")
|
||||
},
|
||||
OverflowStrategy.Default
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
}
|
||||
object ReconnectingWebSocket {
|
||||
sealed trait Error
|
||||
final case object Error extends Error
|
||||
|
||||
/** @throws ReactiveException
|
||||
*/
|
||||
final case class Source[T](value: Observable[T])
|
||||
|
||||
/** A buffered Observer
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
final case class Sink[T](value: Observer[T]) {
|
||||
def send(t: T) =
|
||||
IO.deferFuture(value.onNext(t)).flatMap {
|
||||
case Ack.Continue => Task.unit
|
||||
case Ack.Stop => IO.raiseError(UseAfterClose("Websocket was closed"))
|
||||
}
|
||||
}
|
||||
|
||||
private val defaultOptions = Options()
|
||||
.setMinReconnectionDelay(5000)
|
||||
.setMaxRetries(2)
|
||||
.setMaxEnqueuedMessages(50)
|
||||
|
||||
def onopen[A](op: => A): js.Function1[Event, Unit] = _ => op
|
||||
|
||||
def apply[T <: Product: Encoder: Decoder](
|
||||
path: String,
|
||||
options: Options = defaultOptions,
|
||||
overflowStrategy: OverflowStrategy.Synchronous[T] =
|
||||
OverflowStrategy.DropOld(50)
|
||||
) = for {
|
||||
websocket <- Task(new RW(path, js.undefined, options))
|
||||
p = CancelablePromise[Unit]()
|
||||
_ <- Task(websocket.onopen = onopen(p.success(())))
|
||||
_ <- Task.deferFuture(p.future)
|
||||
_ <- Task(websocket.onopen = onopen(()))
|
||||
impl = new ReconnectingWebSocketImpl[T](websocket, overflowStrategy)
|
||||
source <- impl.source
|
||||
sink <- impl.sink
|
||||
// _ <- Task.deferAction(implicit s => Task(source.subscribe(sink)))
|
||||
} yield Source(source) -> Sink(sink)
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package outwatchapp.util.reactive
|
||||
|
||||
import org.scalajs.dom.{raw => sjsdr}
|
||||
import scala.concurrent.Future
|
||||
|
||||
import io.circe.Decoder
|
||||
import io.circe.Encoder
|
||||
import io.circe.Printer
|
||||
@ -10,13 +11,20 @@ import io.circe.syntax._
|
||||
import monix.bio.Task
|
||||
import monix.execution.Ack
|
||||
import monix.execution.Cancelable
|
||||
import monix.execution.CancelablePromise
|
||||
import monix.execution.cancelables.SingleAssignCancelable
|
||||
import monix.reactive.Observable
|
||||
import monix.reactive.Observer
|
||||
import monix.reactive.OverflowStrategy
|
||||
import outwatchapp.util.reactive.MonixProSubject
|
||||
import org.scalajs.dom.raw.Event
|
||||
import org.scalajs.dom.raw.MessageEvent
|
||||
import scala.concurrent.Future
|
||||
import org.scalajs.dom.{raw => sjsdr}
|
||||
import outwatchapp.util.reactive.MonixProSubject
|
||||
import monix.reactive.observers.Subscriber
|
||||
import monix.execution.Scheduler
|
||||
import monix.reactive.observers.BufferedSubscriber
|
||||
import monix.execution.cancelables.CompositeCancelable
|
||||
import scala.concurrent.duration._
|
||||
|
||||
class WebSocketImpl[T: Encoder: Decoder](
|
||||
ws: sjsdr.WebSocket,
|
||||
@ -24,49 +32,84 @@ class WebSocketImpl[T: Encoder: Decoder](
|
||||
) {
|
||||
val printer = Printer.noSpaces
|
||||
|
||||
lazy val source: Task[Observable[T]] =
|
||||
val source: Task[Observable[T]] =
|
||||
Task.deferAction(implicit s =>
|
||||
for {
|
||||
c <- Task(CompositeCancelable())
|
||||
obs <- Task(
|
||||
Observable
|
||||
.create[T](overflowStrategy) { sub =>
|
||||
// val c = SingleAssignCancelable()
|
||||
ws.onmessage = (e: MessageEvent) =>
|
||||
e.data match {
|
||||
case s: String =>
|
||||
decode[T](s).map(sub.onNext).left.foreach(println)
|
||||
case other => println(other)
|
||||
decode[T](s)
|
||||
.map { res =>
|
||||
if (sub.onNext(res) == Ack.Stop) c.cancel()
|
||||
res
|
||||
}
|
||||
.left
|
||||
.foreach(err =>
|
||||
sub.onError(
|
||||
new Exception(s"Failed to decode $s. Error was $err")
|
||||
)
|
||||
)
|
||||
case other =>
|
||||
sub.onError(new Exception(s"Received wrong type: $other"))
|
||||
}
|
||||
ws.onerror = (e: Event) =>
|
||||
ws.onclose = a => {
|
||||
println(
|
||||
s"Websocket closing - ${a.code} ${a.wasClean} ${a.reason}"
|
||||
)
|
||||
sub.onComplete()
|
||||
}
|
||||
ws.onerror = (e: Event) => {
|
||||
println("Error in websocket")
|
||||
sub.onError(new Exception(s"Error in WebSocket: $e"))
|
||||
Cancelable(() => ws.close())
|
||||
}
|
||||
c += Cancelable { () =>
|
||||
println("Closing websocket")
|
||||
ws.close()
|
||||
}
|
||||
}
|
||||
.publish
|
||||
.refCount
|
||||
)
|
||||
_ <- Task(obs.subscribe(Observer.empty))
|
||||
// empty subscription because otherwise ouwtatch kills the observable when
|
||||
// the dom containing this observable is unmounted
|
||||
_ <- Task(c += obs.subscribe(Observer.empty))
|
||||
} yield obs
|
||||
)
|
||||
|
||||
lazy val sink: Task[Observer[T]] =
|
||||
Task(
|
||||
new Observer[T] {
|
||||
override def onNext(elem: T): Future[Ack] = {
|
||||
val msg = printer.print(elem.asJson)
|
||||
ws.send(msg)
|
||||
Future.successful(Ack.Continue)
|
||||
}
|
||||
override def onError(ex: Throwable): Unit = println(ex)
|
||||
override def onComplete(): Unit = ()
|
||||
}
|
||||
val sink: Task[Subscriber[T]] =
|
||||
Task.deferAction(implicit s =>
|
||||
Task(
|
||||
BufferedSubscriber(
|
||||
new Subscriber[T] {
|
||||
override implicit def scheduler: Scheduler = s
|
||||
|
||||
override def onNext(elem: T): Future[Ack] = {
|
||||
val msg = printer.print(elem.asJson)
|
||||
if (ws.readyState == 2 || ws.readyState == 3)
|
||||
// Future.failed(new Exception("Websocket closed"))
|
||||
Future.successful(Ack.Stop)
|
||||
else {
|
||||
ws.send(msg)
|
||||
Future.successful(Ack.Continue)
|
||||
}
|
||||
}
|
||||
override def onError(ex: Throwable): Unit = println(ex)
|
||||
override def onComplete(): Unit = ()
|
||||
},
|
||||
OverflowStrategy.BackPressure(50)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
}
|
||||
object WebSocket {
|
||||
sealed trait Error
|
||||
final case object Error extends Error
|
||||
// val w = new sjsdr.Worker("/worker.js").asInstanceOf[Worker]
|
||||
|
||||
// type MonixWebWorker[T] = MonixProSubject[T, T]
|
||||
|
||||
def apply[T <: Product: Encoder: Decoder](
|
||||
path: String,
|
||||
@ -74,6 +117,11 @@ object WebSocket {
|
||||
OverflowStrategy.DropOld(50)
|
||||
): Task[WebSocket[T]] = for {
|
||||
websocket <- Task(new sjsdr.WebSocket(path))
|
||||
p <- Task(CancelablePromise[Unit]())
|
||||
// wait for websocket to open
|
||||
_ <- Task(websocket.onopen = _ => p.success(()))
|
||||
_ <- Task.deferFuture(p.future).timeout(10.seconds)
|
||||
_ <- Task(websocket.onopen = _ => ())
|
||||
impl = new WebSocketImpl[T](websocket, overflowStrategy)
|
||||
source <- impl.source
|
||||
sink <- impl.sink
|
||||
|
@ -11,99 +11,98 @@ import io.circe.syntax._
|
||||
import monix.bio.Task
|
||||
import monix.execution.Ack
|
||||
import monix.execution.Cancelable
|
||||
import monix.execution.cancelables.SingleAssignCancelable
|
||||
import monix.reactive.Observable
|
||||
import monix.reactive.Observer
|
||||
import monix.reactive.OverflowStrategy
|
||||
import outwatchapp.util.reactive.MonixProSubject
|
||||
import org.scalajs.dom.raw.Event
|
||||
import org.scalajs.dom.raw.MessageEvent
|
||||
import org.scalajs.dom.{raw => sjsdr}
|
||||
import outwatchapp.util.reactive.MonixProSubject
|
||||
import monix.execution.cancelables.CompositeCancelable
|
||||
import monix.eval.Coeval
|
||||
import monix.reactive.observers.Subscriber
|
||||
import monix.execution.Scheduler
|
||||
import monix.reactive.observers.BufferedSubscriber
|
||||
import outwatchapp.util.reactive.Exceptions.DecodeException
|
||||
import outwatchapp.util.reactive.Exceptions.WrongTypeException
|
||||
import outwatchapp.util.reactive.Exceptions.TerminatedException
|
||||
|
||||
class WebWorkerImpl[T <: Product: Encoder: Decoder](
|
||||
worker: sjsdr.Worker,
|
||||
overflowStrategy: OverflowStrategy.Synchronous[T]
|
||||
) {
|
||||
|
||||
// private def parseFn(data: T) = {
|
||||
// data match {
|
||||
// case _: AnyRef => parseRef(data)
|
||||
// case s: String =>
|
||||
// case other =>
|
||||
// println(other)
|
||||
// Left(WebWorker.Error)
|
||||
// }
|
||||
// }
|
||||
|
||||
// private def parseRef(data: T): Either[WebWorker.Error, T] = {
|
||||
// data match {
|
||||
// case s: String => decode[T](s).leftMap(_ => WebWorker.Error)
|
||||
// case a: Int =>
|
||||
// println(a)
|
||||
// Left(WebWorker.Error)
|
||||
// case other =>
|
||||
// println(other)
|
||||
// Left(WebWorker.Error)
|
||||
// }
|
||||
// }
|
||||
// private def parsePrimitive(data: T): Either[WebWorker.Error, T] = {
|
||||
// data match {
|
||||
// case s: String => decode[T](s).leftMap(_ => WebWorker.Error)
|
||||
// case a: Int =>
|
||||
// println(a)
|
||||
// Left(WebWorker.Error)
|
||||
// case other =>
|
||||
// println(other)
|
||||
// Left(WebWorker.Error)
|
||||
// }
|
||||
// }
|
||||
// println(s"Got data $a")
|
||||
// .map(sub.onNext).left.foreach(println)
|
||||
|
||||
val printer = Printer.noSpaces
|
||||
|
||||
lazy val source: Task[Observable[T]] =
|
||||
val source: Task[Observable[T]] =
|
||||
Task.deferAction(implicit s =>
|
||||
for {
|
||||
c <- Task(CompositeCancelable())
|
||||
obs <- Task(
|
||||
Observable
|
||||
.create[T](overflowStrategy) { sub =>
|
||||
// val c = SingleAssignCancelable()
|
||||
worker.onmessage = (e: MessageEvent) =>
|
||||
e.data match {
|
||||
case s: String =>
|
||||
decode[T](s).map(sub.onNext).left.foreach(println)
|
||||
case other => println(other)
|
||||
decode[T](s)
|
||||
.map { res =>
|
||||
if (sub.onNext(res) == Ack.Stop) c.cancel()
|
||||
res
|
||||
}
|
||||
.left
|
||||
.foreach(err =>
|
||||
sub.onError(
|
||||
DecodeException(
|
||||
s"Failed to decode $s. Error was $err"
|
||||
)
|
||||
)
|
||||
)
|
||||
case other =>
|
||||
sub.onError(
|
||||
WrongTypeException(s"Received wrong type: $other")
|
||||
)
|
||||
}
|
||||
worker.onerror = (e: Event) =>
|
||||
sub.onError(new Exception(s"Error in WebSocket: $e"))
|
||||
Cancelable(() => worker.terminate())
|
||||
sub.onError(
|
||||
TerminatedException(s"Worker terminated with error: $e")
|
||||
)
|
||||
c += Cancelable(() => worker.terminate())
|
||||
}
|
||||
.doOnSubscriptionCancelF(Coeval(println("Worker cancelled")))
|
||||
.publish
|
||||
.refCount
|
||||
)
|
||||
_ <- Task(obs.subscribe(Observer.empty))
|
||||
_ <- Task(c += obs.subscribe(Observer.empty))
|
||||
} yield obs
|
||||
)
|
||||
|
||||
lazy val sink: Task[Observer[T]] =
|
||||
Task(
|
||||
new Observer[T] {
|
||||
override def onNext(elem: T): Future[Ack] = {
|
||||
val msg = printer.print(elem.asJson)
|
||||
worker.postMessage(msg)
|
||||
Future.successful(Ack.Continue)
|
||||
}
|
||||
override def onError(ex: Throwable): Unit = println(ex)
|
||||
override def onComplete(): Unit = ()
|
||||
}
|
||||
val sink: Task[Observer[T]] =
|
||||
Task.deferAction(implicit s =>
|
||||
Task(
|
||||
BufferedSubscriber(
|
||||
new Subscriber[T] {
|
||||
|
||||
override implicit def scheduler: Scheduler = s
|
||||
|
||||
override def onNext(elem: T): Future[Ack] = {
|
||||
val msg = printer.print(elem.asJson)
|
||||
worker.postMessage(msg)
|
||||
Ack.Continue
|
||||
}
|
||||
override def onError(ex: Throwable): Unit = s.reportFailure(ex)
|
||||
override def onComplete(): Unit = ()
|
||||
},
|
||||
OverflowStrategy.Default
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
}
|
||||
object WebWorker {
|
||||
sealed trait Error
|
||||
final case object Error extends Error
|
||||
// val w = new sjsdr.Worker("/worker.js").asInstanceOf[Worker]
|
||||
|
||||
// type MonixWebWorker[T] = MonixProSubject[T, T]
|
||||
|
||||
def apply[T <: Product: Encoder: Decoder](
|
||||
path: String,
|
||||
|
@ -8,4 +8,5 @@ package object reactive {
|
||||
type MonixSubject[A] = MonixProSubject[A, A]
|
||||
type WebWorker[A] = MonixSubject[A]
|
||||
type WebSocket[A] = MonixSubject[A]
|
||||
type DedicatedWorker[A] = MonixSubject[A]
|
||||
}
|
||||
|
23690
src/main/scala/outwatchapp/util/reactive/store/test.css
Normal file
23690
src/main/scala/outwatchapp/util/reactive/store/test.css
Normal file
File diff suppressed because it is too large
Load Diff
67
yarn.lock
67
yarn.lock
@ -2,6 +2,11 @@
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@sweetalert2/themes@4.0.1":
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@sweetalert2/themes/-/themes-4.0.1.tgz#ba07a40743fd439663341480c20360116c2e1f27"
|
||||
integrity sha512-dE+SXd0ym2RUkDQT/rVyBIwe6QIKl1cfBF3133XdLeXVKUR+XEtkr1uVBK821uX5DRQfN9doJmVCd2jFdG/qXw==
|
||||
|
||||
"@types/chart.js@2.9.11":
|
||||
version "2.9.11"
|
||||
resolved "https://registry.yarnpkg.com/@types/chart.js/-/chart.js-2.9.11.tgz#2b73fe59e78dfe31c2f8d6c6d0c169e98e65c16b"
|
||||
@ -9,6 +14,13 @@
|
||||
dependencies:
|
||||
moment "^2.10.2"
|
||||
|
||||
"@types/datatables.net@1.10.19":
|
||||
version "1.10.19"
|
||||
resolved "https://registry.yarnpkg.com/@types/datatables.net/-/datatables.net-1.10.19.tgz#17c5f94433f761086131c6c8dc055a0e1099d1f9"
|
||||
integrity sha512-WuzgytEmsIpVYZbkce+EvK1UqBI7/cwcC/WgYeAtXdq2zi+yWzJwMT5Yb6irAiOi52DBjeAEeRt3bYzFYvHWCQ==
|
||||
dependencies:
|
||||
"@types/jquery" "*"
|
||||
|
||||
"@types/glob@^7.1.1":
|
||||
version "7.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183"
|
||||
@ -17,6 +29,13 @@
|
||||
"@types/minimatch" "*"
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/jquery@*", "@types/jquery@3.5.5":
|
||||
version "3.5.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-3.5.5.tgz#2c63f47c9c8d96693d272f5453602afd8338c903"
|
||||
integrity sha512-6RXU9Xzpc6vxNrS6FPPapN1SxSHgQ336WC6Jj/N8q30OiaBZ00l1GBgeP7usjVZPivSkGUfL1z/WW6TX989M+w==
|
||||
dependencies:
|
||||
"@types/sizzle" "*"
|
||||
|
||||
"@types/json-schema@^7.0.6":
|
||||
version "7.0.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0"
|
||||
@ -32,6 +51,16 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.16.tgz#3cc351f8d48101deadfed4c9e4f116048d437b4b"
|
||||
integrity sha512-naXYePhweTi+BMv11TgioE2/FXU4fSl29HAH1ffxVciNsH3rYXjNP2yM8wqmSm7jS20gM8TIklKiTen+1iVncw==
|
||||
|
||||
"@types/paralleljs@0.0.20":
|
||||
version "0.0.20"
|
||||
resolved "https://registry.yarnpkg.com/@types/paralleljs/-/paralleljs-0.0.20.tgz#5013416ccb05d2fec5e2bcf5331d37bd203abe44"
|
||||
integrity sha512-+1y+7aNWEvsXfdubI9y5aUFYU7RiUgVqAo+Px9siGLPUq4RZodvrP2EdoCV+oPQdcgKuQnW+CuikWcUSZuAkhA==
|
||||
|
||||
"@types/sizzle@*":
|
||||
version "2.3.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.2.tgz#a811b8c18e2babab7d542b3365887ae2e4d9de47"
|
||||
integrity sha512-7EJYyKTL7tFR8+gDbB6Wwz/arpGa0Mywk1TJbNzKzHtzbwVmY4HR9WqS5VV7dsBUKQmPNr192jHr/VpBluj/hg==
|
||||
|
||||
"@webassemblyjs/ast@1.9.0":
|
||||
version "1.9.0"
|
||||
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964"
|
||||
@ -1010,6 +1039,21 @@ cyclist@^1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
|
||||
integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=
|
||||
|
||||
datatables.net-bs4@1.10.23:
|
||||
version "1.10.23"
|
||||
resolved "https://registry.yarnpkg.com/datatables.net-bs4/-/datatables.net-bs4-1.10.23.tgz#bdd5d0dcb1bd68a7afe649a4424ba20647523b52"
|
||||
integrity sha512-ChUB8t5t5uzPnJYTPXx2DOvnlm2shz8OadXrKoFavOadB308OuwHVxSldYq9+KGedCeiVxEjNqcaV4nFSXkRsw==
|
||||
dependencies:
|
||||
datatables.net "1.10.23"
|
||||
jquery ">=1.7"
|
||||
|
||||
datatables.net@1.10.23:
|
||||
version "1.10.23"
|
||||
resolved "https://registry.yarnpkg.com/datatables.net/-/datatables.net-1.10.23.tgz#59f7d7b12845183b1b379530d1385077e113ec01"
|
||||
integrity sha512-we3tlNkzpxvgkKKlTxTMXPCt35untVXNg8zUYWpQyC1U5vJc+lT0+Zdc1ztK8d3lh5CfdnuFde2p8n3XwaGl3Q==
|
||||
dependencies:
|
||||
jquery ">=1.7"
|
||||
|
||||
debug@2.6.9, debug@^2.2.0, debug@^2.3.3:
|
||||
version "2.6.9"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
||||
@ -2190,10 +2234,10 @@ isobject@^3.0.0, isobject@^3.0.1:
|
||||
resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
|
||||
integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8=
|
||||
|
||||
jquery@3.3.1:
|
||||
version "3.3.1"
|
||||
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.3.1.tgz#958ce29e81c9790f31be7792df5d4d95fc57fbca"
|
||||
integrity sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg==
|
||||
jquery@3.5.1, jquery@>=1.7:
|
||||
version "3.5.1"
|
||||
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.5.1.tgz#d7b4d08e1bfdb86ad2f1a3d039ea17304717abb5"
|
||||
integrity sha512-XwIBPqcMn57FxfT+Go5pzySnm4KWkT1Tv7gjrpT1srtf8Weynl6R273VJ5GjkRb51IzMp5nbaPjJXMWeju2MKg==
|
||||
|
||||
json-parse-better-errors@^1.0.2:
|
||||
version "1.0.2"
|
||||
@ -2836,6 +2880,11 @@ parallel-transform@^1.1.0:
|
||||
inherits "^2.0.3"
|
||||
readable-stream "^2.1.5"
|
||||
|
||||
paralleljs@1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/paralleljs/-/paralleljs-1.1.0.tgz#d0cd5540fe4dd8e80fa94d3cb6a530510dbe6e70"
|
||||
integrity sha512-5sYLAEzM+qMDYzQeVWyNdqINU4h+/Fhr0CrZs3+HCYPIhOWuA2BCzn74js2qzX+LKFwRQR6sSJBZhgT+WWhbfw==
|
||||
|
||||
parse-asn1@^5.0.0, parse-asn1@^5.1.5:
|
||||
version "5.1.6"
|
||||
resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.6.tgz#385080a3ec13cb62a62d39409cb3e88844cdaed4"
|
||||
@ -3176,6 +3225,11 @@ readdirp@~3.5.0:
|
||||
dependencies:
|
||||
picomatch "^2.2.1"
|
||||
|
||||
reconnecting-websocket@4.4.0:
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/reconnecting-websocket/-/reconnecting-websocket-4.4.0.tgz#3b0e5b96ef119e78a03135865b8bb0af1b948783"
|
||||
integrity sha512-D2E33ceRPga0NvTDhJmphEgJ7FUYF0v4lr1ki0csq06OdlxKfugGzN0dSkxM/NfqCxYELK4KcaTOUOjTV6Dcng==
|
||||
|
||||
regex-not@^1.0.0, regex-not@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c"
|
||||
@ -3751,6 +3805,11 @@ supports-color@^6.1.0:
|
||||
dependencies:
|
||||
has-flag "^3.0.0"
|
||||
|
||||
sweetalert2@10.12.6:
|
||||
version "10.12.6"
|
||||
resolved "https://registry.yarnpkg.com/sweetalert2/-/sweetalert2-10.12.6.tgz#249b95fecb28986a1aff82710a460ecb4717e2a9"
|
||||
integrity sha512-kkPRpNqP0wF9tAVu1Ygp4pQx4pXY/pgseyW3dOXXRu0S6s7HKfHEmpBUIZrBylRfeJFPOsSEk6sALSwKzNZ9RQ==
|
||||
|
||||
tapable@^1.0.0, tapable@^1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2"
|
||||
|
Loading…
Reference in New Issue
Block a user