Skip to content

Commit

Permalink
no more tuple marker, no more Directive1, LinkHandler optimization, a…
Browse files Browse the repository at this point in the history
…dditional directives (hostname, port, etc), scala.js 1.3.1+
  • Loading branch information
yurique committed Jan 1, 2021
1 parent 67ae571 commit bbc61bb
Show file tree
Hide file tree
Showing 14 changed files with 421 additions and 319 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Changelog

### 0.11.4

* New: LinkHandler
* New: directives: `origin`, `host`, `hostname`, `port` and `protocol`
* simplified directives and path matchers (single values are now scalars, not Tuple1), no more `Tuple` marker
* Breaking: Scala.js 1.3.1+ is now required

### 0.11.3

Update the tuplez dependency to `v0.3.0`, no other changes.

### 0.11.2

* preserving scroll position in history state
Expand Down
292 changes: 146 additions & 146 deletions README.md

Large diffs are not rendered by default.

14 changes: 7 additions & 7 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@ scalaVersion := "2.13.4"
crossScalaVersions := Seq("2.12.12", "2.13.4")

libraryDependencies ++= Seq(
"app.tulz" %%% "tuplez-full" % "0.3.0",
"app.tulz" %%% "tuplez-apply" % "0.3.0",
"app.tulz" %%% "tuplez-tuple" % "0.3.0",
"com.raquo" %%% "airstream" % "0.11.1",
"com.raquo" %%% "laminar" % "0.11.0" % Test,
"com.lihaoyi" %%% "utest" % "0.7.5" % Test
"com.raquo" %%% "airstream" % "0.11.1",
"app.tulz" %%% "tuplez-full-light" % "0.3.1",
"app.tulz" %%% "tuplez-apply" % "0.3.1",
"com.raquo" %%% "laminar" % "0.11.0" % Test,
"com.lihaoyi" %%% "utest" % "0.7.5" % Test
)

lazy val adjustScalacOptions = { options: Seq[String] =>
options.filterNot(
Set(
"-Wdead-code"
"-Wdead-code",
"-Ywarn-dead-code"
)
)
}
Expand Down
2 changes: 1 addition & 1 deletion project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
logLevel := Level.Warn

addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.1.1")
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.3.1")

addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0")

Expand Down
5 changes: 5 additions & 0 deletions src/main/scala/io/frontroute/BrowserNavigation.scala
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,11 @@ object BrowserNavigation {

private def extractRouteLocation(event: dom.PopStateEvent) =
RouteLocation(
hostname = dom.window.location.hostname,
port = dom.window.location.port,
protocol = dom.window.location.protocol,
host = dom.window.location.host,
origin = dom.window.location.origin.toOption,
unmatchedPath = extractPath(dom.window.location),
params = extractParams(dom.window.location),
state = HistoryState.tryParse(event.state)
Expand Down
3 changes: 1 addition & 2 deletions src/main/scala/io/frontroute/ConjunctionMagnet.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package io.frontroute

import app.tulz.tuplez.Tuple
import app.tulz.tuplez.Composition

trait ConjunctionMagnet[L] {
Expand All @@ -20,7 +19,7 @@ object ConjunctionMagnet {
inner(composition.compose(prefix, suffix))
}(location, previous, state.path("&"))
}(location, previous, state)
}(Tuple.yes)
}
}

}
70 changes: 24 additions & 46 deletions src/main/scala/io/frontroute/Directive.scala
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
package io.frontroute

import app.tulz.tuplez._
import com.raquo.airstream.eventstream.EventStream
import com.raquo.airstream.signal.Signal
import com.raquo.airstream.signal.Var

