github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/web/elm/src/Dashboard/SearchBar.elm (about) 1 module Dashboard.SearchBar exposing 2 ( handleDelivery 3 , searchInputId 4 , update 5 , view 6 ) 7 8 import Application.Models exposing (Session) 9 import Array 10 import Concourse 11 import Concourse.PipelineStatus 12 exposing 13 ( PipelineStatus(..) 14 , StatusDetails(..) 15 ) 16 import Dashboard.Group.Models exposing (Pipeline) 17 import Dashboard.Models exposing (Dropdown(..), Model) 18 import Dashboard.Styles as Styles 19 import Dict exposing (Dict) 20 import EffectTransformer exposing (ET) 21 import FetchResult exposing (FetchResult) 22 import Html exposing (Html) 23 import Html.Attributes exposing (attribute, id, placeholder, value) 24 import Html.Events exposing (onBlur, onClick, onFocus, onInput, onMouseDown) 25 import Keyboard 26 import Message.Callback exposing (Callback(..)) 27 import Message.Effects exposing (Effect(..)) 28 import Message.Message exposing (DomID(..), Message(..)) 29 import Message.Subscription exposing (Delivery(..)) 30 import Routes 31 import ScreenSize exposing (ScreenSize) 32 import Set 33 34 35 searchInputId : String 36 searchInputId = 37 "search-input-field" 38 39 40 update : Session -> Message -> ET Model 41 update session msg ( model, effects ) = 42 case msg of 43 Click ShowSearchButton -> 44 showSearchInput session ( model, effects ) 45 46 Click ClearSearchButton -> 47 ( { model | query = "" } 48 , effects 49 ++ [ Focus searchInputId 50 , ModifyUrl <| 51 Routes.toString <| 52 Routes.Dashboard 53 { searchType = Routes.Normal "" 54 , dashboardView = model.dashboardView 55 } 56 ] 57 ) 58 59 FilterMsg query -> 60 ( { model | query = query } 61 , effects 62 ++ [ Focus searchInputId 63 , ModifyUrl <| 64 Routes.toString <| 65 Routes.Dashboard 66 { searchType = Routes.Normal query 67 , dashboardView = model.dashboardView 68 } 69 ] 70 ) 71 72 FocusMsg -> 73 ( { model | dropdown = Shown Nothing }, effects ) 74 75 BlurMsg -> 76 ( { model | dropdown = Hidden }, effects ) 77 78 _ -> 79 ( model, effects ) 80 81 82 showSearchInput : { a | screenSize : ScreenSize } -> ET Model 83 showSearchInput session ( model, effects ) = 84 if model.highDensity then 85 ( model, effects ) 86 87 else 88 let 89 isDropDownHidden = 90 model.dropdown == Hidden 91 92 isMobile = 93 session.screenSize == ScreenSize.Mobile 94 in 95 if isDropDownHidden && isMobile && model.query == "" then 96 ( { model | dropdown = Shown Nothing } 97 , effects ++ [ Focus searchInputId ] 98 ) 99 100 else 101 ( model, effects ) 102 103 104 screenResize : Float -> Model -> Model 105 screenResize width model = 106 let 107 newSize = 108 ScreenSize.fromWindowSize width 109 in 110 case newSize of 111 ScreenSize.Desktop -> 112 { model | dropdown = Hidden } 113 114 ScreenSize.BigDesktop -> 115 { model | dropdown = Hidden } 116 117 ScreenSize.Mobile -> 118 model 119 120 121 handleDelivery : Delivery -> ET Model 122 handleDelivery delivery ( model, effects ) = 123 case delivery of 124 WindowResized width _ -> 125 ( screenResize width model, effects ) 126 127 KeyDown keyEvent -> 128 let 129 options = 130 dropdownOptions model 131 in 132 case keyEvent.code of 133 Keyboard.ArrowUp -> 134 ( { model 135 | dropdown = 136 arrowUp options model.dropdown 137 } 138 , effects 139 ) 140 141 Keyboard.ArrowDown -> 142 ( { model 143 | dropdown = 144 arrowDown options model.dropdown 145 } 146 , effects 147 ) 148 149 Keyboard.Enter -> 150 case model.dropdown of 151 Shown (Just idx) -> 152 let 153 selectedItem = 154 options 155 |> Array.fromList 156 |> Array.get idx 157 |> Maybe.withDefault 158 model.query 159 in 160 ( { model 161 | dropdown = Shown Nothing 162 , query = selectedItem 163 } 164 , [ ModifyUrl <| 165 Routes.toString <| 166 Routes.Dashboard 167 { searchType = Routes.Normal selectedItem 168 , dashboardView = model.dashboardView 169 } 170 ] 171 ) 172 173 Shown Nothing -> 174 ( model, effects ) 175 176 Hidden -> 177 ( model, effects ) 178 179 Keyboard.Escape -> 180 ( model, effects ++ [ Blur searchInputId ] ) 181 182 Keyboard.Slash -> 183 ( model 184 , if keyEvent.shiftKey then 185 effects 186 187 else 188 effects ++ [ Focus searchInputId ] 189 ) 190 191 -- any other keycode 192 _ -> 193 ( model, effects ) 194 195 _ -> 196 ( model, effects ) 197 198 199 arrowUp : List a -> Dropdown -> Dropdown 200 arrowUp options dropdown = 201 case dropdown of 202 Shown Nothing -> 203 let 204 lastItem = 205 List.length options - 1 206 in 207 Shown (Just lastItem) 208 209 Shown (Just idx) -> 210 let 211 newSelection = 212 modBy (List.length options) (idx - 1) 213 in 214 Shown (Just newSelection) 215 216 Hidden -> 217 Hidden 218 219 220 arrowDown : List a -> Dropdown -> Dropdown 221 arrowDown options dropdown = 222 case dropdown of 223 Shown Nothing -> 224 Shown (Just 0) 225 226 Shown (Just idx) -> 227 let 228 newSelection = 229 modBy (List.length options) (idx + 1) 230 in 231 Shown (Just newSelection) 232 233 Hidden -> 234 Hidden 235 236 237 view : 238 { a | screenSize : ScreenSize } 239 -> 240 { b 241 | query : String 242 , dropdown : Dropdown 243 , teams : FetchResult (List Concourse.Team) 244 , highDensity : Bool 245 , pipelines : Maybe (Dict String (List Pipeline)) 246 } 247 -> Html Message 248 view session ({ query, dropdown, pipelines } as params) = 249 let 250 isDropDownHidden = 251 dropdown == Hidden 252 253 isMobile = 254 session.screenSize == ScreenSize.Mobile 255 256 noPipelines = 257 pipelines 258 |> Maybe.withDefault Dict.empty 259 |> Dict.values 260 |> List.all List.isEmpty 261 262 clearSearchButton = 263 if String.length query > 0 then 264 [ Html.div 265 ([ id "search-clear" 266 , onClick <| Click ClearSearchButton 267 ] 268 ++ Styles.searchClearButton 269 ) 270 [] 271 ] 272 273 else 274 [] 275 in 276 if noPipelines then 277 Html.text "" 278 279 else if isDropDownHidden && isMobile && query == "" then 280 Html.div 281 (Styles.showSearchContainer 282 { screenSize = session.screenSize 283 , highDensity = params.highDensity 284 } 285 ) 286 [ Html.div 287 ([ id "show-search-button" 288 , onClick <| Click ShowSearchButton 289 ] 290 ++ Styles.searchButton 291 ) 292 [] 293 ] 294 295 else 296 Html.div 297 (id "search-container" :: Styles.searchContainer session.screenSize) 298 (Html.input 299 ([ id searchInputId 300 , placeholder "filter pipelines by name, status, or team" 301 , attribute "autocomplete" "off" 302 , value query 303 , onFocus FocusMsg 304 , onBlur BlurMsg 305 , onInput FilterMsg 306 ] 307 ++ Styles.searchInput 308 session.screenSize 309 (String.length query > 0) 310 ) 311 [] 312 :: clearSearchButton 313 ++ viewDropdownItems session params 314 ) 315 316 317 viewDropdownItems : 318 { a 319 | screenSize : ScreenSize 320 } 321 -> 322 { b 323 | query : String 324 , dropdown : Dropdown 325 , teams : FetchResult (List Concourse.Team) 326 , pipelines : Maybe (Dict String (List Pipeline)) 327 } 328 -> List (Html Message) 329 viewDropdownItems { screenSize } ({ dropdown, query } as model) = 330 case dropdown of 331 Hidden -> 332 [] 333 334 Shown selectedIdx -> 335 let 336 dropdownItem : Int -> String -> Html Message 337 dropdownItem idx text = 338 Html.li 339 (onMouseDown (FilterMsg text) 340 :: Styles.dropdownItem 341 (Just idx == selectedIdx) 342 (String.length query > 0) 343 ) 344 [ Html.text text ] 345 in 346 [ Html.ul 347 (id "search-dropdown" :: Styles.dropdownContainer screenSize) 348 (List.indexedMap dropdownItem (dropdownOptions model)) 349 ] 350 351 352 dropdownOptions : 353 { a 354 | query : String 355 , teams : FetchResult (List Concourse.Team) 356 , pipelines : Maybe (Dict String (List Pipeline)) 357 } 358 -> List String 359 dropdownOptions { query, teams, pipelines } = 360 case String.trim query of 361 "" -> 362 [ "status: ", "team: " ] 363 364 "status:" -> 365 [ "status: paused" 366 , "status: pending" 367 , "status: failed" 368 , "status: errored" 369 , "status: aborted" 370 , "status: running" 371 , "status: succeeded" 372 ] 373 374 "team:" -> 375 Set.union 376 (teams 377 |> FetchResult.withDefault [] 378 |> List.map .name 379 |> Set.fromList 380 ) 381 (pipelines 382 |> Maybe.withDefault Dict.empty 383 |> Dict.keys 384 |> Set.fromList 385 ) 386 |> Set.toList 387 |> List.take 10 388 |> List.map (\teamName -> "team: " ++ teamName) 389 390 _ -> 391 []