diff --git a/common/src/main/scala/explore/components/Tile.scala b/common/src/main/scala/explore/components/Tile.scala index 82ceb0c9ab..64643567c1 100644 --- a/common/src/main/scala/explore/components/Tile.scala +++ b/common/src/main/scala/explore/components/Tile.scala @@ -17,12 +17,51 @@ import lucuma.react.common.ReactFnProps import lucuma.react.common.style.* import lucuma.react.primereact.Button import lucuma.ui.syntax.all.given -import org.scalajs.dom +/** + * In explore we have a concept of a Tile which is a draggable and resizable window that can contain + * any content. Tiles can be minimized, maximized and resized and dragged from the title Often we + * want to render content in the body and on the title of the tile, thus you can pass a tileBody and + * tileTitle function to the Tile constructor returning a VdomNode. The body and title may need to + * share some state of type A, you can pass an initial value and the body and title will get a + * View[A] to access the state and modify it. + * + * @param id + * a unique identifier for the tile + * @param initialState + * the initial state of the tile + * @param title + * the title of the tile to be rendered in the title bar + * @param back + * an optional back button to be rendered in the title bar + * @param canMinimize + * whether the tile can be minimized + * @param canMaximize + * whether the tile can be maximized + * @param hidden + * whether the tile is hidden + * @param sizeState + * the initial size state of the tile + * @param sizeStateCallback + * a callback to be called when the size state changes + * @param controllerClass + * a css class to be applied to the wrapping div when in a TileController + * @param bodyClass + * a css class to be applied to the tile body + * @param tileClass + * a css class to be applied to the tile + * @param tileTitleClass + * a css class to be applied to the title + * + * @param tileBody + * a function to render the body of the tile, it receives a View[A] to access the state + * @param tileTitle + * a function to render the title of the tile, it receives a View[A] and the current size state + */ case class Tile[A]( id: Tile.TileId, - initialState: A, title: String, + initialState: A = (), back: Option[VdomNode] = None, canMinimize: Boolean = true, canMaximize: Boolean = true, @@ -37,12 +76,12 @@ case class Tile[A]( val tileBody: View[A] => VdomNode, val tileTitle: (View[A], TileSizeState) => VdomNode = (_: View[A], _: TileSizeState) => EmptyVdom ) extends ReactFnProps(Tile.component) { - val fullSize: Boolean = !canMinimize && !canMaximize + protected val fullSize: Boolean = !canMinimize && !canMaximize - def showMaximize: Boolean = + protected def showMaximize: Boolean = sizeState === TileSizeState.Minimized || (canMaximize && sizeState === TileSizeState.Minimized) - def showMinimize: Boolean = + protected def showMinimize: Boolean = sizeState === TileSizeState.Maximized || (canMinimize && sizeState === TileSizeState.Maximized) def withState( @@ -66,9 +105,7 @@ object Tile: ScalaFnComponent .withHooks[Props[A]] .useStateViewBy(_.initialState) - // infoRef - We use state instead of a regular Ref in order to force a rerender when it's set. - .useState(none[dom.html.Element]) - .render: (p, sharedState, infoRef) => + .render: (p, sharedState) => val maximizeButton = Button( text = true, @@ -89,11 +126,7 @@ object Tile: .when_(p.sizeState === TileSizeState.Maximized) ) - def setInfoRef(node: dom.Node | Null): Unit = - infoRef - .modState(_.fold(Option(node.asInstanceOf[dom.html.Element]))(_.some)) - .runNow() - + // Tile wrapper if (!p.hidden) { <.div( ExploreStyles.Tile |+| ExploreStyles.FadeIn |+| p.tileClass, @@ -101,35 +134,32 @@ object Tile: )( // Tile title <.div(ExploreStyles.TileTitle)( + // Title and optional back button <.div( ExploreStyles.TileTitleMenu |+| p.tileTitleClass, p.back.map(b => <.div(ExploreStyles.TileButton, b)), <.div(ExploreStyles.TileTitleText |+| ExploreStyles.TileDraggable, p.title) ), + // Title controls <.div( + ^.key := s"tileTitle-${p.id.value}", ExploreStyles.TileTitleControlArea, <.div(ExploreStyles.TileTitleStrip |+| ExploreStyles.TileControl, p.tileTitle(sharedState, p.sizeState) - ), - <.div(^.key := s"tileTitle-${p.id.value}", - ^.untypedRef(setInfoRef).when(infoRef.value.isEmpty) - )( - ExploreStyles.TileTitleStrip, - ExploreStyles.FixedSizeTileTitle.when(!p.canMinimize && !p.canMaximize) ) ), + // Size control buttons <.div(ExploreStyles.TileControlButtons, - minimizeButton.when(p.showMinimize && !p.fullSize), - maximizeButton.when(p.showMaximize && !p.fullSize) - ) + minimizeButton.when(p.showMinimize), + maximizeButton.when(p.showMaximize) + ).unless(p.fullSize) ), // Tile body - infoRef.value - .map(node => - <.div(ExploreStyles.TileBody |+| p.bodyClass, p.tileBody(sharedState)) - .when(p.sizeState =!= TileSizeState.Minimized) - ) - .whenDefined + <.div(^.key := s"tileBody${p.id.value}", + ExploreStyles.TileBody |+| p.bodyClass, + p.tileBody(sharedState) + ) + .unless(p.sizeState === TileSizeState.Minimized) ) } else EmptyVdom diff --git a/common/src/main/scala/explore/components/ui/ExploreStyles.scala b/common/src/main/scala/explore/components/ui/ExploreStyles.scala index b3ec07d946..7ec0a671fe 100644 --- a/common/src/main/scala/explore/components/ui/ExploreStyles.scala +++ b/common/src/main/scala/explore/components/ui/ExploreStyles.scala @@ -13,24 +13,20 @@ object ExploreStyles: val HideReusability = Css("hide-reusability") - val Tile: Css = Css("explore-tile") - val TileTitle: Css = Css("explore-tile-title") - val TileTitleText: Css = Css("explore-tile-title-text") - val TileTitleMenu: Css = Css("explore-tile-title-menu") - val TileTitleControl: Css = Css("explore-tile-title-control") - val TileTitleStrip: Css = Css("explore-tile-title-strip") - val TileDraggable: Css = Css("explore-tile-draggable") - val TileTitleControlArea: Css = Css("explore-tile-title-control-area") - val MainTitleProgramId: Css = Css("main-title-program-id") - val JustifiedEndTileControl: Css = Css("justified-end-tile-control") - val JustifiedCenterTileControl: Css = Css("justified-center-tile-control") - val TileControlButtons: Css = Css("explore-tile-control-buttons") - val FixedSizeTileTitle: Css = Css("explore-fixed-size-tile-title") - val TileBackButton: Css = Css("tile-back-button") - val TileBody: Css = Css("explore-tile-body") - val TileButton: Css = Css("explore-tile-button") - val TileControl: Css = Css("explore-tile-control") - val TileStateButton: Css = Css("explore-tile-state-button") + val Tile: Css = Css("explore-tile") + val TileTitle: Css = Css("explore-tile-title") + val TileTitleText: Css = Css("explore-tile-title-text") + val TileTitleMenu: Css = Css("explore-tile-title-menu") + val TileTitleStrip: Css = Css("explore-tile-title-strip") + val TileDraggable: Css = Css("explore-tile-draggable") + val TileTitleControlArea: Css = Css("explore-tile-title-control-area") + val JustifiedEndTileControl: Css = Css("justified-end-tile-control") + val TileControlButtons: Css = Css("explore-tile-control-buttons") + val TileBackButton: Css = Css("tile-back-button") + val TileBody: Css = Css("explore-tile-body") + val TileButton: Css = Css("explore-tile-button") + val TileControl: Css = Css("explore-tile-control") + val TileStateButton: Css = Css("explore-tile-state-button") val Accented: Css = Css("explore-accented") val TextPlain: Css = Css("explore-text-plain") @@ -57,6 +53,7 @@ object ExploreStyles: val ObsGroupTitleWithList: Css = Css("obs-group-title-with-list") val DeleteButton: Css = Css("delete-button") + val MainTitleProgramId: Css = Css("main-title-program-id") val ResizeHandle: Css = Css("resize-handle") val ResizableSeparator: Css = Css("resize-separator") val Tree: Css = Css("tree") diff --git a/common/src/main/webapp/sass/explore.scss b/common/src/main/webapp/sass/explore.scss index 52f42710c4..0f9d0459f8 100644 --- a/common/src/main/webapp/sass/explore.scss +++ b/common/src/main/webapp/sass/explore.scss @@ -606,12 +606,6 @@ thead tr th.sticky-header { flex-shrink: 1; } - - .explore-tile-title-control-area { - flex: 1; - display: flex; - justify-items: center; - } } .explore-tile-title-menu { @@ -648,16 +642,6 @@ thead tr th.sticky-header { justify-content: end; } -.justified-center-tile-control { - flex-grow: 1; - display: flex; - justify-content: center; -} - -.explore-fixed-size-tile-title { - margin-right: 1em; -} - .p-button.p-button-text.explore-tile-state-button { color: var(--site-text-color); filter: brightness(70%); diff --git a/explore/src/main/scala/explore/config/sequence/SequenceEditorTile.scala b/explore/src/main/scala/explore/config/sequence/SequenceEditorTile.scala index fb71563cc8..a3e6793092 100644 --- a/explore/src/main/scala/explore/config/sequence/SequenceEditorTile.scala +++ b/explore/src/main/scala/explore/config/sequence/SequenceEditorTile.scala @@ -25,7 +25,6 @@ object SequenceEditorTile: ) = Tile( ObsTabTilesIds.SequenceId.id, - (), s"Sequence" )( _ => diff --git a/explore/src/main/scala/explore/notes/NotesTile.scala b/explore/src/main/scala/explore/notes/NotesTile.scala index 99cbabd0c0..321aad4e97 100644 --- a/explore/src/main/scala/explore/notes/NotesTile.scala +++ b/explore/src/main/scala/explore/notes/NotesTile.scala @@ -34,8 +34,8 @@ object NotesTile: def notesTile(obsId: Observation.Id, notes: View[Option[NonEmptyString]]) = Tile( ObsTabTilesIds.NotesId.id, - NotesTileState(notes.get.foldMap(_.value), Editing.NotEditing), s"Note for Observer", + NotesTileState(notes.get.foldMap(_.value), Editing.NotEditing), canMinimize = true, bodyClass = ExploreStyles.NotesTile )(NotesTileBody(obsId, notes, _), NotesTileTitle(obsId, notes, _, _)) diff --git a/explore/src/main/scala/explore/proposal/ProgramUsers.scala b/explore/src/main/scala/explore/proposal/ProgramUsers.scala index c39f87113a..c8f4187bf9 100644 --- a/explore/src/main/scala/explore/proposal/ProgramUsers.scala +++ b/explore/src/main/scala/explore/proposal/ProgramUsers.scala @@ -66,8 +66,8 @@ object ProgramUsers: )(using AppContext[IO], Logger[IO]) = Tile( ProposalTabTileIds.UsersId.id, - ProgramUsersState(CreateInviteProcess.Idle), - "Investigators" + "Investigators", + ProgramUsersState(CreateInviteProcess.Idle) )(ProgramUsers(pid, readOnly, users, invitations, _), (s, _) => inviteControl(readOnly, ref, s)) private type Props = ProgramUsers diff --git a/explore/src/main/scala/explore/proposal/ProposalEditor.scala b/explore/src/main/scala/explore/proposal/ProposalEditor.scala index 04f20ed86f..2168d24a34 100644 --- a/explore/src/main/scala/explore/proposal/ProposalEditor.scala +++ b/explore/src/main/scala/explore/proposal/ProposalEditor.scala @@ -92,7 +92,7 @@ object ProposalEditor: val defaultLayouts = ExploreGridLayouts.sectionLayout(GridLayoutSection.ProposalLayout) val detailsTile = - Tile(ProposalTabTileIds.DetailsId.id, (), "Details")( + Tile(ProposalTabTileIds.DetailsId.id, "Details")( _ => ProposalDetailsBody(props.proposal, aligner, @@ -113,9 +113,7 @@ object ProposalEditor: val abstractTile = Tile(ProposalTabTileIds.AbstractId.id, - (), "Abstract", - canMinimize = true, bodyClass = ExploreStyles.ProposalAbstract )(_ => FormInputTextAreaView( @@ -125,7 +123,7 @@ object ProposalEditor: ) val attachmentsTile = - Tile(ProposalTabTileIds.AttachmentsId.id, (), "Attachments", canMinimize = true)(_ => + Tile(ProposalTabTileIds.AttachmentsId.id, "Attachments")(_ => props.authToken.map(token => ProposalAttachmentsTable(props.programId, token, props.attachments, props.readonly) ) diff --git a/explore/src/main/scala/explore/tabs/AsterismEditorTile.scala b/explore/src/main/scala/explore/tabs/AsterismEditorTile.scala index c14b13f1d6..91a46b931b 100644 --- a/explore/src/main/scala/explore/tabs/AsterismEditorTile.scala +++ b/explore/src/main/scala/explore/tabs/AsterismEditorTile.scala @@ -66,10 +66,9 @@ object AsterismEditorTile: Tile( ObsTabTilesIds.TargetId.id, - AsterismTileState(), title, + AsterismTileState(), back = backButton, - canMinimize = true, bodyClass = ExploreStyles.TargetTileBody, controllerClass = ExploreStyles.TargetTileController )( diff --git a/explore/src/main/scala/explore/tabs/ConfigurationTile.scala b/explore/src/main/scala/explore/tabs/ConfigurationTile.scala index f16555de54..f8c303b221 100644 --- a/explore/src/main/scala/explore/tabs/ConfigurationTile.scala +++ b/explore/src/main/scala/explore/tabs/ConfigurationTile.scala @@ -47,7 +47,6 @@ object ConfigurationTile: )(using Logger[IO]) = Tile( ObsTabTilesIds.ConfigurationId.id, - (), "Configuration", bodyClass = ExploreStyles.ConfigurationTileBody )(_ => diff --git a/explore/src/main/scala/explore/tabs/ConstraintsTabContents.scala b/explore/src/main/scala/explore/tabs/ConstraintsTabContents.scala index 0c545be988..f868664c21 100644 --- a/explore/src/main/scala/explore/tabs/ConstraintsTabContents.scala +++ b/explore/src/main/scala/explore/tabs/ConstraintsTabContents.scala @@ -114,8 +114,8 @@ object ConstraintsTabContents extends TwoPanels: .fold[VdomNode] { Tile( "constraints".refined, - ColumnSelectorState[ConstraintGroup, Nothing](), "Constraints Summary", + ColumnSelectorState[ConstraintGroup, Nothing](), backButton.some, canMinimize = false, canMaximize = false @@ -146,7 +146,6 @@ object ConstraintsTabContents extends TwoPanels: val constraintsTile = Tile( ObsTabTilesIds.ConstraintsId.id, - (), constraintsTitle, backButton.some )(_ => diff --git a/explore/src/main/scala/explore/tabs/ElevationPlotTile.scala b/explore/src/main/scala/explore/tabs/ElevationPlotTile.scala index 0f22cf2b3b..5d8128187c 100644 --- a/explore/src/main/scala/explore/tabs/ElevationPlotTile.scala +++ b/explore/src/main/scala/explore/tabs/ElevationPlotTile.scala @@ -35,9 +35,7 @@ object ElevationPlotTile: ) = Tile( ObsTabTilesIds.PlotId.id, - (), "Elevation Plot", - canMinimize = true, bodyClass = ExploreStyles.ElevationPlotTileBody ) { _ => (uid, tid, coordinates) diff --git a/explore/src/main/scala/explore/tabs/FinderChartsTile.scala b/explore/src/main/scala/explore/tabs/FinderChartsTile.scala index e5521b7c0e..8310a25a8b 100644 --- a/explore/src/main/scala/explore/tabs/FinderChartsTile.scala +++ b/explore/src/main/scala/explore/tabs/FinderChartsTile.scala @@ -36,8 +36,8 @@ object FinderChartsTile: ) = Tile( ObsTabTilesIds.FinderChartsId.id, - FinderChartsTileState(ChartSelector.Closed, None), s"Finder Charts", + FinderChartsTileState(ChartSelector.Closed, None), bodyClass = ExploreStyles.FinderChartsTile )( s => diff --git a/explore/src/main/scala/explore/tabs/ItcTile.scala b/explore/src/main/scala/explore/tabs/ItcTile.scala index 873749d353..fcc661925e 100644 --- a/explore/src/main/scala/explore/tabs/ItcTile.scala +++ b/explore/src/main/scala/explore/tabs/ItcTile.scala @@ -34,8 +34,8 @@ object ItcTile: ) = Tile( ObsTabTilesIds.ItcId.id, - ItcPanelTileState(), - s"ITC" + s"ITC", + ItcPanelTileState() )( s => uid.map( diff --git a/explore/src/main/scala/explore/tabs/ObsGroupTiles.scala b/explore/src/main/scala/explore/tabs/ObsGroupTiles.scala index 603dcda7ac..ae66f63090 100644 --- a/explore/src/main/scala/explore/tabs/ObsGroupTiles.scala +++ b/explore/src/main/scala/explore/tabs/ObsGroupTiles.scala @@ -68,7 +68,6 @@ object ObsGroupTiles: val editTile = Tile( GroupEditIds.GroupEditId.id, - (), s"${if group.get.isAnd then "AND" else "OR"} Group", props.backButton.some, tileTitleClass = ExploreStyles.GroupEditTitle @@ -87,9 +86,7 @@ object ObsGroupTiles: val notesTile = Tile( GroupEditIds.GroupNotesId.id, - (), - s"Note for Observer", - canMinimize = true + s"Note for Observer" )(_ => <.div( ExploreStyles.NotesTile, diff --git a/explore/src/main/scala/explore/tabs/ObsTabContents.scala b/explore/src/main/scala/explore/tabs/ObsTabContents.scala index 86ca51ddfa..2754f3a9f2 100644 --- a/explore/src/main/scala/explore/tabs/ObsTabContents.scala +++ b/explore/src/main/scala/explore/tabs/ObsTabContents.scala @@ -129,8 +129,8 @@ object ObsTabContents extends TwoPanels: val observationTable = Tile( "observations".refined, - ColumnSelectorState[Expandable[ObsSummaryTable.ObsSummaryRow], Nothing](), "Observations Summary", + ColumnSelectorState[Expandable[ObsSummaryTable.ObsSummaryRow], Nothing](), backButton.some )( ObsSummaryTable( diff --git a/explore/src/main/scala/explore/tabs/ObsTabTiles.scala b/explore/src/main/scala/explore/tabs/ObsTabTiles.scala index 3f30163196..443ed90405 100644 --- a/explore/src/main/scala/explore/tabs/ObsTabTiles.scala +++ b/explore/src/main/scala/explore/tabs/ObsTabTiles.scala @@ -495,7 +495,6 @@ object ObsTabTiles: val constraintsTile = Tile( ObsTabTilesIds.ConstraintsId.id, - (), "Constraints" )( renderInTitle => diff --git a/explore/src/main/scala/explore/tabs/OverviewTabContents.scala b/explore/src/main/scala/explore/tabs/OverviewTabContents.scala index 83c1b6fc80..559c12a5f9 100644 --- a/explore/src/main/scala/explore/tabs/OverviewTabContents.scala +++ b/explore/src/main/scala/explore/tabs/OverviewTabContents.scala @@ -54,10 +54,8 @@ object OverviewTabContents { val warningsAndErrorsTile = Tile( ObsTabTilesIds.WarningsAndErrorsId.id, - ObservationValidationsTableTileState(_ => Callback.empty), "Warnings And Errors", - none, - canMinimize = true + ObservationValidationsTableTileState(_ => Callback.empty) )(ObservationValidationsTableBody(props.programId, props.observations, _), ObservationValidationsTableTitle.apply ) @@ -66,10 +64,8 @@ object OverviewTabContents { .map(vault => Tile( ObsTabTilesIds.ObsAttachmentsId.id, - ObsAttachmentsTableTileState(), "Observation Attachments", - none, - canMinimize = true + ObsAttachmentsTableTileState() )( ObsAttachmentsTableBody(props.programId, vault.token, diff --git a/explore/src/main/scala/explore/tabs/ProgramTabContents.scala b/explore/src/main/scala/explore/tabs/ProgramTabContents.scala index 9a14d6abfb..97b0312de3 100644 --- a/explore/src/main/scala/explore/tabs/ProgramTabContents.scala +++ b/explore/src/main/scala/explore/tabs/ProgramTabContents.scala @@ -55,25 +55,19 @@ object ProgramTabContents: val detailsTile = Tile( ProgramTabTileIds.DetailsId.id, - (), - "Program Details", - canMinimize = true + "Program Details" )(_ => ProgramDetailsTile(props.allocations, props.programTimes)) val notesTile = Tile( ProgramTabTileIds.NotesId.id, - (), - "Notes", - canMinimize = true + "Notes" )(_ => ProgramNotesTile()) val changeRequestsTile = Tile( ProgramTabTileIds.ChangeRequestsId.id, - (), - "Change Requests", - canMinimize = true + "Change Requests" )(_ => ProgramChangeRequestsTile()) <.div(ExploreStyles.MultiPanelTile)( diff --git a/explore/src/main/scala/explore/tabs/SiderealTargetEditorTile.scala b/explore/src/main/scala/explore/tabs/SiderealTargetEditorTile.scala index a872b60657..d2fdff720f 100644 --- a/explore/src/main/scala/explore/tabs/SiderealTargetEditorTile.scala +++ b/explore/src/main/scala/explore/tabs/SiderealTargetEditorTile.scala @@ -43,10 +43,8 @@ object SiderealTargetEditorTile: ) = Tile( ObsTabTilesIds.TargetId.id, - (), title, back = backButton, - canMinimize = true, bodyClass = ExploreStyles.TargetTileBody ) { _ => <.div( diff --git a/explore/src/main/scala/explore/tabs/TargetTabContents.scala b/explore/src/main/scala/explore/tabs/TargetTabContents.scala index a5c12b5773..6a2477ca81 100644 --- a/explore/src/main/scala/explore/tabs/TargetTabContents.scala +++ b/explore/src/main/scala/explore/tabs/TargetTabContents.scala @@ -139,8 +139,8 @@ object TargetTabContents extends TwoPanels: */ val renderSummary: Tile[TargetSummaryTileState] = Tile( ObsTabTilesIds.TargetSummaryId.id, - TargetSummaryTileState(Nil, ColumnSelectorState(), DeletingTargets(false)), "Target Summary", + TargetSummaryTileState(Nil, ColumnSelectorState(), DeletingTargets(false)), backButton.some )( TargetSummaryBody( @@ -413,14 +413,14 @@ object TargetTabContents extends TwoPanels: // We still want to render these 2 tiles, even when not shown, so as not to mess up the stored layout. val dummyTargetTile: Tile[Unit] = - Tile(ObsTabTilesIds.TargetId.id, (), "", hidden = true)(_ => EmptyVdom) + Tile(ObsTabTilesIds.TargetId.id, "", hidden = true)(_ => EmptyVdom) val dummyElevationTile: Tile[Unit] = - Tile(ObsTabTilesIds.PlotId.id, (), "", hidden = true)(_ => EmptyVdom) + Tile(ObsTabTilesIds.PlotId.id, "", hidden = true)(_ => EmptyVdom) val renderNonSiderealTargetEditor: List[Tile[?]] = List( renderSummary, - Tile("nonSiderealTarget".refined, (), "Non-sidereal target")(_ => + Tile("nonSiderealTarget".refined, "Non-sidereal target")(_ => <.div("Editing of Non-Sidereal targets not supported") ), dummyElevationTile diff --git a/explore/src/main/scala/explore/timingwindows/TimingWindowsTile.scala b/explore/src/main/scala/explore/timingwindows/TimingWindowsTile.scala index 75018e5d7c..abbdf087ba 100644 --- a/explore/src/main/scala/explore/timingwindows/TimingWindowsTile.scala +++ b/explore/src/main/scala/explore/timingwindows/TimingWindowsTile.scala @@ -74,7 +74,7 @@ object TimingWindowsTile: val base = "Scheduling Windows" val title = if (timingWindows.get.isEmpty) base else s"$base (${timingWindows.get.length})" - Tile(ObsTabTilesIds.TimingWindowsId.id, TimingWindowsTileState(), title)( + Tile(ObsTabTilesIds.TimingWindowsId.id, title, TimingWindowsTileState())( TimingWindowsBody(timingWindows, readOnly, _), TimingWindowsTitle(timingWindows, readOnly, _, _) )