From f600bc7bcfcea2819f2b6468221197ad56f9e942 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Piaggio?= Date: Sun, 22 Dec 2024 20:55:02 -0300 Subject: [PATCH] better identification of new programs --- .../main/scala/explore/EditableLabel.scala | 27 +++++++------- .../scala/explore/programs/ProgramTable.scala | 12 +++++-- .../explore/programs/ProgramsPopup.scala | 36 ++++++++++++------- 3 files changed, 49 insertions(+), 26 deletions(-) diff --git a/explore/src/main/scala/explore/EditableLabel.scala b/explore/src/main/scala/explore/EditableLabel.scala index e714c924b6..5cb3557ecf 100644 --- a/explore/src/main/scala/explore/EditableLabel.scala +++ b/explore/src/main/scala/explore/EditableLabel.scala @@ -25,6 +25,7 @@ import scalajs.js.JSConverters.* case class EditableLabel( value: Option[NonEmptyString], mod: Option[NonEmptyString] => Callback, + forceEditing: Boolean = false, editOnClick: Boolean = false, textClass: Css = Css.Empty, inputClass: Css = Css.Empty, @@ -40,11 +41,12 @@ case class EditableLabel( readonly: Boolean = false ) extends ReactFnProps(EditableLabel.component) -object EditableLabel { - type Props = EditableLabel +object EditableLabel: + private type Props = EditableLabel def fromView( value: View[Option[NonEmptyString]], + forceEditing: Boolean = false, editOnClick: Boolean = false, textClass: Css = Css.Empty, inputClass: Css = Css.Empty, @@ -62,6 +64,7 @@ object EditableLabel { EditableLabel( value.get, value.set, + forceEditing, editOnClick, textClass, inputClass, @@ -77,20 +80,23 @@ object EditableLabel { readonly ) - type Editing = Editing.Type - object Editing extends NewType[Boolean]: - inline def NotEditing = Editing(false) - inline def InEdition = Editing(true) + private type Editing = Editing.Type + private object Editing extends NewType[Boolean]: + inline def NotEditing = Editing(false) + inline def InEdition = Editing(true) + def fromBoolean(b: Boolean): Editing = Editing(b) import Editing.* private val component = ScalaFnComponent .withHooks[Props] - .useState(NotEditing) // editing - .useState("") // displayValue + .useStateBy(props => Editing.fromBoolean(props.forceEditing)) // editing + .useEffectWithDepsBy((props, _) => props.forceEditing): (_, editing) => + forceEditing => if forceEditing then editing.setState(InEdition) else Callback.empty + .useState("") // displayValue .useId - .render { (props, editing, displayValue, id) => + .render: (props, editing, displayValue, id) => def editCB(e: ReactMouseEvent): Callback = e.stopPropagationCB >> e.preventDefaultCB >> displayValue.setState(props.value.map(_.value).orEmpty) >> @@ -181,6 +187,3 @@ object EditableLabel { rightButton.unless(props.readonly) ) ) - } - -} diff --git a/explore/src/main/scala/explore/programs/ProgramTable.scala b/explore/src/main/scala/explore/programs/ProgramTable.scala index 2c2691abef..05c84da12d 100644 --- a/explore/src/main/scala/explore/programs/ProgramTable.scala +++ b/explore/src/main/scala/explore/programs/ProgramTable.scala @@ -40,13 +40,18 @@ case class ProgramTable( selectProgram: Program.Id => Callback, isRequired: Boolean, onClose: Option[Callback], + newProgramId: Option[Program.Id], virtualizerRef: UseRef[Option[HTMLTableVirtualizer]] ) extends ReactFnProps(ProgramTable.component) object ProgramTable: private type Props = ProgramTable - private case class TableMeta(currentProgramId: Option[Program.Id], programCount: Int) + private case class TableMeta( + currentProgramId: Option[Program.Id], + newProgramId: Option[Program.Id], + programCount: Int + ) private val ColDef = ColumnDef.WithTableMeta[View[ProgramInfo], TableMeta] @@ -138,6 +143,9 @@ object ProgramTable: EditableLabel .fromView( value = cell.value, + forceEditing = cell.table.options.meta + .flatMap(_.newProgramId) + .contains_(cell.row.original.get.id), addButtonLabel = ("Add program name": VdomNode).reuseAlways, textClass = ExploreStyles.ProgramName, inputClass = ExploreStyles.ProgramNameInput, @@ -158,7 +166,7 @@ object ProgramTable: rows, enableSorting = true, enableColumnResizing = false, - meta = TableMeta(props.currentProgramId, props.programInfos.size) + meta = TableMeta(props.currentProgramId, props.newProgramId, props.programInfos.size) ) .render: (props, ctx, _, _, table) => PrimeAutoHeightVirtualizedTable( diff --git a/explore/src/main/scala/explore/programs/ProgramsPopup.scala b/explore/src/main/scala/explore/programs/ProgramsPopup.scala index 0b539c238c..bb16e0ed92 100644 --- a/explore/src/main/scala/explore/programs/ProgramsPopup.scala +++ b/explore/src/main/scala/explore/programs/ProgramsPopup.scala @@ -3,6 +3,7 @@ package explore.programs +import cats.Endo import cats.Order.* import cats.effect.IO import cats.syntax.all.* @@ -78,15 +79,17 @@ object ProgramsPopup: .setAlign(rawVirtual.mod.ScrollAlignment.start) // force to go as far as possible private def addProgram( - programs: View[ProgramInfoList], - adding: View[IsAdding] + programsMod: Endo[ProgramInfoList] => Callback, + adding: View[IsAdding], + setNewProgramId: Program.Id => Callback )(using FetchClient[IO, ObservationDB], Logger[IO] ): IO[Unit] = ProgramQueries .createProgram[IO](none) - .flatMap(pi => programs.async.mod(_.updated(pi.id, pi))) + .flatTap(pi => programsMod(_.updated(pi.id, pi)).to[IO]) + .flatMap(pi => setNewProgramId(pi.id).to[IO]) .switching(adding.async, IsAdding(_)) private val component = ScalaFnComponent @@ -95,29 +98,33 @@ object ProgramsPopup: .useStateView(IsOpen(true)) .useStateView(IsAdding(false)) // Adding new program .useStateView(ShowDeleted(false)) // Show deleted + .useStateView(none[Program.Id]) // Recently added program .useRef(none[HTMLTableVirtualizer]) - .render: (props, ctx, isOpen, isAdding, showDeleted, virtualizerRef) => + .render: (props, ctx, isOpen, isAdding, showDeleted, newProgramId, virtualizerRef) => import ctx.given - val onHide = props.onClose.map(oc => isOpen.set(IsOpen(false)) >> oc) + val onHide: Callback = + props.onClose.map(oc => isOpen.set(IsOpen(false)) >> oc).orEmpty >> newProgramId.set(none) val closeButton = - props.onClose.fold(none)(cb => + props.onClose.fold(none): cb => Button( label = "Cancel", icon = Icons.Close, severity = Button.Severity.Danger, onClick = cb ).small.compact.some - ) val programInfosViewOpt: Option[View[ProgramInfoList]] = props.programInfos.toOptionView + val doSelectProgram: Program.Id => Callback = + selectProgram(props.onClose, props.undoStacks, ctx) + val programInfoViewListOpt: Option[List[View[ProgramInfo]]] = programInfosViewOpt.map: _.toListOfViews - .map(_._2) + .map(_._2.withOnMod(pi => newProgramId.set(none) >> doSelectProgram(pi.id))) .filter(vpi => showDeleted.get.value || !vpi.get.deleted) .sortBy(_.get.id) @@ -133,7 +140,7 @@ object ProgramsPopup: Dialog( visible = isOpen.get.value, - onHide = onHide.orEmpty, + onHide = onHide, position = DialogPosition.Top, closeOnEscape = props.onClose.isDefined, closable = props.onClose.isDefined, @@ -150,7 +157,11 @@ object ProgramsPopup: severity = Button.Severity.Success, disabled = isAdding.get.value, loading = isAdding.get.value, - onClick = addProgram(pis, isAddingWithScroll).runAsync + onClick = addProgram( + pis.mod, + isAddingWithScroll, + programId => newProgramId.set(programId.some) + ).runAsync ).small.compact, CheckboxView( id = "show-deleted".refined, @@ -165,9 +176,10 @@ object ProgramsPopup: ProgramTable( props.currentProgramId, pis, - selectProgram = selectProgram(props.onClose, props.undoStacks, ctx), + selectProgram = doSelectProgram, props.onClose.isEmpty, - onHide, + onHide.some, + newProgramId.get, virtualizerRef ), props.message.map(msg =>