github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/web/elm/src/Dashboard/PipelineGrid.elm (about) 1 module Dashboard.PipelineGrid exposing 2 ( Bounds 3 , DropArea 4 , Header 5 , PipelineCard 6 , computeFavoritePipelinesLayout 7 , computeLayout 8 ) 9 10 import Concourse 11 import Dashboard.Drag exposing (dragPipeline) 12 import Dashboard.Group.Models exposing (Group, Pipeline) 13 import Dashboard.Models exposing (DragState(..), DropState(..)) 14 import Dashboard.PipelineGrid.Constants 15 exposing 16 ( cardHeight 17 , cardWidth 18 , headerHeight 19 , padding 20 ) 21 import Dashboard.PipelineGrid.Layout as Layout 22 import Dict exposing (Dict) 23 import List.Extra 24 import Message.Message exposing (DomID(..), DropTarget(..), Message(..)) 25 import UserState exposing (UserState(..)) 26 27 28 type alias Bounds = 29 { x : Float 30 , y : Float 31 , width : Float 32 , height : Float 33 } 34 35 36 type alias PipelineCard = 37 { bounds : Bounds 38 , pipeline : Pipeline 39 } 40 41 42 type alias DropArea = 43 { bounds : Bounds 44 , target : DropTarget 45 } 46 47 48 type alias Header = 49 { bounds : Bounds 50 , header : String 51 } 52 53 54 computeLayout : 55 { dragState : DragState 56 , dropState : DropState 57 , pipelineLayers : Dict ( String, String ) (List (List Concourse.JobIdentifier)) 58 , viewportWidth : Float 59 , viewportHeight : Float 60 , scrollTop : Float 61 } 62 -> Group 63 -> 64 { pipelineCards : List PipelineCard 65 , dropAreas : List DropArea 66 , height : Float 67 } 68 computeLayout params g = 69 let 70 orderedPipelines = 71 case ( params.dragState, params.dropState ) of 72 ( Dragging team pipeline, Dropping target ) -> 73 if g.teamName == team then 74 dragPipeline pipeline target g.pipelines 75 76 else 77 g.pipelines 78 79 _ -> 80 g.pipelines 81 82 numColumns = 83 max 1 (floor (params.viewportWidth / (cardWidth + padding))) 84 85 rowHeight = 86 cardHeight + padding 87 88 isVisible_ = 89 isVisible params.viewportHeight params.scrollTop rowHeight 90 91 cards = 92 orderedPipelines 93 |> previewSizes params.pipelineLayers 94 |> List.map Layout.cardSize 95 |> Layout.layout numColumns 96 97 numRows = 98 cards 99 |> List.map (\c -> c.row + c.spannedRows - 1) 100 |> List.maximum 101 |> Maybe.withDefault 1 102 103 totalCardsHeight = 104 toFloat numRows 105 * cardHeight 106 + padding 107 * toFloat numRows 108 109 cardLookup = 110 cards 111 |> List.map2 Tuple.pair orderedPipelines 112 |> List.map (\( pipeline, card ) -> ( pipeline.id, card )) 113 |> Dict.fromList 114 115 prevAndCurrentCards = 116 cards 117 |> List.map2 Tuple.pair (Nothing :: (cards |> List.map Just)) 118 119 cardBounds = 120 boundsForCell 121 { colGap = padding 122 , rowGap = padding 123 , offsetX = padding 124 , offsetY = 0 125 } 126 127 dropAreaBounds = 128 cardBounds >> (\b -> { b | x = b.x - padding, width = b.width + padding }) 129 130 dropAreas = 131 (prevAndCurrentCards 132 |> List.map2 Tuple.pair g.pipelines 133 |> List.filter (\( _, ( _, card ) ) -> isVisible_ card) 134 |> List.map 135 (\( pipeline, ( prevCard, card ) ) -> 136 let 137 boundsToRightOf otherCard = 138 dropAreaBounds 139 { otherCard 140 | column = otherCard.column + otherCard.spannedColumns 141 , spannedColumns = 1 142 } 143 144 bounds = 145 case prevCard of 146 Just otherCard -> 147 if 148 (otherCard.row < card.row) 149 && (otherCard.column + otherCard.spannedColumns <= numColumns) 150 then 151 boundsToRightOf otherCard 152 153 else 154 dropAreaBounds card 155 156 Nothing -> 157 dropAreaBounds card 158 in 159 { bounds = bounds, target = Before pipeline.name } 160 ) 161 ) 162 ++ (case List.head (List.reverse (List.map2 Tuple.pair cards g.pipelines)) of 163 Just ( lastCard, lastPipeline ) -> 164 if not (isVisible_ lastCard) then 165 [] 166 167 else 168 [ { bounds = 169 dropAreaBounds 170 { lastCard 171 | column = lastCard.column + lastCard.spannedColumns 172 , spannedColumns = 1 173 } 174 , target = After lastPipeline.name 175 } 176 ] 177 178 Nothing -> 179 [] 180 ) 181 182 pipelineCards = 183 g.pipelines 184 |> List.map 185 (\pipeline -> 186 cardLookup 187 |> Dict.get pipeline.id 188 |> Maybe.withDefault 189 { row = 0 190 , column = 0 191 , spannedColumns = 0 192 , spannedRows = 0 193 } 194 |> (\card -> ( pipeline, card )) 195 ) 196 |> List.filter (\( _, card ) -> isVisible_ card) 197 |> List.map 198 (\( pipeline, card ) -> 199 { pipeline = pipeline 200 , bounds = cardBounds card 201 } 202 ) 203 in 204 { pipelineCards = pipelineCards 205 , dropAreas = dropAreas 206 , height = totalCardsHeight 207 } 208 209 210 computeFavoritePipelinesLayout : 211 { pipelineLayers : Dict ( String, String ) (List (List Concourse.JobIdentifier)) 212 , viewportWidth : Float 213 , viewportHeight : Float 214 , scrollTop : Float 215 } 216 -> List Pipeline 217 -> 218 { pipelineCards : List PipelineCard 219 , headers : List Header 220 , height : Float 221 } 222 computeFavoritePipelinesLayout params pipelines = 223 let 224 numColumns = 225 max 1 (floor (params.viewportWidth / (cardWidth + padding))) 226 227 rowHeight = 228 cardHeight + headerHeight 229 230 isVisible_ = 231 isVisible params.viewportHeight params.scrollTop rowHeight 232 233 cards = 234 pipelines 235 |> previewSizes params.pipelineLayers 236 |> List.map Layout.cardSize 237 |> Layout.layout numColumns 238 239 numRows = 240 cards 241 |> List.map (\c -> c.row + c.spannedRows - 1) 242 |> List.maximum 243 |> Maybe.withDefault 1 244 245 totalCardsHeight = 246 toFloat numRows * rowHeight 247 248 cardBounds = 249 boundsForCell 250 { colGap = padding 251 , rowGap = headerHeight 252 , offsetX = padding 253 , offsetY = headerHeight 254 } 255 256 pipelineCards = 257 cards 258 |> List.map2 Tuple.pair pipelines 259 |> List.filter (\( _, card ) -> isVisible_ card) 260 |> List.map 261 (\( pipeline, card ) -> 262 { pipeline = pipeline 263 , bounds = cardBounds card 264 } 265 ) 266 267 headers = 268 pipelineCards 269 |> List.Extra.groupWhile 270 (\c1 c2 -> 271 (c1.pipeline.teamName == c2.pipeline.teamName) 272 && (c1.bounds.y == c2.bounds.y) 273 ) 274 |> List.map 275 (\( c, cs ) -> 276 ( c 277 , case List.Extra.last cs of 278 Nothing -> 279 c 280 281 Just tail -> 282 tail 283 ) 284 ) 285 |> List.foldl 286 (\( first, last ) ( prevTeam, headers_ ) -> 287 let 288 curTeam = 289 first.pipeline.teamName 290 291 header = 292 case prevTeam of 293 Nothing -> 294 curTeam 295 296 Just prevTeam_ -> 297 if prevTeam_ == curTeam then 298 curTeam ++ " (continued)" 299 300 else 301 curTeam 302 in 303 ( Just curTeam 304 , { header = header 305 , bounds = 306 { x = first.bounds.x 307 , y = first.bounds.y - headerHeight 308 , width = last.bounds.x + cardWidth - first.bounds.x 309 , height = headerHeight 310 } 311 } 312 :: headers_ 313 ) 314 ) 315 ( Nothing, [] ) 316 |> Tuple.second 317 in 318 { pipelineCards = pipelineCards 319 , headers = headers 320 , height = totalCardsHeight 321 } 322 323 324 previewSizes : 325 Dict ( String, String ) (List (List Concourse.JobIdentifier)) 326 -> List Pipeline 327 -> List ( Int, Int ) 328 previewSizes pipelineLayers = 329 List.map 330 (\pipeline -> 331 Dict.get ( pipeline.teamName, pipeline.name ) pipelineLayers 332 |> Maybe.withDefault [] 333 ) 334 >> List.map 335 (\layers -> 336 ( List.length layers 337 , layers 338 |> List.map List.length 339 |> List.maximum 340 |> Maybe.withDefault 0 341 ) 342 ) 343 344 345 isVisible : Float -> Float -> Float -> { r | row : Int, spannedRows : Int } -> Bool 346 isVisible viewportHeight scrollTop rowHeight { row, spannedRows } = 347 let 348 numRowsVisible = 349 ceiling (viewportHeight / rowHeight) + 1 350 351 numRowsOffset = 352 floor (scrollTop / rowHeight) 353 in 354 (numRowsOffset < row + spannedRows) 355 && (row <= numRowsOffset + numRowsVisible) 356 357 358 boundsForCell : 359 { colGap : Float 360 , rowGap : Float 361 , offsetX : Float 362 , offsetY : Float 363 } 364 -> Layout.Card 365 -> Bounds 366 boundsForCell { colGap, rowGap, offsetX, offsetY } card = 367 let 368 colWidth = 369 cardWidth + colGap 370 371 rowHeight = 372 cardHeight + rowGap 373 in 374 { x = (toFloat card.column - 1) * colWidth + offsetX 375 , y = (toFloat card.row - 1) * rowHeight + offsetY 376 , width = 377 cardWidth 378 * toFloat card.spannedColumns 379 + colGap 380 * (toFloat card.spannedColumns - 1) 381 , height = 382 cardHeight 383 * toFloat card.spannedRows 384 + rowGap 385 * (toFloat card.spannedRows - 1) 386 }