Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Monadic hook migration #4425

Merged
merged 3 commits into from
Dec 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 58 additions & 60 deletions explore/src/main/scala/explore/HelpBody.scala
Original file line number Diff line number Diff line change
Expand Up @@ -55,68 +55,66 @@ object HelpBody:

val themeAttr = VdomAttr("data-theme")

private val component =
ScalaFnComponent
.withHooks[Props]
.useContext(AppContext.ctx)
.useContext(HelpContext.ctx)
.useStateView(pending[String])
.useEffectOnMountBy: (props, ctx, _, state) =>
load(props.url, ctx.httpClient).flatMap(v => state.set(Pot.fromTry(v)).toAsync)
.render: (props, _, helpCtx, state) =>
val imageConv = (s: Uri) => s.host.fold(props.baseUrl.addPath(s.path))(_ => s)
private val component = ScalaFnComponent[Props]: props =>
for
ctx <- useContext(AppContext.ctx)
helpCtx <- useContext(HelpContext.ctx)
state <- useStateView(pending[String])
_ <- useEffectOnMount:
load(props.url, ctx.httpClient).flatMap(v => state.set(Pot.fromTry(v)).toAsync)
yield
val imageConv = (s: Uri) => s.host.fold(props.baseUrl.addPath(s.path))(_ => s)

val helpView = helpCtx.displayedHelp
val editUrl = state.get match {
case Pot.Ready(_) => props.editPage
case _ => props.newPage
}
val helpView = helpCtx.displayedHelp
val editUrl = state.get match {
case Pot.Ready(_) => props.editPage
case _ => props.newPage
}

React.Fragment(
React.Fragment(
<.div(ExploreStyles.HelpTitle)(
<.h4("Help"),
<.div(
ExploreStyles.HelpTitle,
<.h4("Help"),
<.div(
<.a(
Button(
icon = Icons.Edit,
severity = Button.Severity.Secondary,
onClick = helpView.set(None)
).mini.compact,
^.href := editUrl.toString(),
^.target := "_blank"
),
Button(icon = Icons.Close,
severity = Button.Severity.Secondary,
onClick = helpView.set(None)
).mini.compact
)
),
<.div(
ExploreStyles.HelpBody,
themeAttr := "light",
state.get match {
case Pot.Ready(a) =>
ReactMarkdown(
content = a,
clazz = ExploreStyles.HelpMarkdownBody,
imageConv,
remarkPlugins = List(RemarkPlugin.RemarkMath, RemarkPlugin.RemarkGFM),
rehypePlugins = List(RehypePlugin.RehypeExternalLinks, RehypePlugin.RehypeKatex)
)
case Pot.Pending =>
<.div(ExploreStyles.HelpMarkdownBody, "Loading...")
case Pot.Error(o) if o.getMessage.contains("404") =>
<.div(
ExploreStyles.HelpMarkdownBody,
"Not found, maybe you want to create it ",
<.a(^.href := props.newPage.toString(), ^.target := "_blank", Icons.Edit)
)
case Pot.Error(_) =>
<.div(
ExploreStyles.HelpMarkdownBody,
"We encountered an error trying to read the help file"
)
}
<.a(
Button(
icon = Icons.Edit,
severity = Button.Severity.Secondary,
onClick = helpView.set(None)
).mini.compact,
^.href := editUrl.toString(),
^.target := "_blank"
),
Button(
icon = Icons.Close,
severity = Button.Severity.Secondary,
onClick = helpView.set(None)
).mini.compact
)
),
<.div(
ExploreStyles.HelpBody,
themeAttr := "light",
state.get match {
case Pot.Ready(a) =>
ReactMarkdown(
content = a,
clazz = ExploreStyles.HelpMarkdownBody,
imageConv,
remarkPlugins = List(RemarkPlugin.RemarkMath, RemarkPlugin.RemarkGFM),
rehypePlugins = List(RehypePlugin.RehypeExternalLinks, RehypePlugin.RehypeKatex)
)
case Pot.Pending =>
<.div(ExploreStyles.HelpMarkdownBody, "Loading...")
case Pot.Error(o) if o.getMessage.contains("404") =>
<.div(ExploreStyles.HelpMarkdownBody)(
"Not found, maybe you want to create it ",
<.a(^.href := props.newPage.toString(), ^.target := "_blank", Icons.Edit)
)
case Pot.Error(_) =>
<.div(
ExploreStyles.HelpMarkdownBody,
"We encountered an error trying to read the help file"
)
}
)
)
97 changes: 53 additions & 44 deletions explore/src/main/scala/explore/observationtree/ObsTree.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package explore.observationtree
import cats.effect.IO
import cats.syntax.all.*
import crystal.react.*
import crystal.react.hooks.*
import eu.timepit.refined.types.numeric.NonNegShort
import eu.timepit.refined.types.string.NonEmptyString
import explore.Icons
Expand All @@ -28,6 +29,7 @@ import explore.undo.UndoSetter
import explore.undo.Undoer
import explore.utils.*
import japgolly.scalajs.react.*
import japgolly.scalajs.react.hooks.Hooks.UseRef
import japgolly.scalajs.react.vdom.html_<^.*
import lucuma.core.enums.ObservationWorkflowState
import lucuma.core.enums.ScienceBand
Expand Down Expand Up @@ -172,52 +174,59 @@ object ObsTree:
}

