Compare commits

..

8 Commits

40 changed files with 25568 additions and 206 deletions

View File

@ -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>

18
assets/worker.js Normal file
View File

@ -0,0 +1,18 @@
var i = 0;
console.log("Starting worker")
onmessage = (ev) => {
console.log(`Worker received data ${ev.data}`)
const data = JSON.parse(ev.data)
postMessage(JSON.stringify({ data: data.data * 2 }))
}
// function timedCount() {
// i = i + 1;
// postMessage(JSON.stringify({ data: i }));
// setTimeout("timedCount()", 2000);
// }
// timedCount();

View File

@ -33,20 +33,30 @@ libraryDependencies ++= Seq(
// "io.circe" %%% "circe-config" % "0.8.0",
"org.akka-js" %%% "shocon" % "1.0.0",
"com.beachape" %%% "enumeratum-circe" % "1.6.1",
"com.github.valskalla" %%% "odin-core" % "0.7.0+95-ab4381ae+20201227-1831-SNAPSHOT"
"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",
"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",
"popper.js" -> "1.16.1",
// // "@popperjs/core" -> "2.6.0",
"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
@ -110,6 +126,9 @@ webpackDevServerPort := 8080
webpackConfigFile in fastOptJS := Some(
baseDirectory.value / "webpack.config.dev.js"
)
webpackConfigFile in fullOptJS := Some(
baseDirectory.value / "webpack.config.prod.js"
)
// webpackConfigFile in fullOptJS := Some(
// baseDirectory.value / "webpack.config.js"
// )

View File

@ -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] = {

View File

@ -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)

View File

@ -1,50 +1,187 @@
package outwatchapp
import scala.concurrent.duration._
import colibri.ext.monix._
import com.softwaremill.macwire._
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.pages.HomePage
import nova.monadic_sfx.ui.components.todo.TodoListStore
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 {
counterDemo <- CounterDemo()
chartDemo <- ChartjsDemo()
todoStore <- TodoListStore()
todoStore <- TodoListStore(consoleLogger[Task]())
requestDemo <- RequestDemo(todoStore)
resolver: PartialFunction[Page, VDomModifier] = {
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
// )
// .take(2)
// .map(_ => div()),
demoWorker.map(_.toString).map(v => p(cls := "text-white", v)),
requestDemo,
div(cls := "slider")
)
case Page.UserHome(id) =>
div(div(cls := "title", "UserHome"), s"User id: $id")
case Page.NotFound =>
div(
div(cls := "title", "NotFound"),
p(cls := "profile-description", "notfound")
// cls := "text-white",
div(cls := "title", "UserHome"),
s"User id: $id",
div(FusejsDemo.y.map(_.item.toString).mkString(" ")),
// ExampleDataTable.value(
// onDomMount.asHtml.foreachSync(el =>
// Coeval(DatatablesDemo.init(el)) >> Coeval.unit
// )
// )
dtDemo
)
case Page.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."
)
)
)
)
)
)
}
} yield resolver
)
}
object MainApp {
final case class Backoff(maxRetries: Long, delay: FiniteDuration)
}

View File

@ -1,18 +1,20 @@
package outwatchapp
import scala.scalajs.js.annotation.JSImport
import cats.effect.ExitCode
import monix.bio._
import org.scalajs.dom.raw.Element
import sttp.client.impl.monix.FetchMonixBackend
import org.scalajs.dom.document
import scala.scalajs.js.annotation.JSImport
import scalajs.js
import org.scalajs.dom.raw.Element
import outwatch.router.AppRouter
import sttp.client.impl.monix.FetchMonixBackend
import scalajs.js
@JSImport("bootstrap/dist/css/bootstrap.min.css", JSImport.Namespace)
@js.native
object BootstrapBundleCss extends js.Object
@JSImport("bootstrap", JSImport.Namespace)
@JSImport("bootstrap/dist/js/bootstrap.bundle.min.js", JSImport.Namespace)
@js.native
object BootstrapBundleJs extends js.Object

View File

