Updated dependencies

Updated version to 0.0.10

Updated ScalaTest to 3.2.2
Updated scala version to 2.13.4
Updated scala.js version to 1.1.0
Updated to outwatch version to commit 676f94a
Added dependency to outwatch-utils
Updated scala.js bundler to 0.20.0
Updated sbt-microsites to 1.1.2
Removed obsolete scalac arguments

Updated Router class itself
- Now generic over effect type instead of being hardcoded to
cats.effect.IO
- Uses colibri.Observable instead of monix.Observable
- Added history events listener to update dom on back/forward
 button press
This commit is contained in:
Rohan Sircar 2020-12-08 12:41:02 +05:30 committed by Rohan Sircar
parent 7c235c7706
commit 2c40f7b73f
11 changed files with 105 additions and 84 deletions

3
.gitignore vendored
View File

@ -142,3 +142,6 @@ GitHub.sublime-settings
/.idea/
/outwatch-router/yarn.lock
/.bsp
metals.sbt

View File

@ -1,3 +1,4 @@
version = "2.7.4"
style = default
maxColumn = 100

View File

@ -2,14 +2,9 @@ import xerial.sbt.Sonatype._
cancelable in Global := true
val compilerPlugins = Seq(
addCompilerPlugin("org.typelevel" %% "kind-projector" % "0.10.2"),
addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full)
)
val versions = new {
val scalatest = "3.1.0-SNAP11"
val outwatch = "676f94a"
val scalatest = "3.2.2"
val outwatch = "61deece8"
}
val commonSettings = Seq(
@ -18,8 +13,8 @@ val commonSettings = Seq(
scalaVersion := Version.scalaVersion,
scalacOptions ++= options.scalac,
scalacOptions in (Compile, console) := options.scalacConsole,
updateOptions := updateOptions.value.withLatestSnapshots(false)
) ++ compilerPlugins
updateOptions := updateOptions.value.withLatestSnapshots(false),
)
lazy val publishSettings = Seq(
useGpg := true,
@ -29,15 +24,14 @@ lazy val publishSettings = Seq(
homepage := Some(url("https://github.com/clovellytech/outwatch-router")),
pomIncludeRepository := Function.const(false),
sonatypeProfileName := "com.clovellytech",
// License of your choice
licenses := Seq("MIT" -> url("http://opensource.org/licenses/MIT")),
// Where is the source code hosted
sonatypeProjectHosting := Some(GitHubHosting("clovellytech", "outwatch-router", "pattersonzak@gmail.com"))
sonatypeProjectHosting := Some(
GitHubHosting("clovellytech", "outwatch-router", "pattersonzak@gmail.com"),
),
)
lazy val docs = project
.in(file("./router-docs"))
.settings(commonSettings)
@ -55,18 +49,18 @@ lazy val docs = project
micrositeCompilingDocsTool := WithMdoc,
micrositeGithubOwner := "clovellytech",
micrositeGithubRepo := "outwatch-router",
scalacOptions := options.scalacConsole
scalacOptions := options.scalacConsole,
)
.settings(
mdocVariables := Map(
"VERSION" -> version.value
)
"VERSION" -> version.value,
),
)
.dependsOn(router)
lazy val copyFastOptJS = TaskKey[Unit]("copyFastOptJS", "Copy javascript files to target directory")
lazy val router = project
lazy val router = project
.in(file("./outwatch-router"))
.settings(name := "outwatch-router")
.enablePlugins(ScalaJSPlugin)
@ -81,21 +75,29 @@ lazy val router = project
webpackConfigFile in fastOptJS := Some(baseDirectory.value / "webpack.config.dev.js"),
// https://scalacenter.github.io/scalajs-bundler/cookbook.html#performance
webpackBundlingMode in fastOptJS := BundlingMode.LibraryOnly(),
resolvers += "jitpack" at "https://jitpack.io",
resolvers += "jitpack".at("https://jitpack.io"),
libraryDependencies ++= Seq(
"io.github.outwatch" % "outwatch" % versions.outwatch,
"com.github.outwatch.outwatch" %%% "outwatch" % versions.outwatch,
"com.github.outwatch.outwatch" %%% "outwatch-util" % versions.outwatch,
"org.scalatest" %%% "scalatest" % versions.scalatest % Test,
),
copyFastOptJS := {
val inDir = (crossTarget in (Compile, fastOptJS)).value
val outDir = (crossTarget in (Compile, fastOptJS)).value / "dev"
val files = Seq("outwatch-router-fastopt-loader.js", "outwatch-router-frontend-fastopt.js", "outwatch-router-frontend-fastopt.js.map") map { p => (inDir / p, outDir / p) }
val files = Seq(
"outwatch-router-fastopt-loader.js",
"outwatch-router-frontend-fastopt.js",
"outwatch-router-frontend-fastopt.js.map",
).map(p => (inDir / p, outDir / p))
IO.copy(files, overwrite = true, preserveLastModified = true, preserveExecutable = true)
},
// hot reloading configuration:
// https://github.com/scalacenter/scalajs-bundler/issues/180
addCommandAlias("dev", "; compile; fastOptJS::startWebpackDevServer; devwatch; fastOptJS::stopWebpackDevServer"),
addCommandAlias("devwatch", "~; fastOptJS; copyFastOptJS")
addCommandAlias(
"dev",
"; compile; fastOptJS::startWebpackDevServer; devwatch; fastOptJS::stopWebpackDevServer",
),
addCommandAlias("devwatch", "~; fastOptJS; copyFastOptJS"),
)
.settings(publishSettings)
@ -108,5 +110,3 @@ lazy val root = project
)
.dependsOn(router)
.aggregate(router)

