Skip to content

Commit

Permalink
New crystal with contexts.
Browse files Browse the repository at this point in the history
  • Loading branch information
rpiaggio committed Apr 3, 2021
1 parent 4cd368a commit 583be8b
Show file tree
Hide file tree
Showing 37 changed files with 315 additions and 302 deletions.
33 changes: 17 additions & 16 deletions common/src/main/scala/explore/AppMain.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import cats.effect.IOApp
import cats.syntax.all._
import clue.WebSocketReconnectionStrategy
import clue.js.WebSocketJSBackend
import crystal.AppRootContext
import crystal.react.AppRoot
import explore.components.ui.ExploreStyles
import explore.model.AppConfig
import explore.model.AppContext
Expand Down Expand Up @@ -42,12 +40,14 @@ import scala.concurrent.duration._
import scala.scalajs.js

import js.annotation._

object AppCtx extends AppRootContext[AppContextIO]
import japgolly.scalajs.react.Reusability
import crystal.react._

trait AppMain extends IOApp {
LogLevelLogger.setLevel(LogLevelLogger.Level.INFO)

implicit val reuseContext: Reusability[AppContextIO] = Reusability.never

implicit val logger: Logger[IO] = LogLevelLogger.createForRoot[IO]

implicit val gqlStreamingBackend: WebSocketJSBackend[IO] = WebSocketJSBackend[IO]
Expand Down Expand Up @@ -136,19 +136,20 @@ trait AppMain extends IOApp {
}

(for {
_ <- utils.setupScheme[IO](Theme.Dark)
appConfig <- fetchConfig
_ <- logger.info(s"Git Commit: [${BuildInfo.gitHeadCommit.getOrElse("NONE")}]")
_ <- logger.info(s"Config: ${appConfig.show}")
ctx <- AppContext.from[IO](appConfig, reconnectionStrategy, pageUrl, IO.fromFuture)
r <- (ctx.sso.whoami,
setupDOM(),
showEnvironment(appConfig.environment),
AppCtx.initIn[IO](ctx)
).parTupled
(vault, container, _, _) = r
_ <- utils.setupScheme[IO](Theme.Dark)
appConfig <- fetchConfig
_ <- logger.info(s"Git Commit: [${BuildInfo.gitHeadCommit.getOrElse("NONE")}]")
_ <- logger.info(s"Config: ${appConfig.show}")
ctx <- AppContext.from[IO](appConfig, reconnectionStrategy, pageUrl, IO.fromFuture)
r <- (ctx.sso.whoami, setupDOM(), showEnvironment(appConfig.environment)).parTupled
(vault, container, _) = r
} yield {
val RootComponent = AppRoot[IO](initialModel(vault))(rootView => rootComponent(rootView))
val RootComponent =
ContextProvider[IO](AppCtx, ctx)(
ContextProvider[IO](HelpCtx, HelpContext(none))(
StateProvider[IO](initialModel(vault))(rootView => rootComponent(rootView))
)
)

RootComponent().renderIntoDOM(container)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import explore.model.AirMassRange
import explore.model.ConstraintsSummary
import explore.model.ObsSummary
import explore.model.reusability._
import japgolly.scalajs.react.Reusability
import japgolly.scalajs.react._
import japgolly.scalajs.react.vdom.html_<^._
import lucuma.core.model.ConstraintSet
import lucuma.core.model.Observation
Expand Down Expand Up @@ -197,14 +197,9 @@ object ConstraintSetObsQueries {
implicit val constraintSetWithObsReusability: Reusability[ConstraintSetsWithObs] =
Reusability.derive

type LiveQueryRenderer =
(
View[ConstraintSetsWithObs] => VdomNode
) => LiveQueryRenderMod[ObservationDB, ConstraintSetsObsQuery.Data, ConstraintSetsWithObs]

val ConstraintSetObsLiveQuery: LiveQueryRenderer =
render =>
AppCtx.runWithCtx { implicit appCtx =>
val ConstraintSetObsLiveQuery =
ScalaFnComponent[View[ConstraintSetsWithObs] => VdomNode](render =>
AppCtx.using { implicit appCtx =>
LiveQueryRenderMod[ObservationDB, ConstraintSetsObsQuery.Data, ConstraintSetsWithObs](
ConstraintSetsObsQuery.query(),
ConstraintSetsObsQuery.Data.asConstraintSetsWithObs.get,
Expand All @@ -214,5 +209,6 @@ object ConstraintSetObsQueries {
)
)(render)
}
)

}
13 changes: 5 additions & 8 deletions common/src/main/scala/explore/common/ObsQueries.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import explore.data.KeyedIndexedList
import explore.implicits._
import explore.model.ObsSummary
import explore.model.reusability._
import japgolly.scalajs.react._
import japgolly.scalajs.react.vdom.html_<^._
import lucuma.core.model.Observation
import lucuma.ui.reusability._
Expand Down Expand Up @@ -74,14 +75,9 @@ object ObsQueries {
"""
}

type LiveQueryRenderer =
(
View[ObservationList] => VdomNode
) => LiveQueryRenderMod[ObservationDB, ProgramObservationsQuery.Data, ObservationList]

val ObsLiveQuery: LiveQueryRenderer =
render =>
AppCtx.runWithCtx { implicit appCtx =>
val ObsLiveQuery =
ScalaFnComponent[View[ObservationList] => VdomNode](render =>
AppCtx.using { implicit appCtx =>
LiveQueryRenderMod[ObservationDB, ProgramObservationsQuery.Data, ObservationList](
ProgramObservationsQuery.query(),
ProgramObservationsQuery.Data.asObservationList.get,
Expand All @@ -91,6 +87,7 @@ object ObsQueries {
)
)(render)
}
)

@GraphQL
object ProgramCreateObservation extends GraphQLOperation[ObservationDB] {
Expand Down
14 changes: 5 additions & 9 deletions common/src/main/scala/explore/common/TargetObsQueries.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import io.circe.Decoder
import io.circe.HCursor
import io.circe.generic.semiauto._
import io.circe.refined._
import japgolly.scalajs.react.Reusability
import japgolly.scalajs.react._
import japgolly.scalajs.react.vdom.html_<^._
import lucuma.core.model.Asterism
import lucuma.core.model.Observation
Expand Down Expand Up @@ -314,14 +314,9 @@ object TargetObsQueries {
implicit val targetsWithObsReusability: Reusability[TargetsAndAsterismsWithObs] =
Reusability.derive

type LiveQueryRenderer =
(
View[TargetsAndAsterismsWithObs] => VdomNode
) => LiveQueryRenderMod[ObservationDB, TargetsObsQuery.Data, TargetsAndAsterismsWithObs]

val TargetObsLiveQuery: LiveQueryRenderer =
render =>
AppCtx.runWithCtx { implicit appCtx =>
val TargetObsLiveQuery =
ScalaFnComponent[View[TargetsAndAsterismsWithObs] => VdomNode](render =>
AppCtx.using { implicit appCtx =>
LiveQueryRenderMod[ObservationDB, TargetsObsQuery.Data, TargetsAndAsterismsWithObs](
TargetsObsQuery.query(),
TargetsObsQuery.Data.asTargetsWithObs.get,
Expand All @@ -333,4 +328,5 @@ object TargetObsQueries {
)
)(render)
}
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ object ConnectionsStatus {
val component = ScalaComponent
.builder[Props]
.render(_ =>
AppCtx.runWithCtx { ctx =>
AppCtx.using { ctx =>
ctx.clients.ODBConnectionStatus(renderStatus("ODB"))
}
)
Expand Down
2 changes: 1 addition & 1 deletion common/src/main/scala/explore/components/InputModal.scala
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ object InputModal {
.builder[Props]
.initialStateFromProps(p => State(p.initialValue.fold("")(_.value)))
.renderPS { ($, props, state) =>
AppCtx.runWithCtx { implicit appCtx =>
AppCtx.using { implicit appCtx =>
val valueView = ViewF.fromState[IO]($).zoom(State.inputValue)

val cleanInput = $.setStateL(State.inputValue)("")
Expand Down
14 changes: 5 additions & 9 deletions common/src/main/scala/explore/components/UserSelectionForm.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,10 @@ final case class UserSelectionForm(
vault: View[Option[UserVault]],
message: View[Option[NonEmptyString]]
) extends ReactProps[UserSelectionForm](UserSelectionForm.component) {
def guest: Callback =
AppCtx.runWithCtx { implicit ctx =>
ctx.sso.guest.flatMap(v => vault.set(v.some)).runAsyncCB
}
def login: Callback =
AppCtx.runWithCtx { implicit ctx =>
ctx.sso.redirectToLogin.runAsyncCB
}
def guest(implicit ctx: AppContextIO): Callback =
ctx.sso.guest.flatMap(v => vault.set(v.some)).runAsyncCB
def login(implicit ctx: AppContextIO): Callback =
ctx.sso.redirectToLogin.runAsyncCB

def supportedOrcidBrowser: CallbackTo[(Boolean, Boolean)] = CallbackTo[(Boolean, Boolean)] {
val browser = new UAParser(dom.window.navigator.userAgent).getBrowser()
Expand Down Expand Up @@ -68,7 +64,7 @@ object UserSelectionForm {
p.supportedOrcidBrowser.map(Function.tupled(State.apply _))
}
.render_PS { (p, s) =>
AppCtx.runWithCtx { implicit ctx =>
AppCtx.using { implicit ctx =>
Modal(
size = ModalSize.Large,
clazz = ExploreStyles.LoginBox,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import cats.syntax.all._
import crystal.react.implicits._
import eu.timepit.refined.cats._
import eu.timepit.refined.types.string.NonEmptyString
import explore.AppCtx
import explore.implicits._
import explore.model.Clients
import io.circe.Json
Expand All @@ -22,7 +21,8 @@ import react.semanticui.sizes._

final case class ConnectionManager(ssoToken: NonEmptyString, onConnect: IO[Unit])(
val render: () => VdomNode
) extends ReactProps[ConnectionManager](ConnectionManager.component)
)(implicit val ctx: AppContextIO)
extends ReactProps[ConnectionManager](ConnectionManager.component)

object ConnectionManager {
type Props = ConnectionManager
Expand Down Expand Up @@ -55,24 +55,21 @@ object ConnectionManager {
.builder[Props]
.initialState(State())
.renderBackend[Backend]
.componentDidMount($ =>
AppCtx.runWithCtx { implicit ctx =>
$.backend.onMount(ctx.clients).runAsyncCB
}
)
.componentDidUpdate($ =>
AppCtx.runWithCtx { implicit ctx =>
(Logger[IO].debug(s"[ConnectionManager] Token changed. Refreshing connections.") >>
$.backend.refresh(ctx.clients))
.whenA($.prevProps.ssoToken =!= $.currentProps.ssoToken)
.runAsyncCB
}
)
.componentWillUnmountConst(
AppCtx.runWithCtx { implicit ctx =>
(Logger[IO].debug(s"[ConnectionManager] Terminating connections.") >>
ctx.clients.close()).runAsyncCB
}
)
.componentDidMount { $ =>
implicit val ctx = $.props.ctx
$.backend.onMount(ctx.clients).runAsyncCB
}
.componentDidUpdate { $ =>
implicit val ctx = $.currentProps.ctx
(Logger[IO].debug(s"[ConnectionManager] Token changed. Refreshing connections.") >>
$.backend.refresh(ctx.clients))
.whenA($.prevProps.ssoToken =!= $.currentProps.ssoToken)
.runAsyncCB
}
.componentWillUnmount { $ =>
implicit val ctx = $.props.ctx
(Logger[IO].debug(s"[ConnectionManager] Terminating connections.") >>
ctx.clients.close()).runAsyncCB
}
.build
}
30 changes: 15 additions & 15 deletions common/src/main/scala/explore/components/state/IfLogged.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,30 +23,30 @@ object IfLogged {
type Props = IfLogged

// Creates a "profile" for user preferences.
private def createUserPrefs(vault: UserVault): IO[Unit] =
AppCtx.flatMap(implicit ctx =>
UserInsertMutation.execute(Input(vault.user.id.toString)).start.void
)
private def createUserPrefs(vault: UserVault)(implicit ctx: AppContextIO): IO[Unit] =
UserInsertMutation.execute(Input(vault.user.id.toString)).start.void

private val component =
ScalaComponent
.builder[IfLogged]
.stateless
.render_P { p =>
val vaultView = p.view.zoom(RootModel.vault)
val messageView = p.view.zoom(RootModel.userSelectionMessage)
AppCtx.using { implicit ctx =>
val vaultView = p.view.zoom(RootModel.vault)
val messageView = p.view.zoom(RootModel.userSelectionMessage)

vaultView.get.fold[VdomElement](
UserSelectionForm(vaultView, messageView)
) { vault =>
React.Fragment(
SSOManager(vault.expiration, vaultView.set, messageView.set.compose(_.some)),
ConnectionManager(vault.token, onConnect = createUserPrefs(vault))(() =>
LogoutTracker(vaultView.set, messageView.set.compose(_.some))(onLogout =>
p.render(vault, onLogout)
vaultView.get.fold[VdomElement](
UserSelectionForm(vaultView, messageView)
) { vault =>
React.Fragment(
SSOManager(vault.expiration, vaultView.set, messageView.set.compose(_.some)),
ConnectionManager(vault.token, onConnect = createUserPrefs(vault))(() =>
LogoutTracker(vaultView.set, messageView.set.compose(_.some))(onLogout =>
p.render(vault, onLogout)
)
)
)
)
}
}
}
.build
Expand Down
28 changes: 13 additions & 15 deletions common/src/main/scala/explore/components/state/LogoutTracker.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import cats.syntax.all._
import crystal.react.implicits._
import eu.timepit.refined.auto._
import eu.timepit.refined.types.string.NonEmptyString
import explore.AppCtx
import explore.implicits._
import explore.model.UserVault
import explore.utils.ExploreEvent
Expand All @@ -21,7 +20,7 @@ import react.common.ReactProps
final case class LogoutTracker(
setVault: Option[UserVault] => IO[Unit],
setMessage: NonEmptyString => IO[Unit]
)(val render: IO[Unit] => VdomNode)
)(val render: IO[Unit] => VdomNode)(implicit val ctx: AppContextIO)
extends ReactProps[LogoutTracker](LogoutTracker.component)

object LogoutTracker {
Expand All @@ -42,19 +41,18 @@ object LogoutTracker {
)
}
.componentDidMount { $ =>
AppCtx.runWithCtx { implicit ctx =>
IO {
val bc = new BroadcastChannel[ExploreEvent]("explore")
bc.onmessage = (x: ExploreEvent) =>
// This is coming from the js world, we can't match the type
(x.event match {
case ExploreEvent.Logout.event =>
$.props.setVault(none) >> $.props.setMessage("You logged out in another instance")
case _ => IO.unit
})
bc
}.flatMap(bc => $.modStateIn[IO](State.bc.set(bc.some))).runAsyncCB
}
implicit val ctx = $.props.ctx
IO {
val bc = new BroadcastChannel[ExploreEvent]("explore")
bc.onmessage = (x: ExploreEvent) =>
// This is coming from the js world, we can't match the type
(x.event match {
case ExploreEvent.Logout.event =>
$.props.setVault(none) >> $.props.setMessage("You logged out in another instance")
case _ => IO.unit
})
bc
}.flatMap(bc => $.modStateIn[IO](State.bc.set(bc.some))).runAsyncCB
}
.componentWillUnmount($ =>
$.state.bc.map(bc => IO(bc.close()).attempt.void).orEmpty.runAsyncAndForgetCB
Expand Down
Loading

0 comments on commit 583be8b

Please sign in to comment.