diff --git a/explore/src/main/scala/explore/HelpBody.scala b/explore/src/main/scala/explore/HelpBody.scala index 3843f1a1a..4bf1ec5dd 100644 --- a/explore/src/main/scala/explore/HelpBody.scala +++ b/explore/src/main/scala/explore/HelpBody.scala @@ -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" + ) + } ) + ) diff --git a/explore/src/main/scala/explore/observationtree/ObsTree.scala b/explore/src/main/scala/explore/observationtree/ObsTree.scala index 36de507ec..1891ec0c9 100644 --- a/explore/src/main/scala/explore/observationtree/ObsTree.scala +++ b/explore/src/main/scala/explore/observationtree/ObsTree.scala @@ -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 @@ -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 @@ -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 = diff --git a/project/Versions.scala b/project/Versions.scala index 88cbf1a4a..fdd60ba91 100644 --- a/project/Versions.scala +++ b/project/Versions.scala @@ -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" @@ -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" }