statesnapshot

This commit is contained in:
Rohan Sircar 2020-06-12 11:50:23 +05:30
parent be847a4cd3
commit ae1e0be7b8
19 changed files with 627 additions and 83 deletions

1
.gitignore vendored
View File

@ -33,3 +33,4 @@ bin/
.bloop .bloop
.metals .metals
.vscode

View File

@ -9,13 +9,14 @@ lazy val server = (project in file("server"))
libraryDependencies ++= Seq( libraryDependencies ++= Seq(
"com.vmunier" %% "scalajs-scripts" % "1.1.4", "com.vmunier" %% "scalajs-scripts" % "1.1.4",
guice, guice,
specs2 % Test specs2 % Test,
"com.softwaremill.quicklens" %% "quicklens" % "1.6.0"
), ),
// Compile the project before generating Eclipse files, so that generated .scala or .class files for views and routes are present // Compile the project before generating Eclipse files, so that generated .scala or .class files for views and routes are present
EclipseKeys.preTasks := Seq(compile in Compile) EclipseKeys.preTasks := Seq(compile in Compile)
) )
.enablePlugins(PlayScala) .enablePlugins(PlayScala)
// .enablePlugins(SbtWeb) .enablePlugins(SbtWeb)
.enablePlugins(WebScalaJSBundlerPlugin) .enablePlugins(WebScalaJSBundlerPlugin)
.dependsOn(sharedJvm) .dependsOn(sharedJvm)
@ -27,6 +28,12 @@ lazy val client = (project in file("client"))
"org.scala-js" %%% "scalajs-dom" % "1.0.0", "org.scala-js" %%% "scalajs-dom" % "1.0.0",
"com.github.japgolly.scalajs-react" %%% "core" % "1.7.0", "com.github.japgolly.scalajs-react" %%% "core" % "1.7.0",
"com.github.japgolly.scalajs-react" %%% "extra" % "1.7.0", "com.github.japgolly.scalajs-react" %%% "extra" % "1.7.0",
// "com.github.japgolly.scalajs-react" %%% "ext-monocle" % "1.7.0",
"com.github.japgolly.scalajs-react" %%% "ext-monocle-cats" % "1.7.0",
"com.softwaremill.quicklens" %%% "quicklens" % "1.5.0",
"com.github.julien-truffaut" %%% "monocle-core" % "2.0.4",
"com.github.julien-truffaut" %%% "monocle-macro" % "2.0.4",
"org.typelevel" %%% "cats-core" % "2.1.1"
// "com.github.japgolly.scalajs-react" %%% "test" % "1.7.0", // "com.github.japgolly.scalajs-react" %%% "test" % "1.7.0",
), ),
scalacOptions ++= Seq("-Ymacro-annotations", "-deprecation"), scalacOptions ++= Seq("-Ymacro-annotations", "-deprecation"),
@ -48,7 +55,11 @@ lazy val shared = crossProject(JSPlatform, JVMPlatform)
.crossType(CrossType.Pure) .crossType(CrossType.Pure)
.in(file("shared")) .in(file("shared"))
.settings(commonSettings) .settings(commonSettings)
.jsConfigure(_.enablePlugins(ScalaJSWeb)) // .jsConfigure(_.enablePlugins(ScalaJSWeb))
.jsConfigure(
_.enablePlugins(ScalaJSBundlerPlugin)
// .enablePlugins(ScalablyTypedConverterPlugin)
)
lazy val sharedJvm = shared.jvm lazy val sharedJvm = shared.jvm
lazy val sharedJs = shared.js lazy val sharedJs = shared.js

View File