@ -1,13 +1,10 @@
package outwatchapp
import colibri.ext.monix._
import monix.bio.Task
import monix.eval.Coeval
import outwatch._
import outwatch.dsl._
import outwatch.router.AppRouter
import outwatch.router._
import outwatch.router.dsl._
import outwatchapp.components.todo.FusejsDemo
import Page._
@ -30,7 +27,7 @@ object Router {
cls := "navbar-translate",
a(
cls := "navbar-brand",
href := "https://demos.creative-tim.com/blk-design-system/index.html",
href := "#",
rel := "tooltip",
title := "",
attr("data-placement") := "bottom",
@ -104,11 +101,6 @@ object Router {
),
div(
cls := "container",
Coeval(
println("Result = " + FusejsDemo.y.toString())
) >> Coeval(
FusejsDemo.y.foreach(o => println(o.item))
) >> Coeval(div()),
router.render(resolver),
router.watch()
)

View File

@ -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(
canvas(
onDomMount.asHtml.map(a =>
a.asInstanceOf[HTMLCanvasElement]
) --> canvasH
),
def apply(): Task[HtmlVNode @@ ChartjsDemo] = Coeval(
div(
canvasH
.doOnNextF(el =>
Coeval(
new Chart(
el,
canvas(
managedElement { el =>
val chart = new Chart(
el.asInstanceOf[HTMLCanvasElement],
chartConfig(ChartType.bar, randomData(100, random.nextInt()))
)
) >> Coeval.unit
)
.map(_ => div())
Cancelable(() => chart.destroy())
}
)
).taggedWith[ChartjsDemo]
)
).to[Task]
def randomData(
max: Int,

View File

@ -15,7 +15,7 @@ object CounterDemo {
def apply(): Task[HtmlVNode @@ CounterDemo] =
Task.deferAction(implicit s =>
Task(
div(p(cls := "profile-description", "count: ", counter2))
div(p(cls := "text-white", "count: ", counter2))
.taggedWith[CounterDemo]
)
)

View 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]
}
}

View 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")
)
)
)
}

View File

@ -1,6 +1,6 @@
package outwatchapp.components.todo
package outwatchapp.components
import nova.monadic_sfx.ui.components.todo.Todo
import outwatchapp.ui.components.todo.Todo
import typings.fuseJs.mod.Fuse.IFuseOptions
import typings.fuseJs.mod._
import typings.fuseJs.mod.{default => FuseC}

View File

@ -5,15 +5,15 @@ import com.softwaremill.tagging._
import monix.bio._
import monix.eval.Coeval
import monix.{eval => me}
import nova.monadic_sfx.ui.components.todo.Todo
import nova.monadic_sfx.ui.components.todo.TodoListStore
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._
import nova.monadic_sfx.util.reactive.store.Store
sealed trait RequestDemo
object RequestDemo {
@ -57,15 +57,14 @@ object RequestDemo {
cls := "form-control",
placeholder := "0",
onInput.value --> requestSub
)
),
div(
cls := "form-group",
label(
color := "hsla(0,0%,100%,0.8)",
"Enter content for todo"
),
small(cls := "form-text text-muted", "default is 0")
),
div(
cls := "form-group",
input(
cls := "form-control",
onInput.value --> todoContent
@ -78,8 +77,7 @@ object RequestDemo {
Coeval(println("Clicked"))
),
onClick(
todoContent
.map(TodoListStore.Add)
todoContent.map(TodoListStore.Add)
) --> todoStore.sink
)
)
@ -87,7 +85,7 @@ object RequestDemo {
),
div(
p(
cls := "profile-description",
cls := "text-white",
requestSub
.doOnNext(str => me.Task(println(str)))
.mapEval(request)

View 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)
}

View File

