statesnapshot
This commit is contained in:
parent
be847a4cd3
commit
ae1e0be7b8
1
.gitignore
vendored
1
.gitignore
vendored
@ -33,3 +33,4 @@ bin/
|
||||
|
||||
.bloop
|
||||
.metals
|
||||
.vscode
|
||||
|
19
build.sbt
19
build.sbt
@ -9,13 +9,14 @@ lazy val server = (project in file("server"))
|
||||
libraryDependencies ++= Seq(
|
||||
"com.vmunier" %% "scalajs-scripts" % "1.1.4",
|
||||
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
|
||||
EclipseKeys.preTasks := Seq(compile in Compile)
|
||||
)
|
||||
.enablePlugins(PlayScala)
|
||||
// .enablePlugins(SbtWeb)
|
||||
.enablePlugins(SbtWeb)
|
||||
.enablePlugins(WebScalaJSBundlerPlugin)
|
||||
.dependsOn(sharedJvm)
|
||||
|
||||
@ -27,9 +28,15 @@ lazy val client = (project in file("client"))
|
||||
"org.scala-js" %%% "scalajs-dom" % "1.0.0",
|
||||
"com.github.japgolly.scalajs-react" %%% "core" % "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",
|
||||
),
|
||||
scalacOptions ++= Seq("-Ymacro-annotations","-deprecation"),
|
||||
scalacOptions ++= Seq("-Ymacro-annotations", "-deprecation"),
|
||||
useYarn := true,
|
||||
stFlavour := Flavour.Japgolly,
|
||||
Compile / npmDependencies ++= Seq(
|
||||
@ -48,7 +55,11 @@ lazy val shared = crossProject(JSPlatform, JVMPlatform)
|
||||
.crossType(CrossType.Pure)
|
||||
.in(file("shared"))
|
||||
.settings(commonSettings)
|
||||
.jsConfigure(_.enablePlugins(ScalaJSWeb))
|
||||
// .jsConfigure(_.enablePlugins(ScalaJSWeb))
|
||||
.jsConfigure(
|
||||
_.enablePlugins(ScalaJSBundlerPlugin)
|
||||
// .enablePlugins(ScalablyTypedConverterPlugin)
|
||||
)
|
||||
lazy val sharedJvm = shared.jvm
|
||||
lazy val sharedJs = shared.js
|
||||
|
||||
|
@ -5,86 +5,20 @@ import org.scalajs.dom
|
||||
import japgolly.scalajs.react._
|
||||
import japgolly.scalajs.react.vdom.html_<^._
|
||||
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.StaticDsl.Route
|
||||
|
||||
import com.example.playscalajsreact.route.AppRouter
|
||||
import com.example.playscalajsreact.component.Content
|
||||
import com.example.playscalajsreact.component.Top2
|
||||
|
||||
object ScalaJSExample {
|
||||
|
||||
def main(args: Array[String]): Unit = {
|
||||
|
||||
sealed trait Page
|
||||
case object Home extends Page
|
||||
case object Hello extends Page
|
||||
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"))
|
||||
val div = dom.document.createElement("div")
|
||||
dom.document.body.appendChild(div)
|
||||
Top2().renderIntoDOM(div)
|
||||
|
||||
dom.document.getElementById("scalajsShoutOut").textContent =
|
||||
SharedMessages.itWorks
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
package com.example.playscalajsreact.model
|
||||
package com.example.playscalajsreact.component
|
||||
|
||||
// import slinky.core.annotations.react
|
||||
// import slinky.core.StatelessComponent
|
||||
@ -13,13 +13,13 @@ import japgolly.scalajs.react._
|
||||
// }
|
||||
|
||||
object HelloWorldSJSRComponent {
|
||||
import japgolly.scalajs.react.vdom.html_<^._
|
||||
import japgolly.scalajs.react.vdom.all._
|
||||
case class Props(name: String, age: Int)
|
||||
|
||||
private val component = ScalaComponent
|
||||
.builder[Props]("HelloWorldComponent")
|
||||
.render_P(p => {
|
||||
<.p(p.name + p.age)
|
||||
.render_P(props => {
|
||||
p(props.name + " " + props.age)
|
||||
})
|
||||
.build
|
||||
|
@ -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)
|
||||
}
|
@ -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))
|
||||
}
|
@ -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) }
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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, "")
|
||||
}
|
@ -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()
|
||||
}
|
@ -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
|
||||
}
|
@ -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)
|
||||
}
|
@ -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
|
||||
}
|
@ -13,7 +13,7 @@ addSbtPlugin("org.scalablytyped.converter" % "sbt-converter" % "1.0.0-beta13")
|
||||
|
||||
// Use Scala.js v1.x
|
||||
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-web-scalajs-bundler" % "0.18.0")
|
||||
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
# Home page
|
||||
GET / com.example.playscalajsreact.controllers.Application.index
|
||||
GET /index com.example.playscalajsreact.controllers.Application.index
|
||||
|
||||
# Prefix must match `play.assets.urlPrefix`
|
||||
GET /assets/*file controllers.Assets.at(file)
|
||||
|
@ -1,5 +1,5 @@
|
||||
package com.example.playscalajsreact.shared
|
||||
|
||||
object SharedMessages {
|
||||
def itWorks = "It works!"
|
||||
def itWorks = "It works too!"
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user