Rohan Sircar
4 years ago
commit
a672513915
25 changed files with 4852 additions and 0 deletions
-
6.gitignore
-
1.scalafmt.conf
-
27README.md
-
24assets/index.css
-
50build.sbt
-
19index.html
-
11project/ScalacOptions.scala
-
1project/build.properties
-
6project/plugins.sbt
-
3src/main/resources/application.conf
-
95src/main/scala/com/example/playscalajsreact/ScalaJSExample.scala
-
63src/main/scala/com/example/playscalajsreact/component/Content.scala
-
29src/main/scala/com/example/playscalajsreact/component/HelloWorldComponent.scala
-
25src/main/scala/com/example/playscalajsreact/component/IntEditor.scala
-
54src/main/scala/com/example/playscalajsreact/component/MenuComponent.scala
-
58src/main/scala/com/example/playscalajsreact/component/Middle.scala
-
47src/main/scala/com/example/playscalajsreact/component/NameChanger.scala
-
170src/main/scala/com/example/playscalajsreact/component/Top.scala
-
19src/main/scala/com/example/playscalajsreact/model/Data.scala
-
19src/main/scala/com/example/playscalajsreact/model/GlobalState.scala
-
53src/main/scala/com/example/playscalajsreact/model/SnapshotTest.scala
-
13src/main/scala/com/example/playscalajsreact/model/User.scala
-
61src/main/scala/com/example/playscalajsreact/route/AppRouter.scala
-
15src/main/scala/com/example/playscalajsreact/route/Page.scala
-
3983yarn.lock
@ -0,0 +1,6 @@ |
|||
target |
|||
.metals |
|||
.bloop |
|||
.vscode |
|||
.idea |
|||
metals.sbt |
@ -0,0 +1 @@ |
|||
version = "2.6.3" |
@ -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 |
@ -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%; |
|||
} |
@ -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() |
@ -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> |
@ -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" |
|||
) |
|||
} |
@ -0,0 +1 @@ |
|||
sbt.version=1.3.12 |
@ -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") |
@ -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 |
|||
} |
@ -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("http://localhost:8080/"), 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 |
|||
} |
3983
yarn.lock
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
Write
Preview
Loading…
Cancel
Save
Reference in new issue