@ -1,15 +1,18 @@
package nova.monadic_sfx.ui.components.todo
package outwatchapp.ui.components.todo
import scala.scalajs.js.annotation.JSExportAll
import cats.kernel.Eq
import com.softwaremill.quicklens._
import io.circe.generic.JsonCodec
import io.odin.Logger
import monix.bio.Task
import nova.monadic_sfx.util.reactive.store.Reducer
import nova.monadic_sfx.util.reactive.store.Store
import outwatchapp.util.reactive.store.Middlewares.actionLoggerMiddleware
import outwatchapp.util.reactive.store.Reducer
import outwatchapp.util.reactive.store.Store
@JSExportAll
@JsonCodec
case class Todo(id: Int, content: String)
object Todo {
implicit val eqForTodo = Eq.fromUniversalEquals[Todo]
@ -37,18 +40,16 @@ object TodoListStore {
implicit val eqForState = Eq.fromUniversalEquals[State]
}
def reducer()(
def reducer(logger: Logger[Task])(
state: State,
action: Action
): (State, Option[Task[Action]]) =
action match {
case Init => (state, None)
case Add(content) =>
println("hello")
val nextAction = Some(for {
// do some validation
// _ <- logger.debug(s"Received $content")
_ <- Task(println(s"Received $content"))
res <- Task.pure(InternalAdd(content))
} yield res)
(state, nextAction)
@ -68,19 +69,21 @@ object TodoListStore {
.using(_ :+ Todo(state.counter, content))
.modify(_.counter)
.using(_ + 1)
(nextState, Some(Task.pure(End)))
(nextState, Some(logger.debug(s"Received $content") >> Task.pure(End)))
case End => (state, None)
}
def apply(): Task[Store[Action, State]] =
def apply(logger: Logger[Task]): Task[Store[Action, State]] =
Task.deferAction(implicit s =>
for {
logMware <- actionLoggerMiddleware[Action, State](logger, "TodoStore")
store <-
Store
.createL[Action, State](
Init,
State(Vector.empty[Todo], 0),
Reducer.withOptionalEffects(reducer() _)
Reducer.withOptionalEffects(reducer(logger) _),
Seq(logMware)
)
} yield store
)

View File

@ -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
}
}

View File

@ -1,15 +1,28 @@
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(
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(
div(cls := "title", "Home"),
div(
cls := "card",
@ -17,8 +30,16 @@ class HomePage(
cls := "card-body",
counterDemo,
chartDemo,
div(
cls := "text-center",
button(
cls := "btn btn-primary",
onClick.preventDefault.doAsync(loginDemo),
"Login"
)
),
p(
cls := "profile-description",
cls := "text-white",
div(
"hm",
htmlTag("blockQuote")(
@ -38,4 +59,6 @@ class HomePage(
)
)
)
)
)
}

View File

@ -0,0 +1,21 @@
package outwatchapp.util
import cats.arrow.FunctionK
import monix.bio.IO
object IOUtils {
def toIO[T](task: monix.eval.Task[T]) =
IO.deferAction(implicit s => IO.from(task))
def toTask[T](bio: monix.bio.IO[Throwable, T]) =
monix.eval.Task.deferAction(implicit s => bio.to[monix.eval.Task])
val ioTaskMapk =
new FunctionK[monix.eval.Task, monix.bio.Task] {
override def apply[A](
fa: monix.eval.Task[A]
): monix.bio.Task[A] = toIO(fa)
}
}

View 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
}
}

View 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)
}

View 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")
// }
// }
// }

View File

@ -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))
}

View 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)
}

View File

@ -1,4 +1,4 @@
package nova.monadic_sfx.util.reactive.store
package outwatchapp.util.reactive
import scala.concurrent.Future

View File

@ -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)
}

View File

@ -0,0 +1,132 @@
package outwatchapp.util.reactive
import scala.concurrent.Future
import io.circe.Decoder
import io.circe.Encoder
import io.circe.Printer
import io.circe.generic.JsonCodec
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 org.scalajs.dom.raw.Event
import org.scalajs.dom.raw.MessageEvent
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,
overflowStrategy: OverflowStrategy.Synchronous[T]
) {
val printer = Printer.noSpaces
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 { 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.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"))
}
c += Cancelable { () =>
println("Closing websocket")
ws.close()
}
}
.publish
.refCount
)
// empty subscription because otherwise ouwtatch kills the observable when
// the dom containing this observable is unmounted
_ <- Task(c += obs.subscribe(Observer.empty))
} yield obs
)
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
def apply[T <: Product: Encoder: Decoder](
path: String,
overflowStrategy: OverflowStrategy.Synchronous[T] =
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
} yield MonixProSubject.from(sink, source)
}
@JsonCodec
case class WebsocketData(data: Long)

View File

