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