View File

@ -1,60 +1,85 @@
package outwatch.router
import cats.effect.IO
import monix.execution.Scheduler
import cats.effect.Sync
import colibri.Observable
import org.scalajs.dom.window
import outwatch.dom._
import monix.reactive.Observable
import outwatch._
import outwatch.dsl._
import outwatch.util.Reducer
import outwatch.util.Store
sealed trait Action
final case class Replace(path: Path) extends Action
final case class HistoryEvent(path: Path) extends Action
final case class RouterState[P](page: P)
/**
* An AppRouter handles parsing of URLs and mapping to pages of the given type.
/** An AppRouter handles parsing of URLs and mapping to pages of the given type.
* @param siteRoot - The prefix part of a pathname, or the subpath at which your site is applied.
* Usually this is just Root, but your site might need a prefix as in /my_site/[parsed pathname]
* @param parent - The parent path at which this router is mounted. You can have routers contained in subroots of your site.
* @param f - a mapping function from a Path to a page P.
* @tparam F - the effect type
* @tparam P - Your page type, such as a sealed trait root type.
*/
class AppRouter[P](siteRoot: Path, parent: Path, f: Path => P) {
class AppRouter[F[_]: Sync, P](siteRoot: Path, parent: Path, f: Path => P) {
// Sync from the required page to the window.location
def routerReducer(state: RouterState[P], action: Action): RouterState[P] = action match {
case Replace(path) =>
window.history.pushState("", "", Path(siteRoot, Path(parent, path)).toUrlString)
state.copy(page = f(path))
case _ => state
}
def routerReducer(state: RouterState[P], action: Action): RouterState[P] =
action match {
case Replace(path) =>
window.history.pushState("", "", Path(siteRoot, Path(parent, path)).toUrlString)
state.copy(page = f(path))
case HistoryEvent(path) =>
state.copy(page = f(path))
case _ => state
}
def store(implicit S : Scheduler): IO[RouterStore[P]] = {
def store: F[RouterStore[P]] = {
val startingPath = Path(window.location.pathname)
Store.create[Action, RouterState[P]](
Store.create[F, Action, RouterState[P]](
Replace(startingPath),
RouterState(f(startingPath)),
Store.Reducer.justState(routerReducer _)
Reducer(routerReducer _),
)
}
def link(linkHref: String)(attrs: VDomModifier*)(implicit store: RouterStore[P]): BasicVNode =
a(href := linkHref)(
onClick.preventDefault.useLazy(Replace(Path(linkHref))) --> store.sink,
attrs,
)
def render(resolver: RouterResolve[P])(implicit store: RouterStore[P]): Observable[VDomModifier] =
store.map { case (_, RouterState(p)) => resolver(p) }
def watch()(implicit store: RouterStore[P]) =
emitter(outwatch.dsl.events.window.onPopState)
.useLazy(HistoryEvent(Path(org.scalajs.dom.window.location.pathname))) --> store.sink
}
object AppRouter{
def render[P](resolver: RouterResolve[P])(implicit store: RouterStore[P]): Observable[VDomModifier] =
store.map{ case (_, RouterState(p)) => resolver(p) }
object AppRouter {
def render[P](resolver: RouterResolve[P])(implicit
store: RouterStore[P],
): Observable[VDomModifier] = store.map { case (_, RouterState(p)) => resolver(p) }
def create[P](notFound: P)(f: PartialFunction[Path, P]): AppRouter[P] =
create[P](Root, notFound)(f)
def watch[P]()(implicit store: RouterStore[P]) = emitter(
outwatch.dsl.events.window.onPopState,
).useLazy(HistoryEvent(Path(org.scalajs.dom.window.location.pathname))) --> store.sink
def create[P](parent: Path, notFound: P)(f: PartialFunction[Path, P]): AppRouter[P] =
new AppRouter[P](Root, parent, f.lift.andThen(_.getOrElse(notFound)))
def create[F[_]: Sync, P](notFound: P)(f: PartialFunction[Path, P]): AppRouter[F, P] =
create[F, P](Root, notFound)(f)
def createParseSiteRoot[P](notFound: P)(f: PartialFunction[Path, P]): AppRouter[P] =
createParseSiteRoot[P](Root, notFound)(f)
def create[F[_]: Sync, P](parent: Path, notFound: P)(
f: PartialFunction[Path, P],
): AppRouter[F, P] = new AppRouter[F, P](Root, parent, f.lift.andThen(_.getOrElse(notFound)))
/**
* Automatically determine what siteroot we're using, based on the current URL and expected parent.
def createParseSiteRoot[F[_]: Sync, P](notFound: P)(
f: PartialFunction[Path, P],
): AppRouter[F, P] = createParseSiteRoot[F, P](Root, notFound)(f)
/** Automatically determine what siteroot we're using, based on the current URL and expected parent.
* For example, your site could be deployed at /example/directory/, your router root path could be /names,
* and the current url could be /example/directory/names/alice. So given the call:
* createParseSubRoot[Page](Path("/names"), NotFound)(f), the router will work out that the window location
@ -64,7 +89,9 @@ object AppRouter{
* @param f - a router function from Path to instances of your page type
* @tparam P - your page type
*/
def createParseSiteRoot[P](parent: Path, notFound: P)(f: PartialFunction[Path, P]): AppRouter[P] = {
def createParseSiteRoot[F[_]: Sync, P](parent: Path, notFound: P)(
f: PartialFunction[Path, P],
): AppRouter[F, P] = {
val initUrl = window.location.pathname
// url is of form /sra/srb/src/pa/pb/pc...
// so just drop the parent part from the right of the url if it exists.
@ -75,6 +102,6 @@ object AppRouter{
}
val routerFun: Path => P = f.lift.andThen(_.getOrElse(notFound))
new AppRouter[P](siteRoot, parent, routerFun)
new AppRouter[F, P](siteRoot, parent, routerFun)
}
}

View File

@ -1,13 +1,13 @@
package outwatch.router
package dsl
import outwatch.dom.VDomModifier
import outwatch.dom.{dsl => O, _}
import outwatch._
import outwatch.dsl._
object C {
def a[P](linkHref: String)(attrs: VDomModifier*)(implicit store: RouterStore[P]): BasicVNode =
O.a(
O.href := linkHref,
O.onClick.preventDefault.mapTo(Replace(Path(linkHref))) --> store
)(attrs)
def link[P](linkHref: String)(attrs: VDomModifier*)(implicit store: RouterStore[P]): BasicVNode =
a(href := linkHref)(
onClick.preventDefault.useLazy(Replace(Path(linkHref))) --> store.sink,
attrs,
)
}

View File

@ -1,9 +1,10 @@
package outwatch
import outwatch.dom.VDomModifier
import outwatch._
import colibri.ProSubject
package object router {
type RouterStore[P] = ProHandler[Action, (Action, RouterState[P])]
type RouterStore[P] = ProSubject[Action, (Action, RouterState[P])]
type RouterResolve[P] = PartialFunction[P, VDomModifier]
}

View File

@ -1,15 +1,14 @@
package outwatch
package router
import org.scalatest._
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
class PathTestSpec extends AnyFlatSpec with Matchers {
class PathTest(url: String, path: Path){
path.toUrlString should equal (url)
class PathTest(url: String, path: Path) {
path.toUrlString should equal(url)
}
"Root url" should "be /" in new PathTest("/", Root)
"A 1 part path" should "be correct" in new PathTest("/search", Root / "search")
}

View File

@ -14,16 +14,13 @@ object options {
"-unchecked", // Enable additional warnings where generated code depends on assumptions.
"-Xcheckinit", // Wrap field accessors to throw an exception on uninitialized access.
"-Xfatal-warnings", // Fail the compilation if there are any warnings.
"-Xfuture", // Turn on future language features.
"-Xlint:adapted-args", // Warn if an argument list is modified to match the receiver.
"-Xlint:by-name-right-associative", // By-name parameter of right associative operator.
"-Xlint:constant", // Evaluation of a constant arithmetic expression results in an error.
"-Xlint:delayedinit-select", // Selecting member of DelayedInit.
"-Xlint:doc-detached", // A Scaladoc comment appears to be detached from its element.
"-Xlint:inaccessible", // Warn about inaccessible types in method signatures.
"-Xlint:infer-any", // Warn when a type argument is inferred to be `Any`.
"-Xlint:missing-interpolator", // A string literal appears to be missing an interpolator id.
"-Xlint:nullary-override", // Warn when non-nullary `def f()' overrides nullary `def f'.
"-Xlint:nullary-unit", // Warn when nullary methods return Unit.
"-Xlint:option-implicit", // Option.apply used implicit view.
"-Xlint:package-object-classes", // Class or object defined in package object.
@ -31,15 +28,8 @@ object options {
"-Xlint:private-shadow", // A private field (or class parameter) shadows a superclass field.
"-Xlint:stars-align", // Pattern sequence wildcard must align with sequence component.
"-Xlint:type-parameter-shadow", // A local type parameter shadows a type already in scope.
"-Xlint:unsound-match", // Pattern match may not be typesafe.
"-Yno-adapted-args", // Do not adapt an argument list (either by inserting () or creating a tuple) to match the receiver.
"-Ypartial-unification", // Enable partial unification in type constructor inference
"-Ywarn-dead-code", // Warn when dead code is identified.
"-Ywarn-extra-implicit", // Warn when more than one implicit parameter section is defined.
"-Ywarn-inaccessible", // Warn about inaccessible types in method signatures.
"-Ywarn-infer-any", // Warn when a type argument is inferred to be `Any`.
"-Ywarn-nullary-unit", // Warn when nullary methods return Unit.
"-Ywarn-nullary-override", // Warn when non-nullary `def f()' overrides nullary `def f'.
"-Ywarn-numeric-widen", // Warn when numerics are widened.
"-Ywarn-unused:implicits", // Warn if an implicit parameter is unused.
"-Ywarn-unused:imports", // Warn if an import selector is not referenced.

View File

@ -1,4 +1,4 @@
object Version{
val version = "0.0.9"
val scalaVersion = "2.12.8"
object Version {
val version = "0.0.10"
val scalaVersion = "2.13.4"
}

View File

@ -1 +1 @@
sbt.version=1.3.0-RC2
sbt.version=1.4.3

View File

@ -1,6 +1,6 @@
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.28")
addSbtPlugin("ch.epfl.scala" % "sbt-scalajs-bundler" % "0.15.0-0.6")
addSbtPlugin("org.scalameta" % "sbt-mdoc" % "1.2.8" )
addSbtPlugin("com.47deg" % "sbt-microsites" % "0.8.0")
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.1.0")
addSbtPlugin("ch.epfl.scala" % "sbt-scalajs-bundler" % "0.20.0")
addSbtPlugin("org.scalameta" % "sbt-mdoc" % "1.2.8")
addSbtPlugin("com.47deg" % "sbt-microsites" % "1.1.2")
addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "2.3")
addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.2")