class Directive[L](
val tapply: (L => Route) => Route
)(implicit val ev: Tuple[L]) {
) {
self =>

def tflatMap[R: Tuple](next: L => Directive[R]): Directive[R] = {
def flatMap[R](next: L => Directive[R]): Directive[R] = {
Directive[R] { inner =>
self.tapply { value => (location, previous, state) =>
next(value).tapply(inner)(location, previous, state.path(".flatMap"))
}
}
}

def tmap[R: Tuple](f: L => R): Directive[R] =
def map[R](f: L => R): Directive[R] =
Directive[R] { inner =>
self.tapply { value => (location, previous, state) =>
inner(f(value))(location, previous, state.path(".map"))
Expand All @@ -34,7 +33,7 @@ class Directive[L](
inner(value)(ctx, previous, state.leaveDisjunction())
}(ctx, previous, state.enterDisjunction())
.flatMap {
case complete: RouteResult.Complete => EventStream.fromValue(complete, emitOnce = true)
case complete: RouteResult.Complete => EventStream.fromValue(complete, emitOnce = false)
case RouteResult.Rejected =>
other.tapply { value => (ctx, previous, state) =>
inner(value)(ctx, previous, state.leaveDisjunction())
Expand All @@ -44,7 +43,7 @@ class Directive[L](

}

def tcollect[R: Tuple](f: PartialFunction[L, R]): Directive[R] =
def collect[R](f: PartialFunction[L, R]): Directive[R] =
Directive[R] { inner =>
self.tapply { value => (location, previous, state) =>
if (f.isDefinedAt(value)) {
Expand All @@ -55,7 +54,7 @@ class Directive[L](
}
}

def tfilter(predicate: L => Boolean): Directive[L] =
def filter(predicate: L => Boolean): Directive[L] =
Directive[L] { inner =>
self.tapply { value => (location, previous, state) =>
if (predicate(value)) {
Expand All @@ -66,11 +65,28 @@ class Directive[L](
}
}

def signal: Directive[Signal[L]] =
new Directive[Signal[L]]({ inner => (ctx, previous, state) =>
this.tapply {
value => // TODO figure this out, when this is run, enter is not yet called
(ctx, previous, state) =>
val next = state.unsetValue().path(".signal")
previous.getValue[Var[L]](next.path) match {
case None =>
val var$ = Var(value)
inner(var$.signal)(ctx, previous, next.setValue(var$))
case Some(var$) =>
var$.writer.onNext(value)
inner(var$.signal)(ctx, previous, next.setValue(var$))
}
}(ctx, previous, state)
})

}

object Directive {

def apply[L: Tuple](f: (L => Route) => Route): Directive[L] = {
def apply[L](f: (L => Route) => Route): Directive[L] = {
new Directive[L](inner =>
(ctx, previous, state) =>
f(value =>
Expand All @@ -81,42 +97,4 @@ object Directive {
)
}

implicit def toDirective[L: Tuple](route: Route): Directive[L] =
Directive[L](_ => route)

implicit class SingleValueModifiers[L](underlying: Directive1[L]) extends AnyRef {

def map[R](f: L => R): Directive1[R] =
underlying.tmap { case Tuple1(value) => Tuple1(f(value)) }

def flatMap[R](f: L => Directive1[R]): Directive1[R] =
underlying.tflatMap { case Tuple1(value) => f(value) }

def collect[R: Tuple](f: PartialFunction[L, R]): Directive[R] =
underlying.tcollect { case Tuple1(value) =>
f(value)
}

def filter(predicate: L => Boolean): Directive1[L] =
underlying.tfilter { case Tuple1(value) => predicate(value) }

def signal: Directive1[Signal[L]] =
new Directive[Tuple1[Signal[L]]]({ inner => (ctx, previous, state) =>
underlying.tapply {
value => // TODO figure this out, when this is run, enter is not yet called
(ctx, previous, state) =>
val next = state.unsetValue().path(".signal")
previous.getValue[Var[L]](next.path) match {
case None =>
val var$ = Var(value._1)
inner(Tuple1(var$.signal))(ctx, previous, next.setValue(var$))
case Some(var$) =>
var$.writer.onNext(value._1)
inner(Tuple1(var$.signal))(ctx, previous, next.setValue(var$))
}
}(ctx, previous, state)
})

}

}
73 changes: 39 additions & 34 deletions src/main/scala/io/frontroute/Directives.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package io.frontroute

import app.tulz.tuplez.Tuple
import com.raquo.airstream.eventstream.EventStream
import com.raquo.airstream.signal.Signal
import io.frontroute.debug.Logging
Expand All @@ -9,40 +8,40 @@ import scala.scalajs.js

trait Directives extends DirectiveApplyConverters {

def reject: Route = (_, _, _) => EventStream.fromValue(RouteResult.Rejected, emitOnce = true)
def reject: Route = (_, _, _) => EventStream.fromValue(RouteResult.Rejected, emitOnce = false)

private[frontroute] def extractContext: Directive1[RouteLocation] =
Directive[Tuple1[RouteLocation]](inner => (ctx, previous, state) => inner(Tuple1(ctx))(ctx, previous, state))
private[frontroute] def extractContext: Directive[RouteLocation] =
Directive[RouteLocation](inner => (ctx, previous, state) => inner(ctx)(ctx, previous, state))

private[frontroute] def extract[T](f: RouteLocation => T): Directive1[T] =
Directive[Tuple1[T]](inner => (ctx, previous, state) => inner(Tuple1(f(ctx)))(ctx, previous, state))
private[frontroute] def extract[T](f: RouteLocation => T): Directive[T] =
extractContext.map(f)

def signal[T](signal: Signal[T]): Directive1[T] = {
Directive[Tuple1[T]](inner =>
def signal[T](signal: Signal[T]): Directive[T] = {
Directive[T](inner =>
(ctx, previous, state) => {
signal.flatMap { extracted =>
inner(Tuple1(extracted))(ctx, previous, state.path(".signal").setValue(extracted))
inner(extracted)(ctx, previous, state.path(".signal").setValue(extracted))
}
}
)
}

def param(name: String): Directive1[String] = {
Directive[Tuple1[String]](inner =>
def param(name: String): Directive[String] = {
Directive[String](inner =>
(ctx, previous, state) => {
ctx.params.get(name).flatMap(_.headOption) match {
case Some(paramValue) => inner(Tuple1(paramValue))(ctx, previous, state.path(s"param($name)").setValue(paramValue))
case None => EventStream.fromValue(RouteResult.Rejected, emitOnce = true)
case Some(paramValue) => inner(paramValue)(ctx, previous, state.path(s"param($name)").setValue(paramValue))
case None => EventStream.fromValue(RouteResult.Rejected, emitOnce = false)
}
}
)
}

def historyState: Directive1[Option[js.Any]] = {
def historyState: Directive[Option[js.Any]] = {
extractContext.map(_.state.flatMap(_.user.toOption))
}

def historyScroll: Directive1[Option[ScrollPosition]] = {
def historyScroll: Directive[Option[ScrollPosition]] = {
extractContext.map(_.state.flatMap(_.internal.toOption).flatMap(_.scroll.toOption).map { scroll =>
ScrollPosition(
scrollX = scroll.scrollX.toOption.map(_.round.toInt),
Expand All @@ -51,30 +50,35 @@ trait Directives extends DirectiveApplyConverters {
})
}

def maybeParam(name: String): Directive1[Option[String]] =
Directive[Tuple1[Option[String]]](inner =>
def maybeParam(name: String): Directive[Option[String]] =
Directive[Option[String]](inner =>
(ctx, previous, state) => {
val maybeParamValue = ctx.params.get(name).flatMap(_.headOption)
inner(Tuple1(maybeParamValue))(ctx, previous, state.path(s"maybeParam($name)").setValue(maybeParamValue))
inner(maybeParamValue)(ctx, previous, state.path(s"maybeParam($name)").setValue(maybeParamValue))
}
)

def extractUnmatchedPath: Directive1[List[String]] =
extract(ctx => ctx.unmatchedPath)
def extractUnmatchedPath: Directive[List[String]] = extract(_.unmatchedPath)

def tprovide[L: Tuple](value: L): Directive[L] =
Directive(inner => (ctx, previous, state) => inner(value)(ctx, previous, state.path(".provide").setValue(value)))
def extractHostname: Directive[String] = extract(_.hostname)

def extractPort: Directive[String] = extract(_.port)

def extractHost: Directive[String] = extract(_.host)

def provide[L](value: L): Directive1[L] =
tprovide(Tuple1(value))
def extractProtocol: Directive[String] = extract(_.protocol)

def extractOrigin: Directive[Option[String]] = extract(_.origin)

def provide[L](value: L): Directive[L] =
Directive(inner => (ctx, previous, state) => inner(value)(ctx, previous, state.path(".provide").setValue(value)))

def pathPrefix[T](m: PathMatcher[T]): Directive[T] = {
import m.tuple
Directive[T](inner =>
(ctx, previous, state) => {
m(ctx.unmatchedPath) match {
case Right((t, rest)) => inner(t)(ctx.withUnmatchedPath(rest), previous, state.path(s"pathPrefix($m)").setValue(t))
case _ => EventStream.fromValue(RouteResult.Rejected, emitOnce = true)
case _ => EventStream.fromValue(RouteResult.Rejected, emitOnce = false)
}
}
)
Expand All @@ -86,18 +90,17 @@ trait Directives extends DirectiveApplyConverters {
if (ctx.unmatchedPath.isEmpty) {
inner(())(ctx, previous, state.path("path-end"))
} else {
EventStream.fromValue(RouteResult.Rejected, emitOnce = true)
EventStream.fromValue(RouteResult.Rejected, emitOnce = false)
}
}
)

def path[T](m: PathMatcher[T]): Directive[T] = {
import m.tuple
Directive[T](inner =>
(ctx, previous, state) => {
m(ctx.unmatchedPath) match {
case Right((t, Nil)) => inner(t)(ctx.withUnmatchedPath(List.empty), previous, state.path(s"path($m)").setValue(t))
case _ => EventStream.fromValue(RouteResult.Rejected, emitOnce = true)
case _ => EventStream.fromValue(RouteResult.Rejected, emitOnce = false)
}
}
)
Expand All @@ -106,14 +109,14 @@ trait Directives extends DirectiveApplyConverters {
def completeN[T](events: EventStream[() => Unit]): Route = { (_, _, state) =>
EventStream.fromValue(
RouteResult.Complete(state, events),
emitOnce = true
emitOnce = false
)
}

def complete[T](action: => Unit): Route = { (_, _, state) =>
EventStream.fromValue(
RouteResult.Complete(state, EventStream.fromValue(() => action, emitOnce = true)),
emitOnce = true
RouteResult.Complete(state, EventStream.fromValue(() => action, emitOnce = false)),
emitOnce = false
)
}

Expand All @@ -126,16 +129,18 @@ trait Directives extends DirectiveApplyConverters {
def concat(routes: Route*): Route = (ctx, previous, state) => {
def findFirst(rs: List[(Route, Int)]): EventStream[RouteResult] =
rs match {
case Nil => EventStream.fromValue(RouteResult.Rejected, emitOnce = true)
case Nil => EventStream.fromValue(RouteResult.Rejected, emitOnce = false)
case (route, index) :: tail =>
state.path(index.toString)
route(ctx, previous, state).flatMap {
case complete: RouteResult.Complete => EventStream.fromValue(complete, emitOnce = true)
case complete: RouteResult.Complete => EventStream.fromValue(complete, emitOnce = false)
case RouteResult.Rejected => findFirst(tail)
}
}

findFirst(routes.zipWithIndex.toList)
}

implicit def toDirective[L](route: Route): Directive[L] = Directive[L](_ => route)

}
Loading

0 comments on commit bbc61bb

Please sign in to comment.