github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/web/elm/src/Build/Shortcuts.elm (about) 1 module Build.Shortcuts exposing (handleDelivery, keyboardHelp) 2 3 import Build.Header.Models exposing (HistoryItem) 4 import Build.Models exposing (ShortcutsModel) 5 import Concourse.BuildStatus 6 import EffectTransformer exposing (ET) 7 import Html exposing (Html) 8 import Html.Attributes exposing (class, classList) 9 import Keyboard 10 import Maybe.Extra 11 import Message.Effects exposing (Effect(..)) 12 import Message.Message exposing (DomID(..), Message(..)) 13 import Message.ScrollDirection exposing (ScrollDirection(..)) 14 import Message.Subscription exposing (Delivery(..)) 15 import Routes 16 17 18 bodyId : String 19 bodyId = 20 "build-body" 21 22 23 keyboardHelp : Bool -> Html Message 24 keyboardHelp showHelp = 25 let 26 shortcuts = 27 [ { keys = [ "h", "l" ], description = "previous/next build" } 28 , { keys = [ "j", "k" ], description = "scroll down/up" } 29 , { keys = [ "T" ], description = "trigger a new build" } 30 , { keys = [ "R" ], description = "rerun the current build" } 31 , { keys = [ "A" ], description = "abort build" } 32 , { keys = [ "gg" ], description = "scroll to the top" } 33 , { keys = [ "G" ], description = "scroll to the bottom" } 34 , { keys = [ "?" ], description = "hide/show help" } 35 ] 36 37 keySpan key = 38 Html.span [ class "key" ] [ Html.text key ] 39 40 helpLine shortcut = 41 Html.div 42 [ class "help-line" ] 43 [ Html.div [ class "keys" ] (List.map keySpan shortcut.keys) 44 , Html.text shortcut.description 45 ] 46 in 47 Html.div 48 [ classList 49 [ ( "keyboard-help", True ) 50 , ( "hidden", not showHelp ) 51 ] 52 ] 53 (Html.div [ class "help-title" ] [ Html.text "keyboard shortcuts" ] 54 :: List.map helpLine shortcuts 55 ) 56 57 58 historyItem : ShortcutsModel r -> HistoryItem 59 historyItem model = 60 { id = model.id 61 , name = model.name 62 , status = model.status 63 , duration = model.duration 64 } 65 66 67 prevHistoryItem : List HistoryItem -> HistoryItem -> Maybe HistoryItem 68 prevHistoryItem builds b = 69 case builds of 70 first :: second :: rest -> 71 if first == b then 72 Just second 73 74 else 75 prevHistoryItem (second :: rest) b 76 77 _ -> 78 Nothing 79 80 81 nextHistoryItem : List HistoryItem -> HistoryItem -> Maybe HistoryItem 82 nextHistoryItem builds b = 83 case builds of 84 first :: second :: rest -> 85 if second == b then 86 Just first 87 88 else 89 nextHistoryItem (second :: rest) b 90 91 _ -> 92 Nothing 93 94 95 handleDelivery : Delivery -> ET (ShortcutsModel r) 96 handleDelivery delivery ( model, effects ) = 97 case delivery of 98 KeyDown keyEvent -> 99 handleKeyPressed keyEvent ( model, effects ) 100 101 KeyUp keyEvent -> 102 case keyEvent.code of 103 Keyboard.T -> 104 ( { model | isTriggerBuildKeyDown = False }, effects ) 105 106 _ -> 107 ( model, effects ) 108 109 _ -> 110 ( model, effects ) 111 112 113 handleKeyPressed : Keyboard.KeyEvent -> ET (ShortcutsModel r) 114 handleKeyPressed keyEvent ( model, effects ) = 115 let 116 newModel = 117 case ( model.previousKeyPress, keyEvent.shiftKey, keyEvent.code ) of 118 ( Nothing, False, Keyboard.G ) -> 119 { model | previousKeyPress = Just keyEvent } 120 121 _ -> 122 { model | previousKeyPress = Nothing } 123 in 124 if Keyboard.hasControlModifier keyEvent then 125 ( newModel, effects ) 126 127 else 128 case ( keyEvent.code, keyEvent.shiftKey ) of 129 ( Keyboard.J, False ) -> 130 ( newModel, [ Scroll Down bodyId ] ) 131 132 ( Keyboard.K, False ) -> 133 ( newModel, [ Scroll Up bodyId ] ) 134 135 ( Keyboard.G, True ) -> 136 ( { newModel | autoScroll = True }, [ Scroll ToBottom bodyId ] ) 137 138 ( Keyboard.G, False ) -> 139 if 140 (model.previousKeyPress |> Maybe.map .code) 141 == Just Keyboard.G 142 then 143 ( { newModel | autoScroll = False }, [ Scroll ToTop bodyId ] ) 144 145 else 146 ( newModel, effects ) 147 148 ( Keyboard.Slash, True ) -> 149 ( { newModel | showHelp = not newModel.showHelp }, effects ) 150 151 ( Keyboard.H, False ) -> 152 case nextHistoryItem model.history (historyItem model) of 153 Just item -> 154 ( newModel 155 , effects 156 ++ [ NavigateTo <| 157 Routes.toString <| 158 Routes.buildRoute 159 item.id 160 item.name 161 newModel.job 162 ] 163 ) 164 165 Nothing -> 166 ( newModel, effects ) 167 168 ( Keyboard.L, False ) -> 169 case prevHistoryItem newModel.history (historyItem newModel) of 170 Just item -> 171 ( newModel 172 , effects 173 ++ [ NavigateTo <| 174 Routes.toString <| 175 Routes.buildRoute 176 item.id 177 item.name 178 newModel.job 179 ] 180 ) 181 182 Nothing -> 183 ( newModel, effects ) 184 185 ( Keyboard.T, True ) -> 186 if not newModel.isTriggerBuildKeyDown then 187 (newModel.job 188 |> Maybe.map (DoTriggerBuild >> (::) >> Tuple.mapSecond) 189 |> Maybe.withDefault identity 190 ) 191 ( { newModel | isTriggerBuildKeyDown = True }, effects ) 192 193 else 194 ( newModel, effects ) 195 196 ( Keyboard.R, True ) -> 197 ( newModel 198 , effects 199 ++ (if Concourse.BuildStatus.isRunning newModel.status then 200 [] 201 202 else 203 newModel.job 204 |> Maybe.map 205 (\j -> 206 RerunJobBuild 207 { teamName = j.teamName 208 , pipelineName = j.pipelineName 209 , jobName = j.jobName 210 , buildName = newModel.name 211 } 212 ) 213 |> Maybe.Extra.toList 214 ) 215 ) 216 217 ( Keyboard.A, True ) -> 218 if Just (historyItem newModel) == List.head newModel.history then 219 ( newModel, DoAbortBuild newModel.id :: effects ) 220 221 else 222 ( newModel, effects ) 223 224 _ -> 225 ( newModel, effects )