@ -0,0 +1,121 @@
package outwatchapp.util.reactive
import scala.concurrent.Future
import io.circe.Decoder
import io.circe.Encoder
import io.circe.Printer
import io.circe.generic.JsonCodec
import io.circe.parser._
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 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]
) {
val printer = Printer.noSpaces
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 { 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(
TerminatedException(s"Worker terminated with error: $e")
)
c += Cancelable(() => worker.terminate())
}
.doOnSubscriptionCancelF(Coeval(println("Worker cancelled")))
.publish
.refCount
)
_ <- Task(c += obs.subscribe(Observer.empty))
} yield obs
)
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
def apply[T <: Product: Encoder: Decoder](
path: String,
overflowStrategy: OverflowStrategy.Synchronous[T] =
OverflowStrategy.DropOld(50)
): Task[WebWorker[T]] = for {
worker <- Task(new sjsdr.Worker(path))
impl = new WebWorkerImpl[T](worker, overflowStrategy)
source <- impl.source
sink <- impl.sink
} yield MonixProSubject.from(sink, source)
}
@JsonCodec
case class WorkerData(data: Long)

View File

@ -0,0 +1,12 @@
package outwatchapp.util
import monix.reactive.Observable
import monix.reactive.Observer
package object reactive {
type MonixProSubject[-I, +O] = Observable[O] with Observer[I]
type MonixSubject[A] = MonixProSubject[A, A]
type WebWorker[A] = MonixSubject[A]
type WebSocket[A] = MonixSubject[A]
type DedicatedWorker[A] = MonixSubject[A]
}

View File

