statesnapshot
This commit is contained in:
parent
be847a4cd3
commit
ae1e0be7b8
1
.gitignore
vendored
1
.gitignore
vendored
@ -33,3 +33,4 @@ bin/
|
|||||||
|
|
||||||
.bloop
|
.bloop
|
||||||
.metals
|
.metals
|
||||||
|
.vscode
|
||||||
|
17
build.sbt
17
build.sbt
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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.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
|
||||||
|
|
@ -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
|
// 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")
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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!"
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user