private val component =
ScalaFnComponent
.withHooks[Props]
.useContext(AppContext.ctx)
.useRefBy: (props, _) => // groupInfo: Saved index into the observation tree
props.focusedGroupInfo
.useEffectWithDepsBy((props, _, _) => // refocus if current focus ceases to exist
(props.focusedObsOrGroup, props.observations.get, props.groups.get)
): (props, ctx, prevGroupInfo) =>
(focusedObsOrGroup, obsList, groupList) =>
focusedObsOrGroup.fold(prevGroupInfo.set(none)): elemId =>
(prevGroupInfo.value, elemId.fold(obsList.contains(_), groupList.contains(_))) match
case (_, true) =>
prevGroupInfo.set(props.focusedGroupInfo)
case (Some(prev), false) => // Previously focused element no longer exists
val prevGroup: Option[Group.Id] = prev._1
val prevIndex: NonNegShort = prev._2
val newElement: Option[Either[Observation, Group]] =
props.groupsChildren
.get(prevGroup)
.flatMap:
_.get(math.max(0, prevIndex.value - 1))
.orElse:
prevGroup.flatMap(groupList.get(_).map(_.asRight))

prevGroupInfo.set:
newElement.flatMap(e => props.groupInfo(e.bimap(_.id, _.id)))
>>
newElement
.fold(focusObs(props.programId, none, ctx)):
_.fold(
obs => focusObs(props.programId, obs.id.some, ctx),
group => focusGroup(props.programId, group.id.some, ctx)
)
case _ => Callback.empty
// Scroll to newly created/selected observation
.useEffectWithDepsBy((props, _, _) => props.focusedObs): (_, _, _) =>
focusedObs => focusedObs.map(scrollIfNeeded).getOrEmpty
// Open the group (and all super-groups) of the focused observation
.useEffectWithDepsBy((props, _, _) => props.activeGroup): (props, _, _) =>
_.map: activeGroupId =>
props.expandedGroups.mod(_ ++ props.parentGroups(activeGroupId.asRight) + activeGroupId)
.orEmpty
.render: (props, ctx, _) =>
ScalaFnComponent[Props]: props =>
// refocus if current focus ceases to exist
inline def refocus(
prevGroupInfo: UseRef[Option[(Option[Group.Id], NonNegShort)]],
ctx: AppContext[IO]
): HookResult[Unit] =
useEffectWithDeps((props.focusedObsOrGroup, props.observations.get, props.groups.get)):
(focusedObsOrGroup, obsList, groupList) =>
focusedObsOrGroup.fold(prevGroupInfo.set(none)): elemId =>
(prevGroupInfo.value, elemId.fold(obsList.contains(_), groupList.contains(_))) match
case (_, true) =>
prevGroupInfo.set(props.focusedGroupInfo)
case (Some(prev), false) => // Previously focused element no longer exists
val prevGroup: Option[Group.Id] = prev._1
val prevIndex: NonNegShort = prev._2
val newElement: Option[Either[Observation, Group]] =
props.groupsChildren
.get(prevGroup)
.flatMap:
_.get(math.max(0, prevIndex.value - 1))
.orElse:
prevGroup.flatMap(groupList.get(_).map(_.asRight))

prevGroupInfo.set:
newElement.flatMap(e => props.groupInfo(e.bimap(_.id, _.id)))
>>
newElement
.fold(focusObs(props.programId, none, ctx)):
_.fold(
obs => focusObs(props.programId, obs.id.some, ctx),
group => focusGroup(props.programId, group.id.some, ctx)
)
case _ => Callback.empty

for
ctx <- useContext(AppContext.ctx)
// Saved index into the observation tree
prevGroupInfo <- useRef(props.focusedGroupInfo)
// refocus if current focus ceases to exist
_ <- refocus(prevGroupInfo, ctx)
adding <- useStateView(AddingObservation(false))
// Scroll to newly created/selected observation
_ <- useEffectWithDeps(props.focusedObs): focusedObs =>
focusedObs.map(scrollIfNeeded).getOrEmpty
// Open the group (and all super-groups) of the focused observation
_ <- useEffectWithDeps(props.activeGroup):
_.map: activeGroupId =>
props.expandedGroups.mod:
_ ++ props.parentGroups(activeGroupId.asRight) + activeGroupId
.orEmpty
yield
import ctx.given

val adding: View[AddingObservation] = props.addingObservation
val expandedGroups: View[Set[Tree.Id]] = props.expandedGroups.as(groupTreeIdLens)

def onDragDrop(e: Tree.DragDropEvent[Either[Observation, Group]]): Callback =
Expand Down
10 changes: 5 additions & 5 deletions project/Versions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ object Versions {
val circeGolden = "0.3.0"
val coulomb = "0.8.0"
val clue = "0.40.0"
val crystal = "0.46.0"
val crystal = "0.47.0"
val discipline = "1.7.0"
val disciplineMUnit = "2.0.0"
val fs2 = "3.11.0"
Expand All @@ -24,22 +24,22 @@ object Versions {
val lucumaCore = "0.112.1"
val lucumaCatalog = "0.48.12"
val lucumaITC = "0.24.3"
val lucumaReact = "0.74.0"
val lucumaReact = "0.76.0"
val lucumaRefined = "0.1.3"
val lucumaSchemas = "0.110.3"
val lucumaOdbSchema = "0.17.3"
val lucumaSSO = "0.7.2"
val lucumaUI = "0.125.0"
val lucumaUI = "0.126.0"
val monocle = "3.3.0"
val mouse = "1.3.2"
val mUnit = "1.0.3"
val mUnitScalacheck = "1.0.0"
val mUnitCatsEffect = "2.0.0"
val reactAladin = "0.32.3"
val reactAladin = "0.33.0"
val refinedAlgebra = "0.1.1"
val sbtBuildInfo = "0.13.1"
val sbtLucuma = "0.12.4"
val scalaCollectionContrib = "0.4.0"
val scalaJsDom = "2.8.0"
val scalaJsReact = "3.0.0-beta7"
val scalaJsReact = "3.0.0-beta8"
}
Loading