@ -1,12 +1,18 @@
package nova.monadic_sfx.util.reactive.store
package outwatchapp.util.reactive.store
import java.time.LocalDateTime
import io.circe.Encoder
import io.circe.Printer
import io.circe.generic.JsonCodec
// object Middleware {
// def apply[A,M,T](ob: Observable[(A,M)], cb: (A,M) => T): Observable[(A,M)] = ob
// }
import io.circe.syntax._
import io.odin.Logger
import io.odin.LoggerMessage
import io.odin.formatter.options.PositionFormat
import io.odin.formatter.options.ThrowableFormat
import io.odin.meta.Render
import monix.bio.Task
import monix.reactive.Observable
@JsonCodec
final case class StoreInfo[A](
@ -17,6 +23,42 @@ final case class StoreInfo[A](
object StoreInfo {
val printer = Printer.noSpaces
implicit def render[T: Encoder]: Render[StoreInfo[T]] =
new Render[StoreInfo[T]] {
override def render(m: StoreInfo[T]): String = printer.print(m.asJson)
}
}
object Middlewares {}
object Middlewares {
val format = create(ThrowableFormat.Default, PositionFormat.Full)
def create(
throwableFormat: ThrowableFormat,
positionFormat: PositionFormat
): io.odin.formatter.Formatter = (msg: LoggerMessage) => msg.message.value
def actionStateLoggerMiddleware[A, M](
logger: Logger[Task]
): Task[Middleware[A, M]] =
Task.deferAction(implicit s =>
Task((obs: Observable[(A, M)]) =>
obs.doOnNextF { case (a, m) =>
logger.debug(s"Received action $a with state $m")
}
)
)
def actionLoggerMiddleware[A: Encoder, M](
logger: Logger[Task],
name: String
): Task[Middleware[A, M]] =
Task.deferAction(implicit s =>
Task((obs: Observable[(A, M)]) =>
obs.doOnNextF { case (a, _) =>
Task(LocalDateTime.now())
.flatMap(curTime => logger.debug(StoreInfo(name, a, curTime)))
}
)
)
}

View File

@ -1,4 +1,4 @@
package nova.monadic_sfx.util.reactive.store
package outwatchapp.util.reactive.store
import cats.implicits._
import monix.reactive.Observable
@ -6,8 +6,7 @@ import monix.reactive.ObservableLike
object Reducer {
/**
* Creates a Reducer which yields a new State, as-well as an Observable of Effects
/** Creates a Reducer which yields a new State, as-well as an Observable of Effects
* Effects are Actions which will be executed after the Action that caused them to occur.
* This is accomplished by subscribing to the Effects Observable within the stores scan loop.
*
@ -20,14 +19,12 @@ object Reducer {
f: (M, A) => (M, F[A])
): Reducer[A, M] = (s: M, a: A) => f(s, a).map(ObservableLike[F].apply)
/**
* Creates a reducer which just transforms the state, without additional effects.
/** Creates a reducer which just transforms the state, without additional effects.
*/
def apply[A, M](f: (M, A) => M): Reducer[A, M] =
(s: M, a: A) => f(s, a) -> Observable.empty
/**
* Creates a Reducer with an optional effect.
/** Creates a Reducer with an optional effect.
*/
def withOptionalEffects[F[_]: ObservableLike, A, M](
f: (M, A) => (M, Option[F[A]])

View File

@ -1,10 +1,11 @@
package nova.monadic_sfx.util.reactive.store
package outwatchapp.util.reactive.store
import monix.bio.Task
import monix.eval.Coeval
import monix.reactive.Observer
import monix.reactive.OverflowStrategy
import monix.reactive.subjects.ConcurrentSubject
import monix.reactive.Observer
import outwatchapp.util.reactive.MonixProSubject
object Store {
def createL[A, M](

View File

@ -1,15 +1,12 @@
package nova.monadic_sfx.util.reactive
package outwatchapp.util.reactive
import monix.reactive.Observable
import monix.reactive.Observer
package object store {
type MonixProSubject[-I, +O] = Observable[O] with Observer[I]
type Middleware[A, M] = Observable[(A, M)] => Observable[(A, M)]
type Store[A, M] = MonixProSubject[A, (A, M)]
/**
* A Function that applies an Action onto the Stores current state.
/** A Function that applies an Action onto the Stores current state.
* @param reducer The reducing function
* @tparam A The Action Type
* @tparam M The Model Type

File diff suppressed because it is too large Load Diff

25
webpack.config.common.js Normal file
View File

@ -0,0 +1,25 @@
var webpack = require('webpack');
var merge = require('webpack-merge');
var generated = require('./scalajs.webpack.config');
var Path = require('path');
var local = {
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
test: /\.(ttf|eot|woff|png|glb|jpeg|jpg|mp4|jsn)$/,
use: 'file-loader'
},
{
test: /\.(eot)$/,
use: 'url-loader'
}
]
},
};
module.exports = merge(generated, local);

View File

@ -1,26 +1,10 @@
var webpack = require('webpack');
var merge = require('webpack-merge');
var generated = require('./scalajs.webpack.config');
var common = require('./webpack.config.common.js');
var Path = require('path');
const rootDir = Path.resolve(__dirname, '../../../..');
var local = {
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
test: /\.(ttf|eot|woff|png|glb|jpeg|jpg|mp4|jsn)$/,
use: 'file-loader'
},
{
test: /\.(eot)$/,
use: 'url-loader'
}
]
},
devServer: {
contentBase: [
Path.resolve(__dirname, 'dev'), // fastOptJS output
@ -35,12 +19,6 @@ var local = {
ignored: ["node_modules"]
}
},
plugins: [
new webpack.ProvidePlugin({
$: 'jquery/src/jquery',
jquery: 'jquery/src/jquery'
})
]
};
module.exports = merge(generated, local);
module.exports = merge(common, local);

View File

@ -1,13 +0,0 @@
var webpack = require('webpack');
module.exports = require('./scalajs.webpack.config');
module.exports.module = {
...module.exports.module,
rules: module.exports.module.rules.concat([
{
test: /\.css$/i,
use: ['style-loader', 'css-loader'],
},
]),
};

8
webpack.config.prod.js Normal file
View File

@ -0,0 +1,8 @@
var webpack = require('webpack');
var merge = require('webpack-merge');
var common = require('./webpack.config.common.js');
var Path = require('path');
var local = {};
module.exports = merge(common, local);

128
yarn.lock
View File

@ -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"
@ -28,9 +47,19 @@
integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
"@types/node@*":
version "14.14.10"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.10.tgz#5958a82e41863cfc71f2307b3748e3491ba03785"
integrity sha512-J32dgx2hw8vXrSbu4ZlVhn1Nm3GbeCFNw2FWL8S5QKucHGY0cyNwjdQdO+KMBZ4wpmC7KhLCiNsdk1RFRIYUQQ==
version "14.14.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"
@ -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"
@ -1230,9 +1274,9 @@ enhanced-resolve@^4.1.0:
tapable "^1.0.0"
errno@^0.1.3, errno@~0.1.7:
version "0.1.7"
resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618"
integrity sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==
version "0.1.8"
resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.8.tgz#8bb3e9c7d463be4976ff888f76b4809ebc2e811f"
integrity sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==
dependencies:
prr "~1.0.1"
@ -1532,9 +1576,9 @@ flush-write-stream@^1.0.0:
readable-stream "^2.3.6"
follow-redirects@^1.0.0:
version "1.13.0"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db"
integrity sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==
version "1.13.1"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.1.tgz#5f69b813376cee4fd0474a3aba835df04ab763b7"
integrity sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg==
for-in@^1.0.2:
version "1.0.2"
@ -1615,9 +1659,9 @@ get-caller-file@^2.0.1:
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
get-intrinsic@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.0.1.tgz#94a9768fcbdd0595a1c9273aacf4c89d075631be"
integrity sha512-ZnWP+AmS1VUaLgTRy47+zKtjTxz+0xMpx3I52i+aalBK1QP19ggLF3Db89KJX7kjfOfP2eoa01qc++GwPgufPg==
version "1.0.2"
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.0.2.tgz#6820da226e50b24894e08859469dc68361545d49"
integrity sha512-aeX0vrFm21ILl3+JpFFRNe9aUvp6VFZb2/CTbgLb8j75kOhvoNYjt9d8KA/tJG4gSo8nzEDedRl0h7vDmBYRVg==
dependencies:
function-bind "^1.1.1"
has "^1.0.3"
@ -1795,9 +1839,9 @@ hpack.js@^2.1.6:
wbuf "^1.1.0"
html-entities@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.3.1.tgz#fb9a1a4b5b14c5daba82d3e34c6ae4fe701a0e44"
integrity sha512-rhE/4Z3hIhzHAUKbW8jVcCyuT5oJCXXqhN/6mXXVCpzTmvJnoH2HL/bt3EZ6p55jbFJBeAe1ZNpL5BugLujxNA==
version "1.4.0"
resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.4.0.tgz#cfbd1b01d2afaf9adca1b10ae7dffab98c71d2dc"
integrity sha512-8nxjcBcd8wovbeKx7h3wTji4e6+rhaVuPNpMqwWgnHh+N9ToqsCs6XztWRBPQ+UtzsoMAdKZtUENoVzU/EMtZA==
http-deceiver@^1.2.7:
version "1.2.7"
@ -1934,9 +1978,9 @@ inherits@2.0.3:
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
ini@^1.3.4:
version "1.3.5"
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==
version "1.3.8"
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
internal-ip@^4.3.0:
version "4.3.0"
@ -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"
@ -2455,16 +2499,11 @@ mime@1.6.0:
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
mime@^2.0.3:
mime@^2.0.3, mime@^2.4.4:
version "2.4.7"
resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.7.tgz#962aed9be0ed19c91fd7dc2ece5d7f4e89a90d74"
integrity sha512-dhNd1uA2u397uQk3Nv5LM4lm93WYDUXFn3Fu291FJerns4jyTudqhIWe4W04YLy7Uk1tm1Ore04NpjRvQp/NPA==
mime@^2.4.4:
version "2.4.6"
resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.6.tgz#e5b407c90db442f2beb5b162373d07b69affa4d1"
integrity sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==
mimic-fn@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
@ -2550,11 +2589,16 @@ ms@2.1.1:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
ms@2.1.2, ms@^2.1.1:
ms@2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
ms@^2.1.1:
version "2.1.3"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
multicast-dns-service-types@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901"
@ -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"
@ -2942,11 +2991,6 @@ pkg-dir@^3.0.0:
dependencies:
find-up "^3.0.0"
popper.js@1.16.1:
version "1.16.1"
resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1.tgz#2a223cb3dc7b6213d740e40372be40de43e65b1b"
integrity sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==
portfinder@^1.0.26:
version "1.0.28"
resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.28.tgz#67c4622852bd5374dd1dd900f779f53462fac778"
@ -3181,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"
@ -3756,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"
@ -4051,9 +4105,9 @@ webpack-cli@3.3.2:
yargs "^12.0.5"
webpack-dev-middleware@^3.7.2:
version "3.7.2"
resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.7.2.tgz#0019c3db716e3fa5cecbf64f2ab88a74bab331f3"
integrity sha512-1xC42LxbYoqLNAhV6YzTYacicgMZQTqRd27Sim9wn5hJrX3I5nxYy1SxSd4+gjUFsz1dQFj+yEe6zEVmSkeJjw==
version "3.7.3"
resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.7.3.tgz#0639372b143262e2b84ab95d3b91a7597061c2c5"
integrity sha512-djelc/zGiz9nZj/U7PTBi2ViorGJXEWo/3ltkPbDyxCXhhEXkW0ce99falaok4TPj+AsxLiXJR0EBOb0zh9fKQ==
dependencies:
memory-fs "^0.4.1"
mime "^2.4.4"