github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/web/elm/src/SideBar/SideBar.elm (about)

     1  module SideBar.SideBar exposing
     2      ( Model
     3      , hamburgerMenu
     4      , handleCallback
     5      , handleDelivery
     6      , tooltip
     7      , update
     8      , view
     9      )
    10  
    11  import Assets
    12  import Colors
    13  import Concourse
    14  import EffectTransformer exposing (ET)
    15  import HoverState
    16  import Html exposing (Html)
    17  import Html.Attributes exposing (id)
    18  import Html.Events exposing (onClick, onMouseDown, onMouseEnter, onMouseLeave)
    19  import List.Extra
    20  import Message.Callback exposing (Callback(..))
    21  import Message.Effects as Effects
    22  import Message.Message exposing (DomID(..), Message(..), PipelinesSection(..))
    23  import Message.Subscription exposing (Delivery(..))
    24  import RemoteData exposing (RemoteData(..), WebData)
    25  import ScreenSize exposing (ScreenSize(..))
    26  import Set exposing (Set)
    27  import SideBar.State exposing (SideBarState)
    28  import SideBar.Styles as Styles
    29  import SideBar.Team as Team
    30  import SideBar.Views as Views
    31  import Tooltip
    32  import Views.Icon as Icon
    33  import Views.Styles
    34  
    35  
    36  type alias Model m =
    37      Tooltip.Model
    38          { m
    39              | expandedTeamsInAllPipelines : Set String
    40              , collapsedTeamsInFavorites : Set String
    41              , pipelines : WebData (List Concourse.Pipeline)
    42              , sideBarState : SideBarState
    43              , draggingSideBar : Bool
    44              , screenSize : ScreenSize.ScreenSize
    45              , favoritedPipelines : Set Concourse.DatabaseID
    46          }
    47  
    48  
    49  type alias PipelineScoped a =
    50      { a
    51          | teamName : String
    52          , pipelineName : String
    53      }
    54  
    55  
    56  update : Message -> Model m -> ( Model m, List Effects.Effect )
    57  update message model =
    58      let
    59          toggle element set =
    60              if Set.member element set then
    61                  Set.remove element set
    62  
    63              else
    64                  Set.insert element set
    65  
    66          toggleFavorite pipelineID =
    67              let
    68                  favoritedPipelines =
    69                      toggle pipelineID model.favoritedPipelines
    70              in
    71              ( { model | favoritedPipelines = favoritedPipelines }
    72              , [ Effects.SaveFavoritedPipelines <| favoritedPipelines ]
    73              )
    74      in
    75      case message of
    76          Click HamburgerMenu ->
    77              let
    78                  oldState =
    79                      model.sideBarState
    80  
    81                  newState =
    82                      { oldState | isOpen = not oldState.isOpen }
    83              in
    84              ( { model | sideBarState = newState }
    85              , [ Effects.SaveSideBarState newState ]
    86              )
    87  
    88          Click (SideBarTeam section teamName) ->
    89              case section of
    90                  AllPipelinesSection ->
    91                      ( { model
    92                          | expandedTeamsInAllPipelines =
    93                              toggle teamName model.expandedTeamsInAllPipelines
    94                        }
    95                      , []
    96                      )
    97  
    98                  FavoritesSection ->
    99                      ( { model
   100                          | collapsedTeamsInFavorites =
   101                              toggle teamName model.collapsedTeamsInFavorites
   102                        }
   103                      , []
   104                      )
   105  
   106          Click SideBarResizeHandle ->
   107              ( { model | draggingSideBar = True }, [] )
   108  
   109          Click (SideBarFavoritedIcon pipelineID) ->
   110              toggleFavorite pipelineID
   111  
   112          Click (PipelineCardFavoritedIcon _ pipelineID) ->
   113              toggleFavorite pipelineID
   114  
   115          Click (TopBarFavoritedIcon pipelineID) ->
   116              toggleFavorite pipelineID
   117  
   118          Hover (Just (SideBarPipeline section pipelineID)) ->
   119              ( model
   120              , [ Effects.GetViewportOf
   121                      (SideBarPipeline section pipelineID)
   122                ]
   123              )
   124  
   125          Hover (Just (SideBarTeam section teamName)) ->
   126              ( model
   127              , [ Effects.GetViewportOf
   128                      (SideBarTeam section teamName)
   129                ]
   130              )
   131  
   132          _ ->
   133              ( model, [] )
   134  
   135  
   136  handleCallback : Callback -> WebData (PipelineScoped a) -> ET (Model m)
   137  handleCallback callback currentPipeline ( model, effects ) =
   138      case callback of
   139          AllPipelinesFetched (Ok pipelines) ->
   140              ( { model
   141                  | pipelines = Success pipelines
   142                  , expandedTeamsInAllPipelines =
   143                      case ( model.pipelines, currentPipeline ) of
   144                          ( NotAsked, Success { teamName } ) ->
   145                              model.expandedTeamsInAllPipelines
   146                                  |> Set.insert teamName
   147  
   148                          _ ->
   149                              model.expandedTeamsInAllPipelines
   150                }
   151              , effects
   152              )
   153  
   154          BuildFetched (Ok build) ->
   155              ( { model
   156                  | expandedTeamsInAllPipelines =
   157                      case ( currentPipeline, build.job ) of
   158                          ( NotAsked, Just { teamName } ) ->
   159                              model.expandedTeamsInAllPipelines
   160                                  |> Set.insert teamName
   161  
   162                          _ ->
   163                              model.expandedTeamsInAllPipelines
   164                }
   165              , effects
   166              )
   167  
   168          _ ->
   169              ( model, effects )
   170  
   171  
   172  handleDelivery : Delivery -> ET (Model m)
   173  handleDelivery delivery ( model, effects ) =
   174      case delivery of
   175          SideBarStateReceived (Ok state) ->
   176              ( { model | sideBarState = state }, effects )
   177  
   178          Moused pos ->
   179              if model.draggingSideBar then
   180                  let
   181                      oldState =
   182                          model.sideBarState
   183  
   184                      newState =
   185                          { oldState | width = pos.x }
   186                  in
   187                  ( { model | sideBarState = newState }
   188                  , effects ++ [ Effects.GetViewportOf Dashboard ]
   189                  )
   190  
   191              else
   192                  ( model, effects )
   193  
   194          MouseUp ->
   195              ( { model | draggingSideBar = False }
   196              , if model.draggingSideBar then
   197                  [ Effects.SaveSideBarState model.sideBarState ]
   198  
   199                else
   200                  []
   201              )
   202  
   203          FavoritedPipelinesReceived (Ok pipelines) ->
   204              ( { model | favoritedPipelines = pipelines }, effects )
   205  
   206          _ ->
   207              ( model, effects )
   208  
   209  
   210  view : Model m -> Maybe (PipelineScoped a) -> Html Message
   211  view model currentPipeline =
   212      if
   213          model.sideBarState.isOpen
   214              && hasVisiblePipelines model
   215              && (model.screenSize /= ScreenSize.Mobile)
   216      then
   217          let
   218              oldState =
   219                  model.sideBarState
   220  
   221              newState =
   222                  { oldState | width = clamp 100 600 oldState.width }
   223          in
   224          Html.div
   225              (id "side-bar" :: Styles.sideBar newState)
   226              (favoritedPipelinesSection model currentPipeline
   227                  ++ allPipelinesSection model currentPipeline
   228                  ++ [ Html.div
   229                          (Styles.sideBarHandle newState
   230                              ++ [ onMouseDown <| Click SideBarResizeHandle ]
   231                          )
   232                          []
   233                     ]
   234              )
   235  
   236      else
   237          Html.text ""
   238  
   239  
   240  tooltip : Model m -> Maybe Tooltip.Tooltip
   241  tooltip { hovered } =
   242      case hovered of
   243          HoverState.Tooltip (SideBarTeam _ teamName) _ ->
   244              Just
   245                  { body = Html.div Styles.tooltipBody [ Html.text teamName ]
   246                  , attachPosition =
   247                      { direction =
   248                          Tooltip.Right (Styles.tooltipArrowSize - Styles.tooltipOffset)
   249                      , alignment = Tooltip.Middle <| 2 * Styles.tooltipArrowSize
   250                      }
   251                  , arrow = Just { size = Styles.tooltipArrowSize, color = Colors.tooltipBackground }
   252                  }
   253  
   254          HoverState.Tooltip (SideBarPipeline _ pipelineID) _ ->
   255              Just
   256                  { body = Html.div Styles.tooltipBody [ Html.text pipelineID.pipelineName ]
   257                  , attachPosition =
   258                      { direction =
   259                          Tooltip.Right <|
   260                              Styles.tooltipArrowSize
   261                                  + (Styles.starPadding * 2)
   262                                  + Styles.starWidth
   263                                  - Styles.tooltipOffset
   264                      , alignment = Tooltip.Middle <| 2 * Styles.tooltipArrowSize
   265                      }
   266                  , arrow = Just { size = Styles.tooltipArrowSize, color = Colors.tooltipBackground }
   267                  }
   268  
   269          _ ->
   270              Nothing
   271  
   272  
   273  allPipelinesSection : Model m -> Maybe (PipelineScoped a) -> List (Html Message)
   274  allPipelinesSection model currentPipeline =
   275      [ Html.div Styles.sectionHeader [ Html.text "all pipelines" ]
   276      , Html.div [ id "all-pipelines" ]
   277          (model.pipelines
   278              |> RemoteData.withDefault []
   279              |> List.Extra.gatherEqualsBy .teamName
   280              |> List.map
   281                  (\( p, ps ) ->
   282                      Team.team
   283                          { hovered = model.hovered
   284                          , pipelines = (p :: ps) |> List.filter (isPipelineVisible model)
   285                          , currentPipeline = currentPipeline
   286                          , favoritedPipelines = model.favoritedPipelines
   287                          , isFavoritesSection = False
   288                          }
   289                          { name = p.teamName
   290                          , isExpanded = Set.member p.teamName model.expandedTeamsInAllPipelines
   291                          }
   292                          |> Views.viewTeam
   293                  )
   294          )
   295      ]
   296  
   297  
   298  favoritedPipelinesSection : Model m -> Maybe (PipelineScoped a) -> List (Html Message)
   299  favoritedPipelinesSection model currentPipeline =
   300      let
   301          favoritedPipelines =
   302              model.pipelines
   303                  |> RemoteData.withDefault []
   304                  |> List.filter
   305                      (\fp ->
   306                          Set.member fp.id model.favoritedPipelines
   307                      )
   308      in
   309      if List.isEmpty favoritedPipelines then
   310          []
   311  
   312      else
   313          [ Html.div Styles.sectionHeader [ Html.text "favorite pipelines" ]
   314          , Html.div [ id "favorites" ]
   315              (favoritedPipelines
   316                  |> List.Extra.gatherEqualsBy .teamName
   317                  |> List.map
   318                      (\( p, ps ) ->
   319                          Team.team
   320                              { hovered = model.hovered
   321                              , pipelines = p :: ps
   322                              , currentPipeline = currentPipeline
   323                              , favoritedPipelines = model.favoritedPipelines
   324                              , isFavoritesSection = True
   325                              }
   326                              { name = p.teamName
   327                              , isExpanded =
   328                                  not <|
   329                                      Set.member p.teamName model.collapsedTeamsInFavorites
   330                              }
   331                              |> Views.viewTeam
   332                      )
   333              )
   334          , Views.Styles.separator 10
   335          ]
   336  
   337  
   338  hamburgerMenu : Model m -> Html Message
   339  hamburgerMenu model =
   340      if model.screenSize == Mobile then
   341          Html.text ""
   342  
   343      else
   344          let
   345              isHamburgerClickable =
   346                  hasVisiblePipelines model
   347          in
   348          Html.div
   349              (id "hamburger-menu"
   350                  :: Styles.hamburgerMenu
   351                      { isSideBarOpen = model.sideBarState.isOpen && isHamburgerClickable
   352                      , isClickable = isHamburgerClickable
   353                      }
   354                  ++ [ onMouseEnter <| Hover <| Just HamburgerMenu
   355                     , onMouseLeave <| Hover Nothing
   356                     ]
   357                  ++ (if isHamburgerClickable then
   358                          [ onClick <| Click HamburgerMenu ]
   359  
   360                      else
   361                          []
   362                     )
   363              )
   364              [ Icon.icon
   365                  { sizePx = 54, image = Assets.HamburgerMenuIcon }
   366                <|
   367                  (Styles.hamburgerIcon <|
   368                      { isHovered =
   369                          isHamburgerClickable
   370                              && HoverState.isHovered HamburgerMenu model.hovered
   371                      , isActive = model.sideBarState.isOpen
   372                      }
   373                  )
   374              ]
   375  
   376  
   377  hasVisiblePipelines : Model m -> Bool
   378  hasVisiblePipelines model =
   379      model.pipelines
   380          |> RemoteData.map (List.any (isPipelineVisible model))
   381          |> RemoteData.withDefault False
   382  
   383  
   384  isPipelineVisible : { a | favoritedPipelines : Set Concourse.DatabaseID } -> Concourse.Pipeline -> Bool
   385  isPipelineVisible { favoritedPipelines } p =
   386      not p.archived || Set.member p.id favoritedPipelines