diff --git a/explore/src/main/scala/explore/observationtree/ObsList.scala b/explore/src/main/scala/explore/observationtree/ObsList.scala index be4a12f229..e747ef7cd1 100644 --- a/explore/src/main/scala/explore/observationtree/ObsList.scala +++ b/explore/src/main/scala/explore/observationtree/ObsList.scala @@ -76,6 +76,9 @@ case class ObsList( clipboardObsContents: Option[ObsIdSet], readonly: Boolean ) extends ReactFnProps(ObsList.component): + private val activeGroup: Option[Group.Id] = focusedGroup.orElse: + focusedObs.flatMap(obsId => observations.get.getValue(obsId).flatMap(_.groupId)) + private val copyDisabled: Boolean = focusedObs.isEmpty private val pasteDisabled: Boolean = clipboardObsContents.isEmpty private val deleteDisabled: Boolean = focusedObs.isEmpty && focusedGroup.isEmpty @@ -83,13 +86,15 @@ case class ObsList( private def observationText(obsId: Observation.Id): String = s"observation $obsId" private def groupText(groupId: Group.Id): String = s"group $groupId" - private val copyText: Option[String] = focusedObs.map(observationText) - private val pasteText: Option[String] = + private val copyText: Option[String] = focusedObs.map(observationText) + private val selectedText: Option[String] = clipboardObsContents.map: obdIdSet => obdIdSet.idSet.size match case 1 => s"observation ${obdIdSet.idSet.head}" case more => s"$more observations" - private val deleteText: Option[String] = + private val pasteText: Option[String] = + selectedText.map(_ + activeGroup.map(gid => s" into ${groupText(gid)}").orEmpty) + private val deleteText: Option[String] = focusedObs.map(observationText).orElse(focusedGroup.map(groupText)) object ObsList: @@ -304,7 +309,7 @@ object ObsList: cloneCB = cloneObs( props.programId, id, - props.observations.get.length, + obs.groupId, // Clone to the same group props.observations, ctx, adding.async.set(AddingObservation(true)), diff --git a/explore/src/main/scala/explore/observationtree/package.scala b/explore/src/main/scala/explore/observationtree/package.scala index e008d8e6a2..c704c077a2 100644 --- a/explore/src/main/scala/explore/observationtree/package.scala +++ b/explore/src/main/scala/explore/observationtree/package.scala @@ -59,7 +59,7 @@ def setGroup[F[_]]( def cloneObs( programId: Program.Id, obsId: Observation.Id, - pos: NonNegInt, + newGroupId: Option[Group.Id], observations: UndoSetter[ObservationList], ctx: AppContext[IO], before: IO[Unit] = IO.unit, @@ -68,10 +68,11 @@ def cloneObs( import ctx.given before >> - cloneObservation[IO](obsId) - .flatMap: obs => - obsExistence(obs.id, o => setObs(programId, o.some, ctx)) - .mod(observations)(obsListMod.upsert(obs, pos)) + cloneObservation[IO](obsId, newGroupId) + .flatMap: newObs => + obsExistence(newObs.id, o => setObs(programId, o.some, ctx)) + .mod(observations): // Convert NonNegShort => NonNegInt + obsListMod.upsert(newObs, NonNegInt.unsafeFrom(newObs.groupIndex.value)) .toAsync .guarantee(after) diff --git a/explore/src/main/scala/explore/tabs/ObsTabContents.scala b/explore/src/main/scala/explore/tabs/ObsTabContents.scala index 9e422593f4..156cce7137 100644 --- a/explore/src/main/scala/explore/tabs/ObsTabContents.scala +++ b/explore/src/main/scala/explore/tabs/ObsTabContents.scala @@ -25,6 +25,7 @@ import explore.model.ProgramSummaries import explore.model.enums.AppTab import explore.model.enums.GridLayoutSection import explore.model.enums.SelectedPanel +import explore.model.reusability.given import explore.modes.SpectroscopyModesMatrix import explore.observationtree.* import explore.shortcuts.* @@ -81,6 +82,8 @@ case class ObsTabContents( private val focusedGroup: Option[Group.Id] = focused.group private val observations: UndoSetter[ObservationList] = programSummaries.zoom(ProgramSummaries.observations) + private val activeGroup: Option[Group.Id] = focusedGroup.orElse: + focusedObs.flatMap(obsId => observations.get.getValue(obsId).flatMap(_.groupId)) private val obsExecutions: ObservationExecutionMap = programSummaries.get.obsExecutionPots private val groupTimeRanges: GroupTimeRangeMap = programSummaries.get.groupTimeRangePots private val groups: UndoSetter[GroupTree] = programSummaries.zoom(ProgramSummaries.groups) @@ -127,29 +130,26 @@ object ObsTabContents extends TwoPanels: .withToast(s"Copied obs $id") .orUnit .runAsync - .useCallbackWithDepsBy((props, _, _, _, _, _) => (props.observationIds, props.readonly)): // PASTE Action Callback - (props, ctx, _, _, _, _) => - (observationIds, readonly) => - import ctx.given + .useCallbackWithDepsBy((props, _, _, _, _, _) => // PASTE Action Callback + (Reusable.explicitly(props.observations)(Reusability.by(_.get)), + props.activeGroup, + props.readonly + ) + ): (props, ctx, _, _, _, _) => + (observations, activeGroup, readonly) => + import ctx.given - ExploreClipboard.get - .flatMap: - case LocalClipboard.CopiedObservations(idSet) => - idSet.idSet.toList - .traverse(oid => - cloneObs( - props.programId, - oid, - NonNegInt.unsafeFrom(observationIds.length), - props.observations, - ctx - ) - ) - .void - .withToast(s"Duplicating obs ${idSet.idSet.mkString_(", ")}") - case _ => IO.unit - .runAsync - .unless_(readonly) + ExploreClipboard.get + .flatMap: + case LocalClipboard.CopiedObservations(obsIdSet) => + obsIdSet.idSet.toList + .traverse: oid => + cloneObs(props.programId, oid, activeGroup, observations, ctx) + .void + .withToast(s"Duplicating obs ${obsIdSet.idSet.mkString_(", ")}") + case _ => IO.unit + .runAsync + .unless_(readonly) .useGlobalHotkeysWithDepsBy((props, _, _, _, _, copyCallback, pasteCallback) => (copyCallback, pasteCallback, props.focusedObs, props.observationIdsWithIndices) ): (props, ctx, _, _, _, _, _) => diff --git a/explore/src/main/scala/queries/schemas/odb/ObsQueries.scala b/explore/src/main/scala/queries/schemas/odb/ObsQueries.scala index 51d614242a..cb27f5aa35 100644 --- a/explore/src/main/scala/queries/schemas/odb/ObsQueries.scala +++ b/explore/src/main/scala/queries/schemas/odb/ObsQueries.scala @@ -183,12 +183,17 @@ object ObsQueries: .map(_.createObservation.observation) def cloneObservation[F[_]: Async]( - obsId: Observation.Id + obsId: Observation.Id, + newGroupId: Option[Group.Id] )(using FetchClient[F, ObservationDB] ): F[Observation] = CloneObservationMutation[F] - .execute(CloneObservationInput(observationId = obsId.assign)) + .execute: + CloneObservationInput( + observationId = obsId.assign, + SET = ObservationPropertiesInput(groupId = newGroupId.orUnassign).assign + ) .map(_.cloneObservation.newObservation) def applyObservation[F[_]: Async](