@ -5,86 +5,20 @@ import org.scalajs.dom
import japgolly.scalajs.react._ import japgolly.scalajs.react._
import japgolly.scalajs.react.vdom.html_<^._ import japgolly.scalajs.react.vdom.html_<^._
import japgolly.scalajs.react.extra._ import japgolly.scalajs.react.extra._
import com.example.playscalajsreact.model.HelloWorldSJSRComponent import com.example.playscalajsreact.component.HelloWorldSJSRComponent
import japgolly.scalajs.react.extra.router._ import japgolly.scalajs.react.extra.router._
import japgolly.scalajs.react.extra.router.StaticDsl.Route import japgolly.scalajs.react.extra.router.StaticDsl.Route
import com.example.playscalajsreact.route.AppRouter
import com.example.playscalajsreact.component.Content
import com.example.playscalajsreact.component.Top2
object ScalaJSExample { object ScalaJSExample {
def main(args: Array[String]): Unit = { def main(args: Array[String]): Unit = {
sealed trait Page val div = dom.document.createElement("div")
case object Home extends Page dom.document.body.appendChild(div)
case object Hello extends Page Top2().renderIntoDOM(div)
case class Person(user: String) extends Page
case class ID(id: Int) extends Page
case class Menu(name: String, route: Page)
val mainMenu = Vector(
Menu("Home", Home),
Menu("Hello", Hello)
)
def layout(c: RouterCtl[Page], r: Resolution[Page]) =
<.ul(
// c.link(Home)("Home"),
// c.link(Hello)("Hello"),
mainMenu.toTagMod { item =>
{
<.li(
^.key := item.name,
item.name,
c setOnClick item.route
)
}
},
r.render()
)
val x = <.ol(
^.id := "my-list",
^.lang := "en",
^.margin := 8.px,
<.li("Item 1"),
<.li("Item 2"),
HelloWorldSJSRComponent("Hello", 18)
)
val routerConfig = RouterConfigDsl[Page].buildConfig { dsl =>
import dsl._
import japgolly.scalajs.react.vdom.Implicits._
case class Item(category: String, itemId: java.util.UUID) extends Page
// val r =
// ("category" / string("[a-z]+") / "item" / int.caseClassDebug[ID])
// FIXME uncomment this block to get an error - Companion object not found for class Product
// case class Product(category: Int, item: Int) extends Page
// val r: Route[Product] = ("cat" / int / "item" / int).caseClass[Product]
// val testRoute =
// ("user" / string("[a-z0-9]{1,20}") / "age" / int).pmap {
// case (a, b) => {}
// }
(emptyRule
| staticRoute(root, Home) ~> render(x)
// FIXME uncomment this block to get an error - Companion object not found for class Person
// | dynamicRouteCT("user" / string("[a-z0-9]{1,20}").caseClass[Person]) ~> dynRender(
// (page: Person) => {
// HelloWorldSJSRComponent(page.user, 0)
// }
// )
| staticRoute("#hello", Hello) ~> render(<.div("TODO"))
| staticRedirect("#hey") ~> redirectToPage(Hello)(SetRouteVia.HistoryReplace))
.notFound(redirectToPage(Home)(SetRouteVia.HistoryReplace))
.renderWith(layout)
}
// x.renderIntoDOM(dom.document.getElementById("app"))
val router = Router(BaseUrl.fromWindowOrigin / "index.html", routerConfig)
router().renderIntoDOM(dom.document.getElementById("app"))
dom.document.getElementById("scalajsShoutOut").textContent = dom.document.getElementById("scalajsShoutOut").textContent =
SharedMessages.itWorks SharedMessages.itWorks

View File

@ -0,0 +1,63 @@
package com.example.playscalajsreact.component
import com.example.playscalajsreact.model.MyGlobalState
import japgolly.scalajs.react.vdom.VdomElement
import japgolly.scalajs.react.Callback
import japgolly.scalajs.react._
import japgolly.scalajs.react.vdom.html_<^._
import com.example.playscalajsreact.route.AppRouter
import org.scalajs.dom
import scala.scalajs.js
import com.example.playscalajsreact.model.User
import japgolly.scalajs.react.extra.StateSnapshot
import com.softwaremill.quicklens._
import monocle.macros.Lenses
object Content {
import scala.concurrent.ExecutionContext.Implicits.global
final case class State(myGlobalState: MyGlobalState = MyGlobalState())
final class Backend($ : BackendScope[_, State]) {
val modifyState = modify[State](_.myGlobalState)
val modifyUsername = modifyState andThenModify MyGlobalState.modifyUsername
var interval: js.UndefOr[js.timers.SetIntervalHandle] =
js.undefined
def render(s: State): VdomElement =
// MyGlobalState.ctx.provide(s.myGlobalState) {
// <.div(AppRouter.router(AppRouter.Props(s.myGlobalState)))
// }
<.div
val updateState = (s: State) => {
val direct = $.withEffectsImpure
direct.modState(modifyUsername.using(_ + "C"))
}
val refresh = (s: State) =>
Callback {
interval = js.timers.setInterval(1000) {
updateState(s)
}
}
val clear = Callback {
interval foreach js.timers.clearInterval
interval = js.undefined
}
}
private val component = ScalaComponent
.builder[Unit]("content")
.initialState(State())
.renderBackend[Backend]
.componentDidMount($ => $.backend.refresh($.state))
.componentWillUnmount(_.backend.clear)
.build
def apply() = component()
}

View File

@ -1,4 +1,4 @@
package com.example.playscalajsreact.model package com.example.playscalajsreact.component
// import slinky.core.annotations.react // import slinky.core.annotations.react
// import slinky.core.StatelessComponent // import slinky.core.StatelessComponent
@ -13,13 +13,13 @@ import japgolly.scalajs.react._
// } // }
object HelloWorldSJSRComponent { object HelloWorldSJSRComponent {
import japgolly.scalajs.react.vdom.html_<^._ import japgolly.scalajs.react.vdom.all._
case class Props(name: String, age: Int) case class Props(name: String, age: Int)
private val component = ScalaComponent private val component = ScalaComponent
.builder[Props]("HelloWorldComponent") .builder[Props]("HelloWorldComponent")
.render_P(p => { .render_P(props => {
<.p(p.name + p.age) p(props.name + " " + props.age)
}) })
.build .build

View File

@ -0,0 +1,25 @@
package com.example.playscalajsreact.component
object IntEditor {
import japgolly.scalajs.react._
import japgolly.scalajs.react.vdom.html_<^._
import japgolly.scalajs.react.MonocleReact._
import japgolly.scalajs.react.extra._
import monocle.macros.Lenses
val component = ScalaComponent
.builder[StateSnapshot[Int]]
.render_P { stateSnapshot =>
<.span(
^.paddingLeft := "6ex", // leave some space for ReusabilityOverlay
<.button(
s"Current value is ${stateSnapshot.value}. Click to increment",
^.onClick --> stateSnapshot.modState(_ + 1),
)
)
}
.configure(ReusabilityOverlay.install)
.build
def apply(ss: StateSnapshot[Int]) = component(ss)
}

View File

@ -0,0 +1,54 @@
package com.example.playscalajsreact.component
import com.example.playscalajsreact.model.MyGlobalState
import japgolly.scalajs.react.vdom.VdomElement
import japgolly.scalajs.react.Callback
import japgolly.scalajs.react._
import japgolly.scalajs.react.vdom.html_<^._
import com.example.playscalajsreact.route.AppRouter
import japgolly.scalajs.react.extra.router.RouterCtl
import com.example.playscalajsreact.route.Page
import com.example.playscalajsreact.route.Page._
import japgolly.scalajs.react.extra.StateSnapshot
import com.example.playscalajsreact.model.User
object MenuComponent {
case class State(myGlobalState: MyGlobalState = MyGlobalState())
case class Props(state: StateSnapshot[MyGlobalState], c: RouterCtl[Page])
class Backend($ : BackendScope[Props, Unit]) {
def render(props: Props): VdomElement =
{
val name = props.state.value.user.getOrElse(User.empty).username
<.ul(
Array(
Menu("Home", Home),
Menu("Hello", Hello),
Menu(name, Person(name, 0)),
Menu("Editor", Editor),
Menu("Test", Test)
).toTagMod { item =>
{
<.li(
^.key := item.name,
<.a(
item.name,
props.c setOnClick item.route,
^.color := "red"
)
)
}
}
)
}
}
private val component = ScalaComponent
.builder[Props]("menu")
// .initialState(State())
.renderBackend[Backend]
// .componentDidMount($ => $.backend.refresh($.state))
.build
def apply(state: StateSnapshot[MyGlobalState], c: RouterCtl[Page]) = component(Props(state, c))
}

View File

@ -0,0 +1,58 @@
package com.example.playscalajsreact.component
import com.example.playscalajsreact.model.MyGlobalState
import japgolly.scalajs.react.vdom.VdomElement
import japgolly.scalajs.react.Callback
import japgolly.scalajs.react._
import japgolly.scalajs.react.vdom.html_<^._
import com.example.playscalajsreact.route.AppRouter
import org.scalajs.dom
import scala.scalajs.js
import com.example.playscalajsreact.model.User
import japgolly.scalajs.react.extra.StateSnapshot
import com.softwaremill.quicklens._
import monocle.macros.Lenses
import com.example.playscalajsreact.model._
import japgolly.scalajs.react.MonocleReact._
object Middle {
final case class Props(name: String, ss: StateSnapshot[Data]) {
@inline def render: VdomElement = Comp(this)
}
implicit def reusability: Reusability[Props] =
Reusability.derive
final class Backend($ : BackendScope[Props, Unit]) {
// Method 2: StateSnapshot.withReuse.zoomL.prepareViaProps
// Notice that we're using a normal lens here instead of a Reusable[lens]
private val ssStrFn =
StateSnapshot.withReuse.zoomL(Data.str).prepareViaProps($)(_.ss)
def render(p: Props): VdomElement = {
// Method 1: ss.withReuse.zoomStateL
val ssI: StateSnapshot[Int] = p.ss.zoomStateL(Data.reusableLens.int)
// Method 2: StateSnapshot.withReuse.zoomL.prepareViaProps
val ssS: StateSnapshot[String] =
ssStrFn(p.ss.value)
<.div(
<.h3(p.name),
<.div("IntEditor: ", IntEditor(ssI))
// <.div("TextEditor: ", TextEditor(ssS), ^.marginTop := "0.6em"))
)
}
}
val Comp = ScalaComponent
.builder[Props]
.renderBackend[Backend]
.configure(Reusability.shouldComponentUpdate)
.build
def apply(_props: Props): VdomElement = { Comp(_props) }
}

View File

@ -0,0 +1,47 @@
package com.example.playscalajsreact.component
import japgolly.scalajs.react.vdom.VdomElement
import japgolly.scalajs.react.Callback
import japgolly.scalajs.react._
import japgolly.scalajs.react.vdom.html_<^._
import com.example.playscalajsreact.route.AppRouter
import org.scalajs.dom
import scala.scalajs.js
import com.example.playscalajsreact.model.User
import japgolly.scalajs.react.extra.StateSnapshot
import com.softwaremill.quicklens._
import monocle.macros.Lenses
object NameChanger {
import japgolly.scalajs.react.MonocleReact._
@Lenses
case class Name(firstName: String, surname: String)
val NameChanger = ScalaComponent
.builder[StateSnapshot[String]]
.render_P { stateSnapshot =>
<.input.text(
^.value := stateSnapshot.value,
^.onChange ==> ((e: ReactEventFromInput) =>
stateSnapshot.setState(e.target.value)
)
)
}
.build
val Main = ScalaComponent
.builder[Unit]
.initialState(Name("John", "Wick"))
.render { $ =>
val name = $.state
val firstNameV = StateSnapshot.zoomL(Name.firstName).of($)
val surnameV = StateSnapshot.zoomL(Name.surname).of($)
<.div(
<.label("First name:", NameChanger(firstNameV)),
<.label("Surname:", NameChanger(surnameV)),
<.p(s"My name is ${name.surname}, ${name.firstName} ${name.surname}.")
)
}
.build
}

View File

@ -0,0 +1,170 @@
package com.example.playscalajsreact.component
import com.example.playscalajsreact.model.MyGlobalState
import japgolly.scalajs.react.vdom.VdomElement
import japgolly.scalajs.react._
import japgolly.scalajs.react.vdom.html_<^._
import com.example.playscalajsreact.route.AppRouter
import com.example.playscalajsreact.model.User
import japgolly.scalajs.react.extra.StateSnapshot
// import com.softwaremill.quicklens._
import com.example.playscalajsreact.model._
object Top {
final class Backend($ : BackendScope[Unit, Data]) {
private val setStateFn =
StateSnapshot.withReuse.prepareVia($)
def render(state: Data): VdomElement = {
val ss = setStateFn(state)
// Middle.Props("Demo", ss).render
Middle(Middle.Props("Demo", ss))
}
}
val Comp = ScalaComponent
.builder[Unit]
.initialState(Data(123, "hello"))
.renderBackend[Backend]
.build
}
object Top2 {
import japgolly.scalajs.react.vdom.all._
import japgolly.scalajs.react.MonocleReact._
final class Backend($ : BackendScope[Unit, MyGlobalState]) {
private val setStateFn =
StateSnapshot.withReuse.prepareVia($)
def render(state: MyGlobalState): VdomElement = {
// val ss = StateSnapshot.zoomL(MyGlobalState.user)(state).setStateVia($)
// val ss2 = ss.xmapState(u => Snappy.State(u))(_.user)
// div(Snappy.Props(ss2).render)
val ss = setStateFn(state)
div(
// Middle2.Props("Middle2", ss).render,
AppRouter.router(AppRouter.Props(ss)),
"Value: ",
state.user.map(_.username)
)
}
}
val Top2Component = ScalaComponent
.builder[Unit]("Top2")
.initialState(MyGlobalState(Some(User("testuser"))))
// .initialState(MyGlobalState.empty)
.renderBackend[Backend]
.build
def apply(): VdomElement = Top2Component()
}
object Middle2 {
import japgolly.scalajs.react.MonocleReact._
import monocle.macros.syntax.lens._
import monocle.std.option._
import monocle.macros.GenIso
import cats.implicits._
// val navigateToUsername = GenIso[MyGlobalState, Option[User]]
// .composePrism(GenIso[User, String].asPrism.below[Option])
val navigateToUsername =
MyGlobalState.user.composePrism(GenIso[User, String].asPrism.below[Option])
final case class Props(name: String, ss: StateSnapshot[MyGlobalState]) {
@inline def render: VdomElement = Comp(this)
}
implicit def reusability: Reusability[Props] =
Reusability.derive
final class Backend($ : BackendScope[Props, Unit]) {
// Method 2: StateSnapshot.withReuse.zoomL.prepareViaProps
// Notice that we're using a normal lens here instead of a Reusable[lens]
private val ssStrFn =
StateSnapshot.withReuse.zoomL(MyGlobalState.user).prepareViaProps($)(_.ss)
def render(p: Props): VdomElement = {
val x = p.ss.zoomStateO(navigateToUsername)
val y = x.map(_.xmapState(_ => 1)(_ => None))
// Method 1: ss.withReuse.zoomStateL
// val ssI: StateSnapshot[Int] = p.ss.zoomStateL(Data.reusableLens.int)
// Method 2: StateSnapshot.withReuse.zoomL.prepareViaProps
// val ssS: StateSnapshot[String] =
// ssStrFn(p.ss.value)
val ss4 = p.ss.zoomStateL(MyGlobalState.user)
// val ss5 = p.ss.zoomStateO(navigateToUsername.asOptional)
// val ss6 =
// ss5.map(_.xmapState(_.map(n => User(n)))(_.map(u => u.username)))
// ss5.foreach(_.modState(e => e))
// val x = p.ss.value.lens(_.user.getOrElse(User.empty).username)
// p.ss.zoomStateO()
val ss2 = ss4.xmapState(u => Snappy.State(u))(_.user)
<.div(
<.h3(p.name),
<.div("Snappy", Snappy.Props(ss2).render)
)
}
}
val Comp = ScalaComponent
.builder[Props]
.renderBackend[Backend]
// .configure(Reusability.shouldComponentUpdate)
.build
def apply(_props: Props): VdomElement = { Comp(_props) }
}
object Snappy {
final case class Props(state: StateSnapshot[State]) {
@inline def render: VdomElement = Component(this)
}
//implicit val reusabilityProps: Reusability[Props] =
// Reusability.derive
final case class State(user: Option[User])
object State {
def empty = State(user = None)
//implicit val reusability: Reusability[State] =
// Reusability.derive
}
final class Backend($ : BackendScope[Props, Unit]) {
import com.softwaremill.quicklens._
def render(p: Props): VdomNode = {
val s = p.state.value
<.div("Test", s.user.map(u => {
<.div(
"Username",
u.username,
^.onClick --> p.state.modState(
_.modify(_.user.each.username).using(_ + "c")
)
)
}))
}
}
val Component = ScalaComponent
.builder[Props]("Snappy")
.renderBackend[Backend]
//.configure(Reusability.shouldComponentUpdate)
.build
}

View File

@ -0,0 +1,19 @@
package com.example.playscalajsreact.model
import monocle.macros.Lenses
import japgolly.scalajs.react.Reusability
import japgolly.scalajs.react.Reusable
@Lenses
final case class Data(int: Int, str: String)
object Data {
implicit val reusability: Reusability[Data] = Reusability.derive
// Here we wrap the lenses in Reusable.byRef so that React can compare setState/modState functions and know when its
// it's got the same lens as a previous render. This is required to make [Method 1] work with Reusability
object reusableLens {
val int = Reusable.byRef(Data.int)
val str = Reusable.byRef(Data.str)
}
}

View File

@ -0,0 +1,19 @@
package com.example.playscalajsreact.model
import japgolly.scalajs.react.feature.Context
import japgolly.scalajs.react.React
import com.softwaremill.quicklens._
import monocle.macros.Lenses
import japgolly.scalajs.react.Reusability
@Lenses
case class MyGlobalState(user: Option[User] = None, name: String = "")
object MyGlobalState {
val ctx: Context[MyGlobalState] = React.createContext(MyGlobalState())
val modifyUsername = modify[MyGlobalState](_.user.each.username)
implicit val reusability: Reusability[MyGlobalState] = Reusability.derive
def empty = MyGlobalState(None, "")
}

View File

@ -0,0 +1,53 @@
package com.example.playscalajsreact.model
import japgolly.scalajs.react._
import japgolly.scalajs.react.vdom.html_<^._
object Top {
final case class Props() {
@inline def render: VdomElement = Component(this)
}
//implicit val reusabilityProps: Reusability[Props] =
// Reusability.derive
final class Backend($: BackendScope[Props, Unit]) {
def render(p: Props): VdomNode =
<.div
}
val Component = ScalaComponent.builder[Props]("Top")
.renderBackend[Backend]
//.configure(Reusability.shouldComponentUpdate)
.build
def apply() = Component(Props())
}
import japgolly.scalajs.react._
import japgolly.scalajs.react.vdom.all._
object Test2 {
final case class Props()
//implicit val reusabilityProps: Reusability[Props] =
// Reusability.derive
final class Backend($: BackendScope[Props, Unit]) {
def render(p: Props): VdomNode =
<.div
}
private val Test2Component = ScalaComponent.builder[Props]("Test2")
.renderBackend[Backend]
//.configure(Reusability.shouldComponentUpdate)
.build
def apply(): VdomElement = Test2Component(Props())
}
object Test3 {
Test2()
}

View File

@ -0,0 +1,13 @@
package com.example.playscalajsreact.model
import monocle.macros.Lenses
import japgolly.scalajs.react.Reusability
@Lenses
case class User(username: String)
object User {
def empty = User("")
implicit val reusability: Reusability[User] = Reusability.derive
}

View File

@ -0,0 +1,61 @@
package com.example.playscalajsreact.route
import org.scalajs.dom
import japgolly.scalajs.react._
import japgolly.scalajs.react.vdom.html_<^._
import japgolly.scalajs.react.extra._
import japgolly.scalajs.react.extra.router._
import com.example.playscalajsreact.component.HelloWorldSJSRComponent
import com.example.playscalajsreact.component.MenuComponent
import com.example.playscalajsreact.component.Top
import com.example.playscalajsreact.component.Top2
import com.example.playscalajsreact.model.MyGlobalState
import com.example.playscalajsreact.component.Middle2
object AppRouter {
import com.example.playscalajsreact.route.Page._
final case class Props(state: StateSnapshot[MyGlobalState])
private def layout(c: RouterCtl[Page], r: ResolutionWithProps[Page, Props])(
appState: Props
) =
<.div(
MenuComponent(appState.state, c),
r.renderP(appState)
)
val x = <.ol(
^.id := "my-list",
^.lang := "en",
^.margin := 8.px,
<.li("Item 1"),
<.li("Item 2")
// HelloWorldSJSRComponent("Hello", 18)
)
val routerConfig = RouterWithPropsConfigDsl[Page, Props].buildConfig { dsl =>
import dsl._
(emptyRule
| staticRoute(root, Home) ~> render(x)
| dynamicRouteCT(
("#user" / string("[a-zA-Z0-9]{1,20}") / "age" / int)
.caseClass[Person]
) ~> dynRender((page: Person) => {
HelloWorldSJSRComponent(page.user, page.age)
})
| staticRoute("#hello", Hello) ~> render(<.div("TODO"))
| staticRoute("#editor", Editor) ~> render(Top.Comp())
| staticRoute("#test", Test) ~> renderP(p => Middle2.Props("Aege", p.state).render)
| staticRedirect("#hey") ~> redirectToPage(Hello)(
SetRouteVia.HistoryReplace
))
.notFound(redirectToPage(Home)(SetRouteVia.HistoryReplace))
.renderWithP(layout)
}
val router =
RouterWithProps(BaseUrl.fromWindowOrigin / "index", AppRouter.routerConfig)
}

View File

@ -0,0 +1,15 @@
package com.example.playscalajsreact.route
sealed trait Page
object Page {
case object Home extends Page
case object Hello extends Page
case class Person(user: String, age: Int) extends Page
case class ID(id: Int) extends Page
case class Menu(name: String, route: Page)
case class Product(category: Int, item: Int) extends Page
case class Item(category: String, itemId: java.util.UUID) extends Page
case object Editor extends Page
case object Test extends Page
}

View File

@ -13,7 +13,7 @@ addSbtPlugin("org.scalablytyped.converter" % "sbt-converter" % "1.0.0-beta13")
// Use Scala.js v1.x // Use Scala.js v1.x
addSbtPlugin("com.vmunier" % "sbt-web-scalajs" % "1.0.11") addSbtPlugin("com.vmunier" % "sbt-web-scalajs" % "1.0.11")
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.0.1") addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.0.0")
// addSbtPlugin("ch.epfl.scala" % "sbt-scalajs-bundler" % "0.18.0") // addSbtPlugin("ch.epfl.scala" % "sbt-scalajs-bundler" % "0.18.0")
addSbtPlugin("ch.epfl.scala" % "sbt-web-scalajs-bundler" % "0.18.0") addSbtPlugin("ch.epfl.scala" % "sbt-web-scalajs-bundler" % "0.18.0")

View File

@ -4,6 +4,7 @@
# Home page # Home page
GET / com.example.playscalajsreact.controllers.Application.index GET / com.example.playscalajsreact.controllers.Application.index
GET /index com.example.playscalajsreact.controllers.Application.index
# Prefix must match `play.assets.urlPrefix` # Prefix must match `play.assets.urlPrefix`
GET /assets/*file controllers.Assets.at(file) GET /assets/*file controllers.Assets.at(file)

View File

@ -1,5 +1,5 @@
package com.example.playscalajsreact.shared package com.example.playscalajsreact.shared
object SharedMessages { object SharedMessages {
def itWorks = "It works!" def itWorks = "It works too!"
} }