Browse Source

First commit

master
Rohan Sircar 1 year ago
commit
a672513915
  1. 6
      .gitignore
  2. 1
      .scalafmt.conf
  3. 27
      README.md
  4. 24
      assets/index.css
  5. 50
      build.sbt
  6. 19
      index.html
  7. 11
      project/ScalacOptions.scala
  8. 1
      project/build.properties
  9. 6
      project/plugins.sbt
  10. 3
      src/main/resources/application.conf
  11. 95
      src/main/scala/com/example/playscalajsreact/ScalaJSExample.scala
  12. 63
      src/main/scala/com/example/playscalajsreact/component/Content.scala
  13. 29
      src/main/scala/com/example/playscalajsreact/component/HelloWorldComponent.scala
  14. 25
      src/main/scala/com/example/playscalajsreact/component/IntEditor.scala
  15. 54
      src/main/scala/com/example/playscalajsreact/component/MenuComponent.scala
  16. 58
      src/main/scala/com/example/playscalajsreact/component/Middle.scala
  17. 47
      src/main/scala/com/example/playscalajsreact/component/NameChanger.scala
  18. 170
      src/main/scala/com/example/playscalajsreact/component/Top.scala
  19. 19
      src/main/scala/com/example/playscalajsreact/model/Data.scala
  20. 19
      src/main/scala/com/example/playscalajsreact/model/GlobalState.scala
  21. 53
      src/main/scala/com/example/playscalajsreact/model/SnapshotTest.scala
  22. 13
      src/main/scala/com/example/playscalajsreact/model/User.scala
  23. 61
      src/main/scala/com/example/playscalajsreact/route/AppRouter.scala
  24. 15
      src/main/scala/com/example/playscalajsreact/route/Page.scala
  25. 3983
      yarn.lock

6
.gitignore

@ -0,0 +1,6 @@
target
.metals
.bloop
.vscode
.idea
metals.sbt

1
.scalafmt.conf

@ -0,0 +1 @@
version = "2.6.3"

27
README.md

