From 2c40f7b73fdf7bafd4e71083e2237ea722e606b9 Mon Sep 17 00:00:00 2001 From: Rohan Sircar Date: Tue, 8 Dec 2020 12:41:02 +0530 Subject: [PATCH] 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 --- .gitignore | 3 + .scalafmt.conf | 1 + build.sbt | 48 +++++------ .../main/scala/outwatch/router/Router.scala | 85 ++++++++++++------- .../main/scala/outwatch/router/dsl/C.scala | 14 +-- .../main/scala/outwatch/router/package.scala | 5 +- .../outwatch/router/AppRouterTestSpec.scala | 7 +- project/Options.scala | 10 --- project/Version.scala | 6 +- project/build.properties | 2 +- project/plugins.sbt | 8 +- 11 files changed, 105 insertions(+), 84 deletions(-) diff --git a/.gitignore b/.gitignore index 472442c..14fdab7 100644 --- a/.gitignore +++ b/.gitignore @@ -142,3 +142,6 @@ GitHub.sublime-settings /.idea/ /outwatch-router/yarn.lock + +/.bsp +metals.sbt \ No newline at end of file diff --git a/.scalafmt.conf b/.scalafmt.conf index 3557379..c02758e 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,3 +1,4 @@ +version = "2.7.4" style = default maxColumn = 100 diff --git a/build.sbt b/build.sbt index 06d4e5b..3308963 100644 --- a/build.sbt +++ b/build.sbt @@ -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) - - diff --git a/outwatch-router/src/main/scala/outwatch/router/Router.scala b/outwatch-router/src/main/scala/outwatch/router/Router.scala index bbe077e..c072b03 100644 --- a/outwatch-router/src/main/scala/outwatch/router/Router.scala +++ b/outwatch-router/src/main/scala/outwatch/router/Router.scala @@ -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 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](notFound: P)(f: PartialFunction[Path, P]): AppRouter[P] = - create[P](Root, notFound)(f) + def create[F[_]: Sync, P](notFound: P)(f: PartialFunction[Path, P]): AppRouter[F, P] = + create[F, P](Root, notFound)(f) - 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](parent: Path, notFound: P)( + f: PartialFunction[Path, P], + ): AppRouter[F, P] = new AppRouter[F, P](Root, parent, f.lift.andThen(_.getOrElse(notFound))) - def createParseSiteRoot[P](notFound: P)(f: PartialFunction[Path, P]): AppRouter[P] = - createParseSiteRoot[P](Root, notFound)(f) + 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. + /** 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) } } diff --git a/outwatch-router/src/main/scala/outwatch/router/dsl/C.scala b/outwatch-router/src/main/scala/outwatch/router/dsl/C.scala index 73e15df..930f59c 100644 --- a/outwatch-router/src/main/scala/outwatch/router/dsl/C.scala +++ b/outwatch-router/src/main/scala/outwatch/router/dsl/C.scala @@ -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, + ) } diff --git a/outwatch-router/src/main/scala/outwatch/router/package.scala b/outwatch-router/src/main/scala/outwatch/router/package.scala index 1087418..652ff66 100644 --- a/outwatch-router/src/main/scala/outwatch/router/package.scala +++ b/outwatch-router/src/main/scala/outwatch/router/package.scala @@ -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] } diff --git a/outwatch-router/src/test/scala/outwatch/router/AppRouterTestSpec.scala b/outwatch-router/src/test/scala/outwatch/router/AppRouterTestSpec.scala index e0092e2..73796cd 100644 --- a/outwatch-router/src/test/scala/outwatch/router/AppRouterTestSpec.scala +++ b/outwatch-router/src/test/scala/outwatch/router/AppRouterTestSpec.scala @@ -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") } - diff --git a/project/Options.scala b/project/Options.scala index 46016de..64cf7f8 100644 --- a/project/Options.scala +++ b/project/Options.scala @@ -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. diff --git a/project/Version.scala b/project/Version.scala index ce5614f..ea1a6bb 100644 --- a/project/Version.scala +++ b/project/Version.scala @@ -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" } diff --git a/project/build.properties b/project/build.properties index c9f2946..947bdd3 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.3.0-RC2 +sbt.version=1.4.3 diff --git a/project/plugins.sbt b/project/plugins.sbt index 2961453..4e739e8 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -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")