First commit
This commit is contained in:
commit
a672513915
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
target
|
||||
.metals
|
||||
.bloop
|
||||
.vscode
|
||||
.idea
|
||||
metals.sbt
|
1
.scalafmt.conf
Normal file
1
.scalafmt.conf
Normal file
@ -0,0 +1 @@
|
||||
version = "2.6.3"
|
27
README.md
Normal file
27
README.md
Normal file
@ -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
Normal file
24
assets/index.css
Normal file
@ -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
Normal file
50
build.sbt
Normal file
@ -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
Normal file
19
index.html
Normal file
@ -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
Normal file
11
project/ScalacOptions.scala
Normal file
@ -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
Normal file
1
project/build.properties
Normal file
@ -0,0 +1 @@
|
||||
sbt.version=1.3.12
|
6
project/plugins.sbt
Normal file
6
project/plugins.sbt
Normal file
@ -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
Normal file
3
src/main/resources/application.conf
Normal file
@ -0,0 +1,3 @@
|
||||
app : {
|
||||
backendUrl: "http://localhost:5080"
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
@ -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, "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) }
|
||||
}
|
@ -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
Normal file
170
src/main/scala/com/example/playscalajsreact/component/Top.scala
Normal 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
|
||||
}
|
19
src/main/scala/com/example/playscalajsreact/model/Data.scala
Normal file
19
src/main/scala/com/example/playscalajsreact/model/Data.scala
Normal 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)
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
13
src/main/scala/com/example/playscalajsreact/model/User.scala
Normal file
13
src/main/scala/com/example/playscalajsreact/model/User.scala
Normal 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
|
||||
}
|
@ -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
Normal file
15
src/main/scala/com/example/playscalajsreact/route/Page.scala
Normal 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
|
||||
}
|
Loading…
Reference in New Issue
Block a user