@ -0,0 +1,27 @@
# Scalajs-react independent demo
This is a demo of an independent scalajs sbt project consisting of only the frontend, that communicates with the backend from the [play-slick-demo project](https://git.arcusiridis.com/nova/Scala-Play-Slick-Demo) -
This is useful because configuring play to serve frontend files is a hassle, libray bundling mode refused to work for me in the integrated project whereas here it's trivial. Library bundling mode makes incremental compilation really fast( ~2 seconds ). This also speeds up incremental compilation of the backend project.
I have ported over all the code from my previous scalajs-react project, and this is the only I will be using primarily from now.
Other features are -
1. HOCON configuration to make backend url conifgurable
2. Scalajs-react with routing for SPA
3. Effect wrappers (cats IO/monix Task) (TODO)
## Usage
Steps -
1. Clone this project
2. Clone [backend project](https://git.arcusiridis.com/nova/Scala-Play-Slick-Demo)
3. Run the [backend project](https://git.arcusiridis.com/nova/Scala-Play-Slick-Demo)
4. Configure the url for the backend project in this project 's application.conf file
5. Run this project with -
```
sbt "fastOptJS::startWebpackDevServer; ~fastOptJS" shell
```
This starts the webpack dev server, the scalajs-bundler to bundle js dependencies, and js file continuous compilation
6. Open `http://localhost:8080` to see it in action

24
assets/index.css

@ -0,0 +1,24 @@
* {
margin: 0;
}
.container {
font-family: Arial, Helvetica, sans-serif;
text-align: center;
margin: unset;
}
.nav {
background: linear-gradient(90deg, #04adad 0%, #00bdff);
color: #fff;
padding: 1.5em;
}
.nav .title {
font-size: 2em;
}
#timer {
font-size: 7em;
margin-top: 20%;
}

50
build.sbt

@ -0,0 +1,50 @@
enablePlugins(ScalaJSPlugin)
enablePlugins(ScalaJSBundlerPlugin)
enablePlugins(ScalablyTypedConverterPlugin)
enablePlugins(ShoconPlugin)
// enablePlugins(SbtTwirl)
name := "scalajs-standalone"
scalaVersion := "2.13.2"
// This is an application with a main method
scalaJSUseMainModuleInitializer := true
// mainClass in Compile := Some("Index.scala")
scalacOptions ++= ScalacOptions.flags
libraryDependencies ++= Seq(
"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-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",
"org.typelevel" %%% "cats-core" % "2.1.1",
"org.typelevel" %%% "cats-effect" % "2.1.4",
"io.monix" %%% "monix" % "3.2.2",
// "com.github.japgolly.scalajs-react" %%% "test" % "1.7.0",
"org.akka-js" %%% "shocon" % "1.0.0",
"com.typesafe.play" %%% "play-json" % "2.9.0"
)
useYarn := true
stFlavour := Flavour.Japgolly
Compile / npmDependencies ++= Seq(
"react" -> "16.13.1",
"react-dom" -> "16.13.1",
"@types/react" -> "16.9.34",
"@types/react-dom" -> "16.9.6"
)
compile in Compile := (compile in Compile).dependsOn(shoconConcat).value
webpackDevServerExtraArgs in fastOptJS ++= Seq(
"--content-base",
(baseDirectory in ThisBuild).value.getAbsolutePath
)
webpackDevServerExtraArgs := Seq("--inline")
webpackBundlingMode := BundlingMode.LibraryOnly()

19
index.html

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Scala JS App</title>
<link rel="stylesheet" href="./assets/index.css">
</head>
<body>
<div id="root"></div>
<!-- <script type="text/javascript" src="./target/scala-2.13/scala-js-test-app-fastopt.js"></script> -->
<!-- <script src="./target/scala-2.13/scalajs-bundler/main/scalajs-standalone-fastopt-bundle.js"></script> -->
<script src="./target/scala-2.13/scalajs-bundler/main/scalajs-standalone-fastopt-library.js"></script>
<script src="./target/scala-2.13/scalajs-bundler/main/scalajs-standalone-fastopt-loader.js"></script>
<script src="./target/scala-2.13/scalajs-bundler/main/scalajs-standalone-fastopt.js"></script>
</body>
</html>

11
project/ScalacOptions.scala

@ -0,0 +1,11 @@
object ScalacOptions {
val flags = Seq(
"-deprecation", // Emit warning and location for usages of deprecated APIs.
"-encoding",
"utf-8", // Specify character encoding used by source files.
"-explaintypes", // Explain type errors in more detail.
"-feature", // Emit warning and location for usages of features that should be imported explicitly.
"-unchecked", // Enable additional warnings where generated code depends on assumptions.
"-Ymacro-annotations"
)
}

1
project/build.properties

@ -0,0 +1 @@
sbt.version=1.3.12

6
project/plugins.sbt

@ -0,0 +1,6 @@
resolvers += Resolver.bintrayRepo("oyvindberg", "converter")
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.1.0")
addSbtPlugin("ch.epfl.scala" % "sbt-scalajs-bundler" % "0.18.0")
addSbtPlugin("org.scalablytyped.converter" % "sbt-converter" % "1.0.0-beta24")
addSbtPlugin("org.akka-js" % "sbt-shocon" % "1.0.0")

3
src/main/resources/application.conf

@ -0,0 +1,3 @@
app : {
backendUrl: "http://localhost:5080"
}

95
src/main/scala/com/example/playscalajsreact/ScalaJSExample.scala

@ -0,0 +1,95 @@
package com.example.playscalajsreact
import org.scalajs.dom
import japgolly.scalajs.react._
import japgolly.scalajs.react.vdom.html_<^._
import japgolly.scalajs.react.extra._
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
import com.typesafe.config.ConfigFactory
import scala.scalajs.js.JSON
import scala.concurrent.ExecutionContext.Implicits._
import scala.util.Success
import play.api.libs.json.Json
import com.example.playscalajsreact.model.User
import typings.std.stdStrings.auth
case class Response(status: Int, response: String)
case class UserForm(name: String)
object UserForm {
implicit val userFormFormat = Json.format[UserForm]
}
object ScalaJSExample {
def main(args: Array[String]): Unit = {
val conf = ConfigFactory.load()
val backendBaseUrl = conf.getString("app.backendUrl")
val userRoute = s"$backendBaseUrl/user"
val authorRoute = (id: Int) => s"$backendBaseUrl/authors/$id"
println(backendBaseUrl)
val x = Ajax
.get(s"https://jsonplaceholder.typicode.com/todos/1")
.send
.validateStatusIs(200)(Callback.throwException)
.asAsyncCallback
.map(xhr => xhr.responseText)
.logResult
val y = Ajax
.get(userRoute)
.send
.validateStatusIs(200)(Callback.throwException)
.asAsyncCallback
.map(xhr =>
Json
.fromJson[UserForm](Json.parse(xhr.responseText))
.getOrElse(UserForm("empty1"))
)
.logResult
// CallbackTo.
val getAuthors = Ajax
.get(authorRoute(1))
.send
.asAsyncCallback
.map(xhr => xhr.responseText)
.logResult
(x >> y >> getAuthors).unsafeToFuture()
// x.>>=
val domCallback = for {
user <- y
authors <- getAuthors
} yield { <.div(user.name + authors) }
val div = dom.document.createElement("div")
dom.document.body.appendChild(div)
Top2().renderIntoDOM(div)
val div2 = dom.document.createElement("div")
dom.document.body.appendChild(div2)
def myDom(user: UserForm): VdomElement = <.div(user.name)
val z = y.map(myDom)
val Main = React.Suspense(
fallback = <.div(
^.color := "#33c",
^.fontSize := "150%",
"AJAXer in progress. Loading..."
),
asyncBody = domCallback
)
<.div(Main)
Main.renderIntoDOM(div2)
}
}

63
src/main/scala/com/example/playscalajsreact/component/Content.scala

@ -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()
}

29
src/main/scala/com/example/playscalajsreact/component/HelloWorldComponent.scala

@ -0,0 +1,29 @@
package com.example.playscalajsreact.component
// import slinky.core.annotations.react
// import slinky.core.StatelessComponent
// import slinky.web.html.h1
import japgolly.scalajs.react._
// @react class HelloWorldComponentSlinky extends StatelessComponent {
// case class Props(name: String, age: Int)
// def render = {
// h1(s"Hello ${props.name} ${props.age}")
// }
// }
object HelloWorldSJSRComponent {
import japgolly.scalajs.react.vdom.all._
case class Props(name: String, age: Int)
private val component = ScalaComponent
.builder[Props]("HelloWorldComponent")
.render_P(props => {
p(props.name + " " + props.age)
})
.build
def apply(name: String, age: Int) = {
component(Props(name, age))
}
}

25
src/main/scala/com/example/playscalajsreact/component/IntEditor.scala

@ -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)
}

54
src/main/scala/com/example/playscalajsreact/component/MenuComponent.scala

@ -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))
}

58
src/main/scala/com/example/playscalajsreact/component/Middle.scala

@ -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, "hello"),
<.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) }
}

47
src/main/scala/com/example/playscalajsreact/component/NameChanger.scala

@ -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
}

170
src/main/scala/com/example/playscalajsreact/component/Top.scala

@ -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
}

19
src/main/scala/com/example/playscalajsreact/model/Data.scala

@ -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)
}
}

19
src/main/scala/com/example/playscalajsreact/model/GlobalState.scala

@ -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, "")
}

53
src/main/scala/com/example/playscalajsreact/model/SnapshotTest.scala

@ -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()
}

13
src/main/scala/com/example/playscalajsreact/model/User.scala

@ -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
}

61
src/main/scala/com/example/playscalajsreact/route/AppRouter.scala

@ -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("http://localhost:8080/"), AppRouter.routerConfig)
}

15
src/main/scala/com/example/playscalajsreact/route/Page.scala

@ -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
}

3983
yarn.lock
File diff suppressed because it is too large
View File

Loading…
Cancel
Save